[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/editor/tinymce/tiny_mce/3.5.10/ -> tiny_mce_src.js (source)

   1  // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
   2  (function(win) {
   3      var whiteSpaceRe = /^\s*|\s*$/g,
   4          undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
   5  
   6      var tinymce = {
   7          majorVersion : '3',
   8  
   9          minorVersion : '5.10',
  10  
  11          releaseDate : '2013-10-24',
  12  
  13          _init : function() {
  14              var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
  15  
  16              t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1);
  17  
  18              t.isOpera = win.opera && opera.buildNumber;
  19  
  20              t.isWebKit = /WebKit/.test(ua);
  21  
  22              t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11;
  23  
  24              t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
  25  
  26              t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
  27  
  28              t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
  29  
  30              t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
  31  
  32              t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);
  33  
  34              t.isMac = ua.indexOf('Mac') != -1;
  35  
  36              t.isAir = /adobeair/i.test(ua);
  37  
  38              t.isIDevice = /(iPad|iPhone)/.test(ua);
  39              
  40              t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
  41  
  42              // TinyMCE .NET webcontrol might be setting the values for TinyMCE
  43              if (win.tinyMCEPreInit) {
  44                  t.suffix = tinyMCEPreInit.suffix;
  45                  t.baseURL = tinyMCEPreInit.base;
  46                  t.query = tinyMCEPreInit.query;
  47                  return;
  48              }
  49  
  50              // Get suffix and base
  51              t.suffix = '';
  52  
  53              // If base element found, add that infront of baseURL
  54              nl = d.getElementsByTagName('base');
  55              for (i=0; i<nl.length; i++) {
  56                  v = nl[i].href;
  57                  if (v) {
  58                      // Host only value like http://site.com or http://site.com:8008
  59                      if (/^https?:\/\/[^\/]+$/.test(v))
  60                          v += '/';
  61  
  62                      base = v ? v.match(/.*\//)[0] : ''; // Get only directory
  63                  }
  64              }
  65  
  66  			function getBase(n) {
  67                  if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
  68                      if (/_(src|dev)\.js/g.test(n.src))
  69                          t.suffix = '_src';
  70  
  71                      if ((p = n.src.indexOf('?')) != -1)
  72                          t.query = n.src.substring(p + 1);
  73  
  74                      t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
  75  
  76                      // If path to script is relative and a base href was found add that one infront
  77                      // the src property will always be an absolute one on non IE browsers and IE 8
  78                      // so this logic will basically only be executed on older IE versions
  79                      if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
  80                          t.baseURL = base + t.baseURL;
  81  
  82                      return t.baseURL;
  83                  }
  84  
  85                  return null;
  86              };
  87  
  88              // Check document
  89              nl = d.getElementsByTagName('script');
  90              for (i=0; i<nl.length; i++) {
  91                  if (getBase(nl[i]))
  92                      return;
  93              }
  94  
  95              // Check head
  96              n = d.getElementsByTagName('head')[0];
  97              if (n) {
  98                  nl = n.getElementsByTagName('script');
  99                  for (i=0; i<nl.length; i++) {
 100                      if (getBase(nl[i]))
 101                          return;
 102                  }
 103              }
 104  
 105              return;
 106          },
 107  
 108          is : function(o, t) {
 109              if (!t)
 110                  return o !== undef;
 111  
 112              if (t == 'array' && tinymce.isArray(o))
 113                  return true;
 114  
 115              return typeof(o) == t;
 116          },
 117  
 118          isArray: Array.isArray || function(obj) {
 119              return Object.prototype.toString.call(obj) === "[object Array]";
 120          },
 121  
 122          makeMap : function(items, delim, map) {
 123              var i;
 124  
 125              items = items || [];
 126              delim = delim || ',';
 127  
 128              if (typeof(items) == "string")
 129                  items = items.split(delim);
 130  
 131              map = map || {};
 132  
 133              i = items.length;
 134              while (i--)
 135                  map[items[i]] = {};
 136  
 137              return map;
 138          },
 139  
 140          each : function(o, cb, s) {
 141              var n, l;
 142  
 143              if (!o)
 144                  return 0;
 145  
 146              s = s || o;
 147  
 148              if (o.length !== undef) {
 149                  // Indexed arrays, needed for Safari
 150                  for (n=0, l = o.length; n < l; n++) {
 151                      if (cb.call(s, o[n], n, o) === false)
 152                          return 0;
 153                  }
 154              } else {
 155                  // Hashtables
 156                  for (n in o) {
 157                      if (o.hasOwnProperty(n)) {
 158                          if (cb.call(s, o[n], n, o) === false)
 159                              return 0;
 160                      }
 161                  }
 162              }
 163  
 164              return 1;
 165          },
 166  
 167  
 168          map : function(a, f) {
 169              var o = [];
 170  
 171              tinymce.each(a, function(v) {
 172                  o.push(f(v));
 173              });
 174  
 175              return o;
 176          },
 177  
 178          grep : function(a, f) {
 179              var o = [];
 180  
 181              tinymce.each(a, function(v) {
 182                  if (!f || f(v))
 183                      o.push(v);
 184              });
 185  
 186              return o;
 187          },
 188  
 189          inArray : function(a, v) {
 190              var i, l;
 191  
 192              if (a) {
 193                  for (i = 0, l = a.length; i < l; i++) {
 194                      if (a[i] === v)
 195                          return i;
 196                  }
 197              }
 198  
 199              return -1;
 200          },
 201  
 202          extend : function(obj, ext) {
 203              var i, l, name, args = arguments, value;
 204  
 205              for (i = 1, l = args.length; i < l; i++) {
 206                  ext = args[i];
 207                  for (name in ext) {
 208                      if (ext.hasOwnProperty(name)) {
 209                          value = ext[name];
 210  
 211                          if (value !== undef) {
 212                              obj[name] = value;
 213                          }
 214                      }
 215                  }
 216              }
 217  
 218              return obj;
 219          },
 220  
 221  
 222          trim : function(s) {
 223              return (s ? '' + s : '').replace(whiteSpaceRe, '');
 224          },
 225  
 226          create : function(s, p, root) {
 227              var t = this, sp, ns, cn, scn, c, de = 0;
 228  
 229              // Parse : <prefix> <class>:<super class>
 230              s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
 231              cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
 232  
 233              // Create namespace for new class
 234              ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
 235  
 236              // Class already exists
 237              if (ns[cn])
 238                  return;
 239  
 240              // Make pure static class
 241              if (s[2] == 'static') {
 242                  ns[cn] = p;
 243  
 244                  if (this.onCreate)
 245                      this.onCreate(s[2], s[3], ns[cn]);
 246  
 247                  return;
 248              }
 249  
 250              // Create default constructor
 251              if (!p[cn]) {
 252                  p[cn] = function() {};
 253                  de = 1;
 254              }
 255  
 256              // Add constructor and methods
 257              ns[cn] = p[cn];
 258              t.extend(ns[cn].prototype, p);
 259  
 260              // Extend
 261              if (s[5]) {
 262                  sp = t.resolve(s[5]).prototype;
 263                  scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
 264  
 265                  // Extend constructor
 266                  c = ns[cn];
 267                  if (de) {
 268                      // Add passthrough constructor
 269                      ns[cn] = function() {
 270                          return sp[scn].apply(this, arguments);
 271                      };
 272                  } else {
 273                      // Add inherit constructor
 274                      ns[cn] = function() {
 275                          this.parent = sp[scn];
 276                          return c.apply(this, arguments);
 277                      };
 278                  }
 279                  ns[cn].prototype[cn] = ns[cn];
 280  
 281                  // Add super methods
 282                  t.each(sp, function(f, n) {
 283                      ns[cn].prototype[n] = sp[n];
 284                  });
 285  
 286                  // Add overridden methods
 287                  t.each(p, function(f, n) {
 288                      // Extend methods if needed
 289                      if (sp[n]) {
 290                          ns[cn].prototype[n] = function() {
 291                              this.parent = sp[n];
 292                              return f.apply(this, arguments);
 293                          };
 294                      } else {
 295                          if (n != cn)
 296                              ns[cn].prototype[n] = f;
 297                      }
 298                  });
 299              }
 300  
 301              // Add static methods
 302              t.each(p['static'], function(f, n) {
 303                  ns[cn][n] = f;
 304              });
 305  
 306              if (this.onCreate)
 307                  this.onCreate(s[2], s[3], ns[cn].prototype);
 308          },
 309  
 310          walk : function(o, f, n, s) {
 311              s = s || this;
 312  
 313              if (o) {
 314                  if (n)
 315                      o = o[n];
 316  
 317                  tinymce.each(o, function(o, i) {
 318                      if (f.call(s, o, i, n) === false)
 319                          return false;
 320  
 321                      tinymce.walk(o, f, n, s);
 322                  });
 323              }
 324          },
 325  
 326          createNS : function(n, o) {
 327              var i, v;
 328  
 329              o = o || win;
 330  
 331              n = n.split('.');
 332              for (i=0; i<n.length; i++) {
 333                  v = n[i];
 334  
 335                  if (!o[v])
 336                      o[v] = {};
 337  
 338                  o = o[v];
 339              }
 340  
 341              return o;
 342          },
 343  
 344          resolve : function(n, o) {
 345              var i, l;
 346  
 347              o = o || win;
 348  
 349              n = n.split('.');
 350              for (i = 0, l = n.length; i < l; i++) {
 351                  o = o[n[i]];
 352  
 353                  if (!o)
 354                      break;
 355              }
 356  
 357              return o;
 358          },
 359  
 360          addUnload : function(f, s) {
 361              var t = this, unload;
 362  
 363              unload = function() {
 364                  var li = t.unloads, o, n;
 365  
 366                  if (li) {
 367                      // Call unload handlers
 368                      for (n in li) {
 369                          o = li[n];
 370  
 371                          if (o && o.func)
 372                              o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
 373                      }
 374  
 375                      // Detach unload function
 376                      if (win.detachEvent) {
 377                          win.detachEvent('onbeforeunload', fakeUnload);
 378                          win.detachEvent('onunload', unload);
 379                      } else if (win.removeEventListener)
 380                          win.removeEventListener('unload', unload, false);
 381  
 382                      // Destroy references
 383                      t.unloads = o = li = w = unload = 0;
 384  
 385                      // Run garbarge collector on IE
 386                      if (win.CollectGarbage)
 387                          CollectGarbage();
 388                  }
 389              };
 390  
 391  			function fakeUnload() {
 392                  var d = document;
 393  
 394  				function stop() {
 395                      // Prevent memory leak
 396                      d.detachEvent('onstop', stop);
 397  
 398                      // Call unload handler
 399                      if (unload)
 400                          unload();
 401  
 402                      d = 0;
 403                  };
 404  
 405                  // Is there things still loading, then do some magic
 406                  if (d.readyState == 'interactive') {
 407                      // Fire unload when the currently loading page is stopped
 408                      if (d)
 409                          d.attachEvent('onstop', stop);
 410  
 411                      // Remove onstop listener after a while to prevent the unload function
 412                      // to execute if the user presses cancel in an onbeforeunload
 413                      // confirm dialog and then presses the browser stop button
 414                      win.setTimeout(function() {
 415                          if (d)
 416                              d.detachEvent('onstop', stop);
 417                      }, 0);
 418                  }
 419              };
 420  
 421              f = {func : f, scope : s || this};
 422  
 423              if (!t.unloads) {
 424                  // Attach unload handler
 425                  if (win.attachEvent) {
 426                      win.attachEvent('onunload', unload);
 427                      win.attachEvent('onbeforeunload', fakeUnload);
 428                  } else if (win.addEventListener)
 429                      win.addEventListener('unload', unload, false);
 430  
 431                  // Setup initial unload handler array
 432                  t.unloads = [f];
 433              } else
 434                  t.unloads.push(f);
 435  
 436              return f;
 437          },
 438  
 439          removeUnload : function(f) {
 440              var u = this.unloads, r = null;
 441  
 442              tinymce.each(u, function(o, i) {
 443                  if (o && o.func == f) {
 444                      u.splice(i, 1);
 445                      r = f;
 446                      return false;
 447                  }
 448              });
 449  
 450              return r;
 451          },
 452  
 453          explode : function(s, d) {
 454              if (!s || tinymce.is(s, 'array')) {
 455                  return s;
 456              }
 457  
 458              return tinymce.map(s.split(d || ','), tinymce.trim);
 459          },
 460  
 461          _addVer : function(u) {
 462              var v;
 463  
 464              if (!this.query)
 465                  return u;
 466  
 467              v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
 468  
 469              if (u.indexOf('#') == -1)
 470                  return u + v;
 471  
 472              return u.replace('#', v + '#');
 473          },
 474  
 475          // Fix function for IE 9 where regexps isn't working correctly
 476          // Todo: remove me once MS fixes the bug
 477          _replace : function(find, replace, str) {
 478              // On IE9 we have to fake $x replacement
 479              if (isRegExpBroken) {
 480                  return str.replace(find, function() {
 481                      var val = replace, args = arguments, i;
 482  
 483                      for (i = 0; i < args.length - 2; i++) {
 484                          if (args[i] === undef) {
 485                              val = val.replace(new RegExp('\\$' + i, 'g'), '');
 486                          } else {
 487                              val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
 488                          }
 489                      }
 490  
 491                      return val;
 492                  });
 493              }
 494  
 495              return str.replace(find, replace);
 496          }
 497  
 498          };
 499  
 500      // Initialize the API
 501      tinymce._init();
 502  
 503      // Expose tinymce namespace to the global namespace (window)
 504      win.tinymce = win.tinyMCE = tinymce;
 505  
 506      // Describe the different namespaces
 507  
 508      })(window);
 509  tinymce.create('tinymce.util.Dispatcher', {
 510      scope : null,
 511      listeners : null,
 512      inDispatch: false,
 513  
 514      Dispatcher : function(scope) {
 515          this.scope = scope || this;
 516          this.listeners = [];
 517      },
 518  
 519      add : function(callback, scope) {
 520          this.listeners.push({cb : callback, scope : scope || this.scope});
 521  
 522          return callback;
 523      },
 524  
 525      addToTop : function(callback, scope) {
 526          var self = this, listener = {cb : callback, scope : scope || self.scope};
 527  
 528          // Create new listeners if addToTop is executed in a dispatch loop
 529          if (self.inDispatch) {
 530              self.listeners = [listener].concat(self.listeners);
 531          } else {
 532              self.listeners.unshift(listener);
 533          }
 534  
 535          return callback;
 536      },
 537  
 538      remove : function(callback) {
 539          var listeners = this.listeners, output = null;
 540  
 541          tinymce.each(listeners, function(listener, i) {
 542              if (callback == listener.cb) {
 543                  output = listener;
 544                  listeners.splice(i, 1);
 545                  return false;
 546              }
 547          });
 548  
 549          return output;
 550      },
 551  
 552      dispatch : function() {
 553          var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
 554  
 555          self.inDispatch = true;
 556          
 557          // Needs to be a real loop since the listener count might change while looping
 558          // And this is also more efficient
 559          for (i = 0; i < listeners.length; i++) {
 560              listener = listeners[i];
 561              returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
 562  
 563              if (returnValue === false)
 564                  break;
 565          }
 566  
 567          self.inDispatch = false;
 568  
 569          return returnValue;
 570      }
 571  
 572      });
 573  (function() {
 574      var each = tinymce.each;
 575  
 576      tinymce.create('tinymce.util.URI', {
 577          URI : function(u, s) {
 578              var t = this, o, a, b, base_url;
 579  
 580              // Trim whitespace
 581              u = tinymce.trim(u);
 582  
 583              // Default settings
 584              s = t.settings = s || {};
 585  
 586              // Strange app protocol that isn't http/https or local anchor
 587              // For example: mailto,skype,tel etc.
 588              if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
 589                  t.source = u;
 590                  return;
 591              }
 592  
 593              // Absolute path with no host, fake host and protocol
 594              if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
 595                  u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
 596  
 597              // Relative path http:// or protocol relative //path
 598              if (!/^[\w\-]*:?\/\//.test(u)) {
 599                  base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
 600                  u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
 601              }
 602  
 603              // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
 604              u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
 605              u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
 606              each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
 607                  var s = u[i];
 608  
 609                  // Zope 3 workaround, they use @@something
 610                  if (s)
 611                      s = s.replace(/\(mce_at\)/g, '@@');
 612  
 613                  t[v] = s;
 614              });
 615  
 616              b = s.base_uri;
 617              if (b) {
 618                  if (!t.protocol)
 619                      t.protocol = b.protocol;
 620  
 621                  if (!t.userInfo)
 622                      t.userInfo = b.userInfo;
 623  
 624                  if (!t.port && t.host === 'mce_host')
 625                      t.port = b.port;
 626  
 627                  if (!t.host || t.host === 'mce_host')
 628                      t.host = b.host;
 629  
 630                  t.source = '';
 631              }
 632  
 633              //t.path = t.path || '/';
 634          },
 635  
 636          setPath : function(p) {
 637              var t = this;
 638  
 639              p = /^(.*?)\/?(\w+)?$/.exec(p);
 640  
 641              // Update path parts
 642              t.path = p[0];
 643              t.directory = p[1];
 644              t.file = p[2];
 645  
 646              // Rebuild source
 647              t.source = '';
 648              t.getURI();
 649          },
 650  
 651          toRelative : function(u) {
 652              var t = this, o;
 653  
 654              if (u === "./")
 655                  return u;
 656  
 657              u = new tinymce.util.URI(u, {base_uri : t});
 658  
 659              // Not on same domain/port or protocol
 660              if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
 661                  return u.getURI();
 662  
 663              var tu = t.getURI(), uu = u.getURI();
 664              
 665              // Allow usage of the base_uri when relative_urls = true
 666              if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
 667                  return tu;
 668  
 669              o = t.toRelPath(t.path, u.path);
 670  
 671              // Add query
 672              if (u.query)
 673                  o += '?' + u.query;
 674  
 675              // Add anchor
 676              if (u.anchor)
 677                  o += '#' + u.anchor;
 678  
 679              return o;
 680          },
 681      
 682          toAbsolute : function(u, nh) {
 683              u = new tinymce.util.URI(u, {base_uri : this});
 684  
 685              return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
 686          },
 687  
 688          toRelPath : function(base, path) {
 689              var items, bp = 0, out = '', i, l;
 690  
 691              // Split the paths
 692              base = base.substring(0, base.lastIndexOf('/'));
 693              base = base.split('/');
 694              items = path.split('/');
 695  
 696              if (base.length >= items.length) {
 697                  for (i = 0, l = base.length; i < l; i++) {
 698                      if (i >= items.length || base[i] != items[i]) {
 699                          bp = i + 1;
 700                          break;
 701                      }
 702                  }
 703              }
 704  
 705              if (base.length < items.length) {
 706                  for (i = 0, l = items.length; i < l; i++) {
 707                      if (i >= base.length || base[i] != items[i]) {
 708                          bp = i + 1;
 709                          break;
 710                      }
 711                  }
 712              }
 713  
 714              if (bp === 1)
 715                  return path;
 716  
 717              for (i = 0, l = base.length - (bp - 1); i < l; i++)
 718                  out += "../";
 719  
 720              for (i = bp - 1, l = items.length; i < l; i++) {
 721                  if (i != bp - 1)
 722                      out += "/" + items[i];
 723                  else
 724                      out += items[i];
 725              }
 726  
 727              return out;
 728          },
 729  
 730          toAbsPath : function(base, path) {
 731              var i, nb = 0, o = [], tr, outPath;
 732  
 733              // Split paths
 734              tr = /\/$/.test(path) ? '/' : '';
 735              base = base.split('/');
 736              path = path.split('/');
 737  
 738              // Remove empty chunks
 739              each(base, function(k) {
 740                  if (k)
 741                      o.push(k);
 742              });
 743  
 744              base = o;
 745  
 746              // Merge relURLParts chunks
 747              for (i = path.length - 1, o = []; i >= 0; i--) {
 748                  // Ignore empty or .
 749                  if (path[i].length === 0 || path[i] === ".")
 750                      continue;
 751  
 752                  // Is parent
 753                  if (path[i] === '..') {
 754                      nb++;
 755                      continue;
 756                  }
 757  
 758                  // Move up
 759                  if (nb > 0) {
 760                      nb--;
 761                      continue;
 762                  }
 763  
 764                  o.push(path[i]);
 765              }
 766  
 767              i = base.length - nb;
 768  
 769              // If /a/b/c or /
 770              if (i <= 0)
 771                  outPath = o.reverse().join('/');
 772              else
 773                  outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
 774  
 775              // Add front / if it's needed
 776              if (outPath.indexOf('/') !== 0)
 777                  outPath = '/' + outPath;
 778  
 779              // Add traling / if it's needed
 780              if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
 781                  outPath += tr;
 782  
 783              return outPath;
 784          },
 785  
 786          getURI : function(nh) {
 787              var s, t = this;
 788  
 789              // Rebuild source
 790              if (!t.source || nh) {
 791                  s = '';
 792  
 793                  if (!nh) {
 794                      if (t.protocol)
 795                          s += t.protocol + '://';
 796  
 797                      if (t.userInfo)
 798                          s += t.userInfo + '@';
 799  
 800                      if (t.host)
 801                          s += t.host;
 802  
 803                      if (t.port)
 804                          s += ':' + t.port;
 805                  }
 806  
 807                  if (t.path)
 808                      s += t.path;
 809  
 810                  if (t.query)
 811                      s += '?' + t.query;
 812  
 813                  if (t.anchor)
 814                      s += '#' + t.anchor;
 815  
 816                  t.source = s;
 817              }
 818  
 819              return t.source;
 820          }
 821      });
 822  })();
 823  (function() {
 824      var each = tinymce.each;
 825  
 826      tinymce.create('static tinymce.util.Cookie', {
 827          getHash : function(n) {
 828              var v = this.get(n), h;
 829  
 830              if (v) {
 831                  each(v.split('&'), function(v) {
 832                      v = v.split('=');
 833                      h = h || {};
 834                      h[unescape(v[0])] = unescape(v[1]);
 835                  });
 836              }
 837  
 838              return h;
 839          },
 840  
 841          setHash : function(n, v, e, p, d, s) {
 842              var o = '';
 843  
 844              each(v, function(v, k) {
 845                  o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
 846              });
 847  
 848              this.set(n, o, e, p, d, s);
 849          },
 850  
 851          get : function(n) {
 852              var c = document.cookie, e, p = n + "=", b;
 853  
 854              // Strict mode
 855              if (!c)
 856                  return;
 857  
 858              b = c.indexOf("; " + p);
 859  
 860              if (b == -1) {
 861                  b = c.indexOf(p);
 862  
 863                  if (b !== 0)
 864                      return null;
 865              } else
 866                  b += 2;
 867  
 868              e = c.indexOf(";", b);
 869  
 870              if (e == -1)
 871                  e = c.length;
 872  
 873              return unescape(c.substring(b + p.length, e));
 874          },
 875  
 876          set : function(n, v, e, p, d, s) {
 877              document.cookie = n + "=" + escape(v) +
 878                  ((e) ? "; expires=" + e.toGMTString() : "") +
 879                  ((p) ? "; path=" + escape(p) : "") +
 880                  ((d) ? "; domain=" + d : "") +
 881                  ((s) ? "; secure" : "");
 882          },
 883  
 884          remove : function(name, path, domain) {
 885              var date = new Date();
 886  
 887              date.setTime(date.getTime() - 1000);
 888  
 889              this.set(name, '', date, path, domain);
 890          }
 891      });
 892  })();
 893  (function() {
 894  	function serialize(o, quote) {
 895          var i, v, t, name;
 896  
 897          quote = quote || '"';
 898  
 899          if (o == null)
 900              return 'null';
 901  
 902          t = typeof o;
 903  
 904          if (t == 'string') {
 905              v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
 906  
 907              return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
 908                  // Make sure single quotes never get encoded inside double quotes for JSON compatibility
 909                  if (quote === '"' && a === "'")
 910                      return a;
 911  
 912                  i = v.indexOf(b);
 913  
 914                  if (i + 1)
 915                      return '\\' + v.charAt(i + 1);
 916  
 917                  a = b.charCodeAt().toString(16);
 918  
 919                  return '\\u' + '0000'.substring(a.length) + a;
 920              }) + quote;
 921          }
 922  
 923          if (t == 'object') {
 924              if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
 925                      for (i=0, v = '['; i<o.length; i++)
 926                          v += (i > 0 ? ',' : '') + serialize(o[i], quote);
 927  
 928                      return v + ']';
 929                  }
 930  
 931                  v = '{';
 932  
 933                  for (name in o) {
 934                      if (o.hasOwnProperty(name)) {
 935                          v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
 936                      }
 937                  }
 938  
 939                  return v + '}';
 940          }
 941  
 942          return '' + o;
 943      };
 944  
 945      tinymce.util.JSON = {
 946          serialize: serialize,
 947  
 948          parse: function(s) {
 949              try {
 950                  return eval('(' + s + ')');
 951              } catch (ex) {
 952                  // Ignore
 953              }
 954          }
 955  
 956          };
 957  })();
 958  tinymce.create('static tinymce.util.XHR', {
 959      send : function(o) {
 960          var x, t, w = window, c = 0;
 961  
 962  		function ready() {
 963              if (!o.async || x.readyState == 4 || c++ > 10000) {
 964                  if (o.success && c < 10000 && x.status == 200)
 965                      o.success.call(o.success_scope, '' + x.responseText, x, o);
 966                  else if (o.error)
 967                      o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
 968  
 969                  x = null;
 970              } else
 971                  w.setTimeout(ready, 10);
 972          };
 973  
 974          // Default settings
 975          o.scope = o.scope || this;
 976          o.success_scope = o.success_scope || o.scope;
 977          o.error_scope = o.error_scope || o.scope;
 978          o.async = o.async === false ? false : true;
 979          o.data = o.data || '';
 980  
 981  		function get(s) {
 982              x = 0;
 983  
 984              try {
 985                  x = new ActiveXObject(s);
 986              } catch (ex) {
 987              }
 988  
 989              return x;
 990          };
 991  
 992          x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
 993  
 994          if (x) {
 995              if (x.overrideMimeType)
 996                  x.overrideMimeType(o.content_type);
 997  
 998              x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
 999  
1000              if (o.content_type)
1001                  x.setRequestHeader('Content-Type', o.content_type);
1002  
1003              x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1004  
1005              x.send(o.data);
1006  
1007              // Syncronous request
1008              if (!o.async)
1009                  return ready();
1010  
1011              // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1012              t = w.setTimeout(ready, 10);
1013          }
1014      }
1015  });
1016  (function() {
1017      var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1018  
1019      tinymce.create('tinymce.util.JSONRequest', {
1020          JSONRequest : function(s) {
1021              this.settings = extend({
1022              }, s);
1023              this.count = 0;
1024          },
1025  
1026          send : function(o) {
1027              var ecb = o.error, scb = o.success;
1028  
1029              o = extend(this.settings, o);
1030  
1031              o.success = function(c, x) {
1032                  c = JSON.parse(c);
1033  
1034                  if (typeof(c) == 'undefined') {
1035                      c = {
1036                          error : 'JSON Parse error.'
1037                      };
1038                  }
1039  
1040                  if (c.error)
1041                      ecb.call(o.error_scope || o.scope, c.error, x);
1042                  else
1043                      scb.call(o.success_scope || o.scope, c.result);
1044              };
1045  
1046              o.error = function(ty, x) {
1047                  if (ecb)
1048                      ecb.call(o.error_scope || o.scope, ty, x);
1049              };
1050  
1051              o.data = JSON.serialize({
1052                  id : o.id || 'c' + (this.count++),
1053                  method : o.method,
1054                  params : o.params
1055              });
1056  
1057              // JSON content type for Ruby on rails. Bug: #1883287
1058              o.content_type = 'application/json';
1059  
1060              XHR.send(o);
1061          },
1062  
1063          'static' : {
1064              sendRPC : function(o) {
1065                  return new tinymce.util.JSONRequest().send(o);
1066              }
1067          }
1068      });
1069  }());
1070  (function(tinymce){
1071      tinymce.VK = {
1072          BACKSPACE: 8,
1073          DELETE: 46,
1074          DOWN: 40,
1075          ENTER: 13,
1076          LEFT: 37,
1077          RIGHT: 39,
1078          SPACEBAR: 32,
1079          TAB: 9,
1080          UP: 38,
1081  
1082          modifierPressed: function (e) {
1083              return e.shiftKey || e.ctrlKey || e.altKey;
1084          },
1085  
1086          metaKeyPressed: function(e) {
1087              // Check if ctrl or meta key is pressed also check if alt is false for Polish users
1088              return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
1089          }
1090      };
1091  })(tinymce);
1092  tinymce.util.Quirks = function(editor) {
1093      var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
1094          settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each;
1095  
1096  	function setEditorCommandState(cmd, state) {
1097          try {
1098              editor.getDoc().execCommand(cmd, false, state);
1099          } catch (ex) {
1100              // Ignore
1101          }
1102      }
1103  
1104  	function getDocumentMode() {
1105          var documentMode = editor.getDoc().documentMode;
1106  
1107          return documentMode ? documentMode : 6;
1108      };
1109  
1110  	function isDefaultPrevented(e) {
1111          return e.isDefaultPrevented();
1112      };
1113  
1114  	function cleanupStylesWhenDeleting() {
1115  		function removeMergedFormatSpans(isDelete) {
1116              var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
1117  
1118  			function isAtStartOrEndOfElm() {
1119                  if (container.nodeType == 3) {
1120                      if (isDelete && offset == container.length) {
1121                          return true;
1122                      }
1123  
1124                      if (!isDelete && offset === 0) {
1125                          return true;
1126                      }
1127                  }
1128              }
1129  
1130              rng = selection.getRng();
1131              var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
1132  
1133              if (!rng.collapsed) {
1134                  isDelete = true;
1135              }
1136  
1137              container = rng[(isDelete ? 'start' : 'end') + 'Container'];
1138              offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
1139  
1140              if (container.nodeType == 3) {
1141                  blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1142  
1143                  // On delete clone the root span of the next block element
1144                  if (isDelete) {
1145                      blockElm = dom.getNext(blockElm, dom.isBlock);
1146                  }
1147  
1148                  if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
1149                      // Wrap children of block in a EM and let WebKit stick is
1150                      // runtime styles junk into that EM
1151                      wrapperElm = dom.create('em', {'id': '__mceDel'});
1152  
1153                      each(tinymce.grep(blockElm.childNodes), function(node) {
1154                          wrapperElm.appendChild(node);
1155                      });
1156  
1157                      blockElm.appendChild(wrapperElm);
1158                  }
1159              }
1160  
1161              // Do the backspace/delete action
1162              rng = dom.createRng();
1163              rng.setStart(tmpRng[0], tmpRng[1]);
1164              rng.setEnd(tmpRng[2], tmpRng[3]);
1165              selection.setRng(rng);
1166              editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1167  
1168              // Remove temp wrapper element
1169              if (wrapperElm) {
1170                  bookmark = selection.getBookmark();
1171  
1172                  while (elm = dom.get('__mceDel')) {
1173                      dom.remove(elm, true);
1174                  }
1175  
1176                  selection.moveToBookmark(bookmark);
1177              }
1178          }
1179  
1180          editor.onKeyDown.add(function(editor, e) {
1181              var isDelete;
1182  
1183              isDelete = e.keyCode == DELETE;
1184              if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1185                  e.preventDefault();
1186                  removeMergedFormatSpans(isDelete);
1187              }
1188          });
1189  
1190          editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1191      };
1192      
1193  	function emptyEditorWhenDeleting() {
1194  		function serializeRng(rng) {
1195              var body = dom.create("body");
1196              var contents = rng.cloneContents();
1197              body.appendChild(contents);
1198              return selection.serializer.serialize(body, {format: 'html'});
1199          }
1200  
1201  		function allContentsSelected(rng) {
1202              var selection = serializeRng(rng);
1203  
1204              var allRng = dom.createRng();
1205              allRng.selectNode(editor.getBody());
1206  
1207              var allSelection = serializeRng(allRng);
1208              return selection === allSelection;
1209          }
1210  
1211          editor.onKeyDown.add(function(editor, e) {
1212              var keyCode = e.keyCode, isCollapsed;
1213  
1214              // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1215              if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
1216                  isCollapsed = editor.selection.isCollapsed();
1217  
1218                  // Selection is collapsed but the editor isn't empty
1219                  if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1220                      return;
1221                  }
1222  
1223                  // IE deletes all contents correctly when everything is selected
1224                  if (tinymce.isIE && !isCollapsed) {
1225                      return;
1226                  }
1227  
1228                  // Selection isn't collapsed but not all the contents is selected
1229                  if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1230                      return;
1231                  }
1232  
1233                  // Manually empty the editor
1234                  editor.setContent('');
1235                  editor.selection.setCursorLocation(editor.getBody(), 0);
1236                  editor.nodeChanged();
1237              }
1238          });
1239      };
1240  
1241  	function selectAll() {
1242          editor.onKeyDown.add(function(editor, e) {
1243              if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
1244                  e.preventDefault();
1245                  editor.execCommand('SelectAll');
1246              }
1247          });
1248      };
1249  
1250  	function inputMethodFocus() {
1251          if (!editor.settings.content_editable) {
1252              // Case 1 IME doesn't initialize if you focus the document
1253              dom.bind(editor.getDoc(), 'focusin', function(e) {
1254                  selection.setRng(selection.getRng());
1255              });
1256  
1257              // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1258              dom.bind(editor.getDoc(), 'mousedown', function(e) {
1259                  if (e.target == editor.getDoc().documentElement) {
1260                      editor.getWin().focus();
1261                      selection.setRng(selection.getRng());
1262                  }
1263              });
1264          }
1265      };
1266  
1267  	function removeHrOnBackspace() {
1268          editor.onKeyDown.add(function(editor, e) {
1269              if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1270                  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1271                      var node = selection.getNode();
1272                      var previousSibling = node.previousSibling;
1273  
1274                      if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1275                          dom.remove(previousSibling);
1276                          tinymce.dom.Event.cancel(e);
1277                      }
1278                  }
1279              }
1280          })
1281      }
1282  
1283  	function focusBody() {
1284          // Fix for a focus bug in FF 3.x where the body element
1285          // wouldn't get proper focus if the user clicked on the HTML element
1286          if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1287              editor.onMouseDown.add(function(editor, e) {
1288                  if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
1289                      var body = editor.getBody();
1290  
1291                      // Blur the body it's focused but not correctly focused
1292                      body.blur();
1293  
1294                      // Refocus the body after a little while
1295                      setTimeout(function() {
1296                          body.focus();
1297                      }, 0);
1298                  }
1299              });
1300          }
1301      };
1302  
1303  	function selectControlElements() {
1304          editor.onClick.add(function(editor, e) {
1305              e = e.target;
1306  
1307              // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1308              // WebKit can't even do simple things like selecting an image
1309              // Needs tobe the setBaseAndExtend or it will fail to select floated images
1310              if (/^(IMG|HR)$/.test(e.nodeName)) {
1311                  selection.getSel().setBaseAndExtent(e, 0, e, 1);
1312              }
1313  
1314              if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1315                  selection.select(e);
1316              }
1317  
1318              editor.nodeChanged();
1319          });
1320      };
1321  
1322  	function removeStylesWhenDeletingAccrossBlockElements() {
1323  		function getAttributeApplyFunction() {
1324              var template = dom.getAttribs(selection.getStart().cloneNode(false));
1325  
1326              return function() {
1327                  var target = selection.getStart();
1328  
1329                  if (target !== editor.getBody()) {
1330                      dom.setAttrib(target, "style", null);
1331  
1332                      each(template, function(attr) {
1333                          target.setAttributeNode(attr.cloneNode(true));
1334                      });
1335                  }
1336              };
1337          }
1338  
1339  		function isSelectionAcrossElements() {
1340              return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
1341          }
1342  
1343  		function blockEvent(editor, e) {
1344              e.preventDefault();
1345              return false;
1346          }
1347  
1348          editor.onKeyPress.add(function(editor, e) {
1349              var applyAttributes;
1350  
1351              if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1352                  applyAttributes = getAttributeApplyFunction();
1353                  editor.getDoc().execCommand('delete', false, null);
1354                  applyAttributes();
1355                  e.preventDefault();
1356                  return false;
1357              }
1358          });
1359  
1360          dom.bind(editor.getDoc(), 'cut', function(e) {
1361              var applyAttributes;
1362  
1363              if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
1364                  applyAttributes = getAttributeApplyFunction();
1365                  editor.onKeyUp.addToTop(blockEvent);
1366  
1367                  setTimeout(function() {
1368                      applyAttributes();
1369                      editor.onKeyUp.remove(blockEvent);
1370                  }, 0);
1371              }
1372          });
1373      }
1374  
1375  	function selectionChangeNodeChanged() {
1376          var lastRng, selectionTimer;
1377  
1378          dom.bind(editor.getDoc(), 'selectionchange', function() {
1379              if (selectionTimer) {
1380                  clearTimeout(selectionTimer);
1381                  selectionTimer = 0;
1382              }
1383  
1384              selectionTimer = window.setTimeout(function() {
1385                  var rng = selection.getRng();
1386  
1387                  // Compare the ranges to see if it was a real change or not
1388                  if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1389                      editor.nodeChanged();
1390                      lastRng = rng;
1391                  }
1392              }, 50);
1393          });
1394      }
1395  
1396  	function ensureBodyHasRoleApplication() {
1397          document.body.setAttribute("role", "application");
1398      }
1399  
1400  	function disableBackspaceIntoATable() {
1401          editor.onKeyDown.add(function(editor, e) {
1402              if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1403                  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1404                      var previousSibling = selection.getNode().previousSibling;
1405                      if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1406                          return tinymce.dom.Event.cancel(e);
1407                      }
1408                  }
1409              }
1410          })
1411      }
1412  
1413  	function addNewLinesBeforeBrInPre() {
1414          // IE8+ rendering mode does the right thing with BR in PRE
1415          if (getDocumentMode() > 7) {
1416              return;
1417          }
1418  
1419           // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1420           // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1421          setEditorCommandState('RespectVisibilityInDesign', true);
1422          editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1423          dom.addClass(editor.getBody(), 'mceHideBrInPre');
1424  
1425          // Adds a \n before all BR elements in PRE to get them visual
1426          parser.addNodeFilter('pre', function(nodes, name) {
1427              var i = nodes.length, brNodes, j, brElm, sibling;
1428  
1429              while (i--) {
1430                  brNodes = nodes[i].getAll('br');
1431                  j = brNodes.length;
1432                  while (j--) {
1433                      brElm = brNodes[j];
1434  
1435                      // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1436                      sibling = brElm.prev;
1437                      if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1438                          sibling.value += '\n';
1439                      } else {
1440                          brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1441                      }
1442                  }
1443              }
1444          });
1445  
1446          // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1447          serializer.addNodeFilter('pre', function(nodes, name) {
1448              var i = nodes.length, brNodes, j, brElm, sibling;
1449  
1450              while (i--) {
1451                  brNodes = nodes[i].getAll('br');
1452                  j = brNodes.length;
1453                  while (j--) {
1454                      brElm = brNodes[j];
1455                      sibling = brElm.prev;
1456                      if (sibling && sibling.type == 3) {
1457                          sibling.value = sibling.value.replace(/\r?\n$/, '');
1458                      }
1459                  }
1460              }
1461          });
1462      }
1463  
1464      function removePreSerializedStylesWhenSelectingControls() {
1465          dom.bind(editor.getBody(), 'mouseup', function(e) {
1466              var value, node = selection.getNode();
1467  
1468              // Moved styles to attributes on IMG eements
1469              if (node.nodeName == 'IMG') {
1470                  // Convert style width to width attribute
1471                  if (value = dom.getStyle(node, 'width')) {
1472                      dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1473                      dom.setStyle(node, 'width', '');
1474                  }
1475  
1476                  // Convert style height to height attribute
1477                  if (value = dom.getStyle(node, 'height')) {
1478                      dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1479                      dom.setStyle(node, 'height', '');
1480                  }
1481              }
1482          });
1483      }
1484  
1485  	function keepInlineElementOnDeleteBackspace() {
1486          editor.onKeyDown.add(function(editor, e) {
1487              var isDelete, rng, container, offset, brElm, sibling, collapsed;
1488  
1489              isDelete = e.keyCode == DELETE;
1490              if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1491                  rng = selection.getRng();
1492                  container = rng.startContainer;
1493                  offset = rng.startOffset;
1494                  collapsed = rng.collapsed;
1495  
1496                  // Override delete if the start container is a text node and is at the beginning of text or
1497                  // just before/after the last character to be deleted in collapsed mode
1498                  if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1499                      // Edge case when deleting <p><b><img> |x</b></p>
1500                      sibling = container.previousSibling;
1501                      if (sibling && sibling.nodeName == "IMG") {
1502                          return;
1503                      }
1504  
1505                      nonEmptyElements = editor.schema.getNonEmptyElements();
1506  
1507                      // Prevent default logic since it's broken
1508                      e.preventDefault();
1509  
1510                      // Insert a BR before the text node this will prevent the containing element from being deleted/converted
1511                      brElm = dom.create('br', {id: '__tmp'});
1512                      container.parentNode.insertBefore(brElm, container);
1513  
1514                      // Do the browser delete
1515                      editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1516  
1517                      // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1518                      container = selection.getRng().startContainer;
1519                      sibling = container.previousSibling;
1520                      if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1521                          dom.remove(sibling);
1522                      }
1523  
1524                      // Remove the temp element we inserted
1525                      dom.remove('__tmp');
1526                  }
1527              }
1528          });
1529      }
1530  
1531  	function removeBlockQuoteOnBackSpace() {
1532          // Add block quote deletion handler
1533          editor.onKeyDown.add(function(editor, e) {
1534              var rng, container, offset, root, parent;
1535  
1536              if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
1537                  return;
1538              }
1539  
1540              rng = selection.getRng();
1541              container = rng.startContainer;
1542              offset = rng.startOffset;
1543              root = dom.getRoot();
1544              parent = container;
1545  
1546              if (!rng.collapsed || offset !== 0) {
1547                  return;
1548              }
1549  
1550              while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1551                  parent = parent.parentNode;
1552              }
1553  
1554              // Is the cursor at the beginning of a blockquote?
1555              if (parent.tagName === 'BLOCKQUOTE') {
1556                  // Remove the blockquote
1557                  editor.formatter.toggle('blockquote', null, parent);
1558  
1559                  // Move the caret to the beginning of container
1560                  rng = dom.createRng();
1561                  rng.setStart(container, 0);
1562                  rng.setEnd(container, 0);
1563                  selection.setRng(rng);
1564              }
1565          });
1566      };
1567  
1568  	function setGeckoEditingOptions() {
1569  		function setOpts() {
1570              editor._refreshContentEditable();
1571  
1572              setEditorCommandState("StyleWithCSS", false);
1573              setEditorCommandState("enableInlineTableEditing", false);
1574  
1575              if (!settings.object_resizing) {
1576                  setEditorCommandState("enableObjectResizing", false);
1577              }
1578          };
1579  
1580          if (!settings.readonly) {
1581              editor.onBeforeExecCommand.add(setOpts);
1582              editor.onMouseDown.add(setOpts);
1583          }
1584      };
1585  
1586  	function addBrAfterLastLinks() {
1587  		function fixLinks(editor, o) {
1588              each(dom.select('a'), function(node) {
1589                  var parentNode = node.parentNode, root = dom.getRoot();
1590  
1591                  if (parentNode.lastChild === node) {
1592                      while (parentNode && !dom.isBlock(parentNode)) {
1593                          if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1594                              return;
1595                          }
1596  
1597                          parentNode = parentNode.parentNode;
1598                      }
1599  
1600                      dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1601                  }
1602              });
1603          };
1604  
1605          editor.onExecCommand.add(function(editor, cmd) {
1606              if (cmd === 'CreateLink') {
1607                  fixLinks(editor);
1608              }
1609          });
1610  
1611          editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1612      };
1613  
1614  	function setDefaultBlockType() {
1615          if (settings.forced_root_block) {
1616              editor.onInit.add(function() {
1617                  setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1618              });
1619          }
1620      }
1621  
1622  	function removeGhostSelection() {
1623  		function repaint(sender, args) {
1624              if (!sender || !args.initial) {
1625                  editor.execCommand('mceRepaint');
1626              }
1627          };
1628  
1629          editor.onUndo.add(repaint);
1630          editor.onRedo.add(repaint);
1631          editor.onSetContent.add(repaint);
1632      };
1633  
1634  	function deleteControlItemOnBackSpace() {
1635          editor.onKeyDown.add(function(editor, e) {
1636              var rng;
1637  
1638              if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
1639                  rng = editor.getDoc().selection.createRange();
1640                  if (rng && rng.item) {
1641                      e.preventDefault();
1642                      editor.undoManager.beforeChange();
1643                      dom.remove(rng.item(0));
1644                      editor.undoManager.add();
1645                  }
1646              }
1647          });
1648      };
1649  
1650  	function renderEmptyBlocksFix() {
1651          var emptyBlocksCSS;
1652  
1653          // IE10+
1654          if (getDocumentMode() >= 10) {
1655              emptyBlocksCSS = '';
1656              each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1657                  emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1658              });
1659  
1660              editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1661          }
1662      };
1663  
1664  	function fakeImageResize() {
1665          var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
1666              resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
1667  
1668          if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1669              return;
1670          }
1671  
1672          // Try disabling object resizing if WebKit implements resizing in the future
1673          setEditorCommandState("enableObjectResizing", false);
1674  
1675          // Details about each resize handle how to scale etc
1676          resizeHandles = {
1677              // Name: x multiplier, y multiplier, delta size x, delta size y
1678              n: [.5, 0, 0, -1],
1679              e: [1, .5, 1, 0],
1680              s: [.5, 1, 0, 1],
1681              w: [0, .5, -1, 0],
1682              nw: [0, 0, -1, -1],
1683              ne: [1, 0, 1, -1],
1684              se: [1, 1, 1, 1],
1685              sw : [0, 1, -1, 1]
1686          };
1687  
1688  		function resizeElement(e) {
1689              var deltaX, deltaY;
1690  
1691              // Calc new width/height
1692              deltaX = e.screenX - startX;
1693              deltaY = e.screenY - startY;
1694  
1695              // Calc new size
1696              width = deltaX * selectedHandle[2] + startW;
1697              height = deltaY * selectedHandle[3] + startH;
1698  
1699              // Never scale down lower than 5 pixels
1700              width = width < 5 ? 5 : width;
1701              height = height < 5 ? 5 : height;
1702  
1703              // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
1704              if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
1705                  width = Math.round(height / ratio);
1706                  height = Math.round(width * ratio);
1707              }
1708  
1709              // Update ghost size
1710              dom.setStyles(selectedElmGhost, {
1711                  width: width,
1712                  height: height
1713              });
1714  
1715              // Update ghost X position if needed
1716              if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
1717                  dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
1718              }
1719  
1720              // Update ghost Y position if needed
1721              if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
1722                  dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
1723              }
1724          }
1725  
1726  		function endResize() {
1727  			function setSizeProp(name, value) {
1728                  if (value) {
1729                      // Resize by using style or attribute
1730                      if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
1731                          dom.setStyle(selectedElm, name, value);
1732                      } else {
1733                          dom.setAttrib(selectedElm, name, value);
1734                      }
1735                  }
1736              }
1737  
1738              // Set width/height properties
1739              setSizeProp('width', width);
1740              setSizeProp('height', height);
1741  
1742              dom.unbind(editableDoc, 'mousemove', resizeElement);
1743              dom.unbind(editableDoc, 'mouseup', endResize);
1744  
1745              if (rootDocument != editableDoc) {
1746                  dom.unbind(rootDocument, 'mousemove', resizeElement);
1747                  dom.unbind(rootDocument, 'mouseup', endResize);
1748              }
1749  
1750              // Remove ghost and update resize handle positions
1751              dom.remove(selectedElmGhost);
1752              showResizeRect(selectedElm);
1753          }
1754  
1755  		function showResizeRect(targetElm) {
1756              var position, targetWidth, targetHeight;
1757  
1758              hideResizeRect();
1759  
1760              // Get position and size of target
1761              position = dom.getPos(targetElm);
1762              selectedElmX = position.x;
1763              selectedElmY = position.y;
1764              targetWidth = targetElm.offsetWidth;
1765              targetHeight = targetElm.offsetHeight;
1766  
1767              // Reset width/height if user selects a new image/table
1768              if (selectedElm != targetElm) {
1769                  selectedElm = targetElm;
1770                  width = height = 0;
1771              }
1772  
1773              each(resizeHandles, function(handle, name) {
1774                  var handleElm;
1775  
1776                  // Get existing or render resize handle
1777                  handleElm = dom.get('mceResizeHandle' + name);
1778                  if (!handleElm) {
1779                      handleElm = dom.add(editableDoc.documentElement, 'div', {
1780                          id: 'mceResizeHandle' + name,
1781                          'class': 'mceResizeHandle',
1782                          style: 'cursor:' + name + '-resize; margin:0; padding:0'
1783                      });
1784  
1785                      dom.bind(handleElm, 'mousedown', function(e) {
1786                          e.preventDefault();
1787  
1788                          endResize();
1789  
1790                          startX = e.screenX;
1791                          startY = e.screenY;
1792                          startW = selectedElm.clientWidth;
1793                          startH = selectedElm.clientHeight;
1794                          ratio = startH / startW;
1795                          selectedHandle = handle;
1796  
1797                          selectedElmGhost = selectedElm.cloneNode(true);
1798                          dom.addClass(selectedElmGhost, 'mceClonedResizable');
1799                          dom.setStyles(selectedElmGhost, {
1800                              left: selectedElmX,
1801                              top: selectedElmY,
1802                              margin: 0
1803                          });
1804  
1805                          editableDoc.documentElement.appendChild(selectedElmGhost);
1806  
1807                          dom.bind(editableDoc, 'mousemove', resizeElement);
1808                          dom.bind(editableDoc, 'mouseup', endResize);
1809  
1810                          if (rootDocument != editableDoc) {
1811                              dom.bind(rootDocument, 'mousemove', resizeElement);
1812                              dom.bind(rootDocument, 'mouseup', endResize);
1813                          }
1814                      });
1815                  } else {
1816                      dom.show(handleElm);
1817                  }
1818  
1819                  // Position element
1820                  dom.setStyles(handleElm, {
1821                      left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
1822                      top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
1823                  });
1824              });
1825  
1826              // Only add resize rectangle on WebKit and only on images
1827              if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
1828                  selectedElm.setAttribute('data-mce-selected', '1');
1829              }
1830          }
1831  
1832  		function hideResizeRect() {
1833              if (selectedElm) {
1834                  selectedElm.removeAttribute('data-mce-selected');
1835              }
1836  
1837              for (var name in resizeHandles) {
1838                  dom.hide('mceResizeHandle' + name);
1839              }
1840          }
1841  
1842          // Add CSS for resize handles, cloned element and selected
1843          editor.contentStyles.push(
1844              '.mceResizeHandle {' +
1845                  'position: absolute;' +
1846                  'border: 1px solid black;' +
1847                  'background: #FFF;' +
1848                  'width: 5px;' +
1849                  'height: 5px;' +
1850                  'z-index: 10000' +
1851              '}' +
1852              '.mceResizeHandle:hover {' +
1853                  'background: #000' +
1854              '}' +
1855              'img[data-mce-selected] {' +
1856                  'outline: 1px solid black' +
1857              '}' +
1858              'img.mceClonedResizable, table.mceClonedResizable {' +
1859                  'position: absolute;' +
1860                  'outline: 1px dashed black;' +
1861                  'opacity: .5;' +
1862                  'z-index: 10000' +
1863              '}'
1864          );
1865  
1866  		function updateResizeRect() {
1867              var controlElm = dom.getParent(selection.getNode(), 'table,img');
1868  
1869              // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
1870              each(dom.select('img[data-mce-selected]'), function(img) {
1871                  img.removeAttribute('data-mce-selected');
1872              });
1873  
1874              if (controlElm) {
1875                  showResizeRect(controlElm);
1876              } else {
1877                  hideResizeRect();
1878              }
1879          }
1880  
1881          // Show/hide resize rect when image is selected
1882          editor.onNodeChange.add(updateResizeRect);
1883  
1884          // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
1885          dom.bind(editableDoc, 'selectionchange', updateResizeRect);
1886  
1887          // Remove the internal attribute when serializing the DOM
1888          editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
1889              var i = nodes.length;
1890  
1891              while (i--) {
1892                  nodes[i].attr(name, null);
1893              }
1894          });
1895      }
1896  
1897  	function keepNoScriptContents() {
1898          if (getDocumentMode() < 9) {
1899              parser.addNodeFilter('noscript', function(nodes) {
1900                  var i = nodes.length, node, textNode;
1901  
1902                  while (i--) {
1903                      node = nodes[i];
1904                      textNode = node.firstChild;
1905  
1906                      if (textNode) {
1907                          node.attr('data-mce-innertext', textNode.value);
1908                      }
1909                  }
1910              });
1911  
1912              serializer.addNodeFilter('noscript', function(nodes) {
1913                  var i = nodes.length, node, textNode, value;
1914  
1915                  while (i--) {
1916                      node = nodes[i];
1917                      textNode = nodes[i].firstChild;
1918  
1919                      if (textNode) {
1920                          textNode.value = tinymce.html.Entities.decode(textNode.value);
1921                      } else {
1922                          // Old IE can't retain noscript value so an attribute is used to store it
1923                          value = node.attributes.map['data-mce-innertext'];
1924                          if (value) {
1925                              node.attr('data-mce-innertext', null);
1926                              textNode = new tinymce.html.Node('#text', 3);
1927                              textNode.value = value;
1928                              textNode.raw = true;
1929                              node.append(textNode);
1930                          }
1931                      }
1932                  }
1933              });
1934          }
1935      }
1936  
1937  	function bodyHeight() {
1938          editor.contentStyles.push('body {min-height: 100px}');
1939          editor.onClick.add(function(ed, e) {
1940              if (e.target.nodeName == 'HTML') {
1941                  editor.execCommand('SelectAll');
1942                  editor.selection.collapse(true);
1943                  editor.nodeChanged();
1944              }
1945          });
1946      }
1947  
1948      // All browsers
1949      disableBackspaceIntoATable();
1950      removeBlockQuoteOnBackSpace();
1951      emptyEditorWhenDeleting();
1952  
1953      // WebKit
1954      if (tinymce.isWebKit) {
1955          keepInlineElementOnDeleteBackspace();
1956          cleanupStylesWhenDeleting();
1957          inputMethodFocus();
1958          selectControlElements();
1959          setDefaultBlockType();
1960  
1961          // iOS
1962          if (tinymce.isIDevice) {
1963              selectionChangeNodeChanged();
1964          } else {
1965              fakeImageResize();
1966              selectAll();
1967          }
1968      }
1969  
1970      // IE
1971      if (tinymce.isIE && !tinymce.isIE11) {
1972          removeHrOnBackspace();
1973          ensureBodyHasRoleApplication();
1974          addNewLinesBeforeBrInPre();
1975          removePreSerializedStylesWhenSelectingControls();
1976          deleteControlItemOnBackSpace();
1977          renderEmptyBlocksFix();
1978          keepNoScriptContents();
1979      }
1980  
1981      // IE 11+
1982      if (tinymce.isIE11) {
1983          bodyHeight();
1984      }
1985  
1986      // Gecko
1987      if (tinymce.isGecko && !tinymce.isIE11) {
1988          removeHrOnBackspace();
1989          focusBody();
1990          removeStylesWhenDeletingAccrossBlockElements();
1991          setGeckoEditingOptions();
1992          addBrAfterLastLinks();
1993          removeGhostSelection();
1994      }
1995  
1996      // Opera
1997      if (tinymce.isOpera) {
1998          fakeImageResize();
1999      }
2000  };
2001  (function(tinymce) {
2002      var namedEntities, baseEntities, reverseEntities,
2003          attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2004          textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2005          rawCharsRegExp = /[<>&\"\']/g,
2006          entityRegExp = /&(#x|#)?([\w]+);/g,
2007          asciiMap = {
2008                  128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2009                  135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2010                  142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2011                  150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2012                  156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2013          };
2014  
2015      // Raw entities
2016      baseEntities = {
2017          '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
2018          "'" : '&#39;',
2019          '<' : '&lt;',
2020          '>' : '&gt;',
2021          '&' : '&amp;'
2022      };
2023  
2024      // Reverse lookup table for raw entities
2025      reverseEntities = {
2026          '&lt;' : '<',
2027          '&gt;' : '>',
2028          '&amp;' : '&',
2029          '&quot;' : '"',
2030          '&apos;' : "'"
2031      };
2032  
2033      // Decodes text by using the browser
2034  	function nativeDecode(text) {
2035          var elm;
2036  
2037          elm = document.createElement("div");
2038          elm.innerHTML = text;
2039  
2040          return elm.textContent || elm.innerText || text;
2041      };
2042  
2043      // Build a two way lookup table for the entities
2044  	function buildEntitiesLookup(items, radix) {
2045          var i, chr, entity, lookup = {};
2046  
2047          if (items) {
2048              items = items.split(',');
2049              radix = radix || 10;
2050  
2051              // Build entities lookup table
2052              for (i = 0; i < items.length; i += 2) {
2053                  chr = String.fromCharCode(parseInt(items[i], radix));
2054  
2055                  // Only add non base entities
2056                  if (!baseEntities[chr]) {
2057                      entity = '&' + items[i + 1] + ';';
2058                      lookup[chr] = entity;
2059                      lookup[entity] = chr;
2060                  }
2061              }
2062  
2063              return lookup;
2064          }
2065      };
2066  
2067      // Unpack entities lookup where the numbers are in radix 32 to reduce the size
2068      namedEntities = buildEntitiesLookup(
2069          '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2070          '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2071          '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2072          '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2073          '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2074          '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2075          '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2076          '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2077          '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2078          '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2079          'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2080          'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2081          't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2082          'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2083          'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2084          '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2085          '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2086          '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2087          '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2088          '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2089          'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2090          'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2091          'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2092          '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2093          '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2094  
2095      tinymce.html = tinymce.html || {};
2096  
2097      tinymce.html.Entities = {
2098          encodeRaw : function(text, attr) {
2099              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2100                  return baseEntities[chr] || chr;
2101              });
2102          },
2103  
2104          encodeAllRaw : function(text) {
2105              return ('' + text).replace(rawCharsRegExp, function(chr) {
2106                  return baseEntities[chr] || chr;
2107              });
2108          },
2109  
2110          encodeNumeric : function(text, attr) {
2111              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2112                  // Multi byte sequence convert it to a single entity
2113                  if (chr.length > 1)
2114                      return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2115  
2116                  return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2117              });
2118          },
2119  
2120          encodeNamed : function(text, attr, entities) {
2121              entities = entities || namedEntities;
2122  
2123              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2124                  return baseEntities[chr] || entities[chr] || chr;
2125              });
2126          },
2127  
2128          getEncodeFunc : function(name, entities) {
2129              var Entities = tinymce.html.Entities;
2130  
2131              entities = buildEntitiesLookup(entities) || namedEntities;
2132  
2133  			function encodeNamedAndNumeric(text, attr) {
2134                  return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2135                      return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2136                  });
2137              };
2138  
2139  			function encodeCustomNamed(text, attr) {
2140                  return Entities.encodeNamed(text, attr, entities);
2141              };
2142  
2143              // Replace + with , to be compatible with previous TinyMCE versions
2144              name = tinymce.makeMap(name.replace(/\+/g, ','));
2145  
2146              // Named and numeric encoder
2147              if (name.named && name.numeric)
2148                  return encodeNamedAndNumeric;
2149  
2150              // Named encoder
2151              if (name.named) {
2152                  // Custom names
2153                  if (entities)
2154                      return encodeCustomNamed;
2155  
2156                  return Entities.encodeNamed;
2157              }
2158  
2159              // Numeric
2160              if (name.numeric)
2161                  return Entities.encodeNumeric;
2162  
2163              // Raw encoder
2164              return Entities.encodeRaw;
2165          },
2166  
2167          decode : function(text) {
2168              return text.replace(entityRegExp, function(all, numeric, value) {
2169                  if (numeric) {
2170                      value = parseInt(value, numeric.length === 2 ? 16 : 10);
2171  
2172                      // Support upper UTF
2173                      if (value > 0xFFFF) {
2174                          value -= 0x10000;
2175  
2176                          return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2177                      } else
2178                          return asciiMap[value] || String.fromCharCode(value);
2179                  }
2180  
2181                  return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2182              });
2183          }
2184      };
2185  })(tinymce);
2186  tinymce.html.Styles = function(settings, schema) {
2187      var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2188          urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2189          styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2190          trimRightRegExp = /\s+$/,
2191          urlColorRegExp = /rgb/,
2192          undef, i, encodingLookup = {}, encodingItems;
2193  
2194      settings = settings || {};
2195  
2196      encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2197      for (i = 0; i < encodingItems.length; i++) {
2198          encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2199          encodingLookup['\uFEFF' + i] = encodingItems[i];
2200      }
2201  
2202  	function toHex(match, r, g, b) {
2203  		function hex(val) {
2204              val = parseInt(val).toString(16);
2205  
2206              return val.length > 1 ? val : '0' + val; // 0 -> 00
2207          };
2208  
2209          return '#' + hex(r) + hex(g) + hex(b);
2210      };
2211  
2212      return {
2213          toHex : function(color) {
2214              return color.replace(rgbRegExp, toHex);
2215          },
2216  
2217          parse : function(css) {
2218              var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2219  
2220  			function compress(prefix, suffix) {
2221                  var top, right, bottom, left;
2222  
2223                  // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
2224                  // So lets asume it shouldn't be there
2225                  if (styles['border-image'] === 'none') {
2226                      delete styles['border-image'];
2227                  }
2228  
2229                  // Get values and check it it needs compressing
2230                  top = styles[prefix + '-top' + suffix];
2231                  if (!top)
2232                      return;
2233  
2234                  right = styles[prefix + '-right' + suffix];
2235                  if (top != right)
2236                      return;
2237  
2238                  bottom = styles[prefix + '-bottom' + suffix];
2239                  if (right != bottom)
2240                      return;
2241  
2242                  left = styles[prefix + '-left' + suffix];
2243                  if (bottom != left)
2244                      return;
2245  
2246                  // Compress
2247                  styles[prefix + suffix] = left;
2248                  delete styles[prefix + '-top' + suffix];
2249                  delete styles[prefix + '-right' + suffix];
2250                  delete styles[prefix + '-bottom' + suffix];
2251                  delete styles[prefix + '-left' + suffix];
2252              };
2253  
2254  			function canCompress(key) {
2255                  var value = styles[key], i;
2256  
2257                  if (!value || value.indexOf(' ') < 0)
2258                      return;
2259  
2260                  value = value.split(' ');
2261                  i = value.length;
2262                  while (i--) {
2263                      if (value[i] !== value[0])
2264                          return false;
2265                  }
2266  
2267                  styles[key] = value[0];
2268  
2269                  return true;
2270              };
2271  
2272  			function compress2(target, a, b, c) {
2273                  if (!canCompress(a))
2274                      return;
2275  
2276                  if (!canCompress(b))
2277                      return;
2278  
2279                  if (!canCompress(c))
2280                      return;
2281  
2282                  // Compress
2283                  styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2284                  delete styles[a];
2285                  delete styles[b];
2286                  delete styles[c];
2287              };
2288  
2289              // Encodes the specified string by replacing all \" \' ; : with _<num>
2290  			function encode(str) {
2291                  isEncoded = true;
2292  
2293                  return encodingLookup[str];
2294              };
2295  
2296              // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2297              // It will also decode the \" \' if keep_slashes is set to fale or omitted
2298  			function decode(str, keep_slashes) {
2299                  if (isEncoded) {
2300                      str = str.replace(/\uFEFF[0-9]/g, function(str) {
2301                          return encodingLookup[str];
2302                      });
2303                  }
2304  
2305                  if (!keep_slashes)
2306                      str = str.replace(/\\([\'\";:])/g, "$1");
2307  
2308                  return str;
2309              };
2310  
2311  			function processUrl(match, url, url2, url3, str, str2) {
2312                  str = str || str2;
2313  
2314                  if (str) {
2315                      str = decode(str);
2316  
2317                      // Force strings into single quote format
2318                      return "'" + str.replace(/\'/g, "\\'") + "'";
2319                  }
2320  
2321                  url = decode(url || url2 || url3);
2322  
2323                  // Convert the URL to relative/absolute depending on config
2324                  if (urlConverter)
2325                      url = urlConverter.call(urlConverterScope, url, 'style');
2326  
2327                  // Output new URL format
2328                  return "url('" + url.replace(/\'/g, "\\'") + "')";
2329              };
2330  
2331              if (css) {
2332                  // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2333                  css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2334                      return str.replace(/[;:]/g, encode);
2335                  });
2336  
2337                  // Parse styles
2338                  while (matches = styleRegExp.exec(css)) {
2339                      name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2340                      value = matches[2].replace(trimRightRegExp, '');
2341  
2342                      if (name && value.length > 0) {
2343                          // Opera will produce 700 instead of bold in their style values
2344                          if (name === 'font-weight' && value === '700')
2345                              value = 'bold';
2346                          else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2347                              value = value.toLowerCase();        
2348  
2349                          // Convert RGB colors to HEX
2350                          value = value.replace(rgbRegExp, toHex);
2351  
2352                          // Convert URLs and force them into url('value') format
2353                          value = value.replace(urlOrStrRegExp, processUrl);
2354                          styles[name] = isEncoded ? decode(value, true) : value;
2355                      }
2356  
2357                      styleRegExp.lastIndex = matches.index + matches[0].length;
2358                  }
2359  
2360                  // Compress the styles to reduce it's size for example IE will expand styles
2361                  compress("border", "");
2362                  compress("border", "-width");
2363                  compress("border", "-color");
2364                  compress("border", "-style");
2365                  compress("padding", "");
2366                  compress("margin", "");
2367                  compress2('border', 'border-width', 'border-style', 'border-color');
2368  
2369                  // Remove pointless border, IE produces these
2370                  if (styles.border === 'medium none')
2371                      delete styles.border;
2372              }
2373  
2374              return styles;
2375          },
2376  
2377          serialize : function(styles, element_name) {
2378              var css = '', name, value;
2379  
2380  			function serializeStyles(name) {
2381                  var styleList, i, l, value;
2382  
2383                  styleList = schema.styles[name];
2384                  if (styleList) {
2385                      for (i = 0, l = styleList.length; i < l; i++) {
2386                          name = styleList[i];
2387                          value = styles[name];
2388  
2389                          if (value !== undef && value.length > 0)
2390                              css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2391                      }
2392                  }
2393              };
2394  
2395              // Serialize styles according to schema
2396              if (element_name && schema && schema.styles) {
2397                  // Serialize global styles and element specific styles
2398                  serializeStyles('*');
2399                  serializeStyles(element_name);
2400              } else {
2401                  // Output the styles in the order they are inside the object
2402                  for (name in styles) {
2403                      value = styles[name];
2404  
2405                      if (value !== undef && value.length > 0)
2406                          css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2407                  }
2408              }
2409  
2410              return css;
2411          }
2412      };
2413  };
2414  (function(tinymce) {
2415      var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2416  
2417  	function split(str, delim) {
2418          return str.split(delim || ',');
2419      };
2420  
2421  	function unpack(lookup, data) {
2422          var key, elements = {};
2423  
2424  		function replace(value) {
2425              return value.replace(/[A-Z]+/g, function(key) {
2426                  return replace(lookup[key]);
2427              });
2428          };
2429  
2430          // Unpack lookup
2431          for (key in lookup) {
2432              if (lookup.hasOwnProperty(key))
2433                  lookup[key] = replace(lookup[key]);
2434          }
2435  
2436          // Unpack and parse data into object map
2437          replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2438              attributes = split(attributes, '|');
2439  
2440              elements[name] = {
2441                  attributes : makeMap(attributes),
2442                  attributesOrder : attributes,
2443                  children : makeMap(children, '|', {'#comment' : {}})
2444              }
2445          });
2446  
2447          return elements;
2448      };
2449  
2450  	function getHTML5() {
2451          var html5 = mapCache.html5;
2452  
2453          if (!html5) {
2454              html5 = mapCache.html5 = unpack({
2455                      A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2456                      B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2457                          'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2458                      C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2459                          'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2460                          'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2461                  }, 'html[A|manifest][body|head]' +
2462                      'head[A][base|command|link|meta|noscript|script|style|title]' +
2463                      'title[A][#]' +
2464                      'base[A|href|target][]' +
2465                      'link[A|href|rel|media|type|sizes][]' +
2466                      'meta[A|http-equiv|name|content|charset][]' +
2467                      'style[A|type|media|scoped][#]' +
2468                      'script[A|charset|type|src|defer|async][#]' +
2469                      'noscript[A][C]' +
2470                      'body[A][C]' +
2471                      'section[A][C]' +
2472                      'nav[A][C]' +
2473                      'article[A][C]' +
2474                      'aside[A][C]' +
2475                      'h1[A][B]' +
2476                      'h2[A][B]' +
2477                      'h3[A][B]' +
2478                      'h4[A][B]' +
2479                      'h5[A][B]' +
2480                      'h6[A][B]' +
2481                      'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2482                      'header[A][C]' +
2483                      'footer[A][C]' +
2484                      'address[A][C]' +
2485                      'p[A][B]' +
2486                      'br[A][]' +
2487                      'pre[A][B]' +
2488                      'dialog[A][dd|dt]' +
2489                      'blockquote[A|cite][C]' +
2490                      'ol[A|start|reversed][li]' +
2491                      'ul[A][li]' +
2492                      'li[A|value][C]' +
2493                      'dl[A][dd|dt]' +
2494                      'dt[A][B]' +
2495                      'dd[A][C]' +
2496                      'a[A|href|target|ping|rel|media|type][B]' +
2497                      'em[A][B]' +
2498                      'strong[A][B]' +
2499                      'small[A][B]' +
2500                      'cite[A][B]' +
2501                      'q[A|cite][B]' +
2502                      'dfn[A][B]' +
2503                      'abbr[A][B]' +
2504                      'code[A][B]' +
2505                      'var[A][B]' +
2506                      'samp[A][B]' +
2507                      'kbd[A][B]' +
2508                      'sub[A][B]' +
2509                      'sup[A][B]' +
2510                      'i[A][B]' +
2511                      'b[A][B]' +
2512                      'mark[A][B]' +
2513                      'progress[A|value|max][B]' +
2514                      'meter[A|value|min|max|low|high|optimum][B]' +
2515                      'time[A|datetime][B]' +
2516                      'ruby[A][B|rt|rp]' +
2517                      'rt[A][B]' +
2518                      'rp[A][B]' +
2519                      'bdo[A][B]' +
2520                      'span[A][B]' +
2521                      'ins[A|cite|datetime][B]' +
2522                      'del[A|cite|datetime][B]' +
2523                      'figure[A][C|legend|figcaption]' +
2524                      'figcaption[A][C]' +
2525                      'img[A|alt|src|height|width|usemap|ismap][]' +
2526                      'iframe[A|name|src|height|width|sandbox|seamless][]' +
2527                      'embed[A|src|height|width|type][]' +
2528                      'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2529                      'param[A|name|value][]' +
2530                      'details[A|open][C|legend]' +
2531                      'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2532                      'menu[A|type|label][C|li]' +
2533                      'legend[A][C|B]' +
2534                      'div[A][C]' +
2535                      'source[A|src|type|media][]' +
2536                      'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2537                      'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2538                      'hr[A][]' +
2539                      'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2540                      'fieldset[A|disabled|form|name][C|legend]' +
2541                      'label[A|form|for][B]' +
2542                      'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2543                          'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2544                      'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2545                      'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2546                      'datalist[A][B|option]' +
2547                      'optgroup[A|disabled|label][option]' +
2548                      'option[A|disabled|selected|label|value][]' +
2549                      'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2550                      'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2551                      'output[A|for|form|name][B]' +
2552                      'canvas[A|width|height][]' +
2553                      'map[A|name][B|C]' +
2554                      'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2555                      'mathml[A][]' +
2556                      'svg[A][]' +
2557                      'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2558                      'caption[A][C]' +
2559                      'colgroup[A|span][col]' +
2560                      'col[A|span][]' +
2561                      'thead[A][tr]' +
2562                      'tfoot[A][tr]' +
2563                      'tbody[A][tr]' +
2564                      'tr[A][th|td]' +
2565                      'th[A|headers|rowspan|colspan|scope][B]' +
2566                      'td[A|headers|rowspan|colspan][C]' +
2567                      'wbr[A][]'
2568              );
2569          }
2570  
2571          return html5;
2572      };
2573  
2574  	function getHTML4() {
2575          var html4 = mapCache.html4;
2576  
2577          if (!html4) {
2578              // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2579              html4 = mapCache.html4 = unpack({
2580                  Z : 'H|K|N|O|P',
2581                  Y : 'X|form|R|Q',
2582                  ZG : 'E|span|width|align|char|charoff|valign',
2583                  X : 'p|T|div|U|W|isindex|fieldset|table',
2584                  ZF : 'E|align|char|charoff|valign',
2585                  W : 'pre|hr|blockquote|address|center|noframes',
2586                  ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2587                  ZD : '[E][S]',
2588                  U : 'ul|ol|dl|menu|dir',
2589                  ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2590                  T : 'h1|h2|h3|h4|h5|h6',
2591                  ZB : 'X|S|Q',
2592                  S : 'R|P',
2593                  ZA : 'a|G|J|M|O|P',
2594                  R : 'a|H|K|N|O',
2595                  Q : 'noscript|P',
2596                  P : 'ins|del|script',
2597                  O : 'input|select|textarea|label|button',
2598                  N : 'M|L',
2599                  M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2600                  L : 'sub|sup',
2601                  K : 'J|I',
2602                  J : 'tt|i|b|u|s|strike',
2603                  I : 'big|small|font|basefont',
2604                  H : 'G|F',
2605                  G : 'br|span|bdo',
2606                  F : 'object|applet|img|map|iframe',
2607                  E : 'A|B|C',
2608                  D : 'accesskey|tabindex|onfocus|onblur',
2609                  C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2610                  B : 'lang|xml:lang|dir',
2611                  A : 'id|class|style|title'
2612              }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2613                  'style[B|id|type|media|title|xml:space][]' + 
2614                  'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2615                  'param[id|name|value|valuetype|type][]' + 
2616                  'p[E|align][#|S]' + 
2617                  'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2618                  'br[A|clear][]' + 
2619                  'span[E][#|S]' + 
2620                  'bdo[A|C|B][#|S]' + 
2621                  'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2622                  'h1[E|align][#|S]' + 
2623                  'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2624                  'map[B|C|A|name][X|form|Q|area]' + 
2625                  'h2[E|align][#|S]' + 
2626                  'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2627                  'h3[E|align][#|S]' + 
2628                  'tt[E][#|S]' + 
2629                  'i[E][#|S]' + 
2630                  'b[E][#|S]' + 
2631                  'u[E][#|S]' + 
2632                  's[E][#|S]' + 
2633                  'strike[E][#|S]' + 
2634                  'big[E][#|S]' + 
2635                  'small[E][#|S]' + 
2636                  'font[A|B|size|color|face][#|S]' + 
2637                  'basefont[id|size|color|face][]' + 
2638                  'em[E][#|S]' + 
2639                  'strong[E][#|S]' + 
2640                  'dfn[E][#|S]' + 
2641                  'code[E][#|S]' + 
2642                  'q[E|cite][#|S]' + 
2643                  'samp[E][#|S]' + 
2644                  'kbd[E][#|S]' + 
2645                  'var[E][#|S]' + 
2646                  'cite[E][#|S]' + 
2647                  'abbr[E][#|S]' + 
2648                  'acronym[E][#|S]' + 
2649                  'sub[E][#|S]' + 
2650                  'sup[E][#|S]' + 
2651                  'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2652                  'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2653                  'optgroup[E|disabled|label][option]' + 
2654                  'option[E|selected|disabled|label|value][]' + 
2655                  'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2656                  'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2657                  'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2658                  'h4[E|align][#|S]' + 
2659                  'ins[E|cite|datetime][#|Y]' + 
2660                  'h5[E|align][#|S]' + 
2661                  'del[E|cite|datetime][#|Y]' + 
2662                  'h6[E|align][#|S]' + 
2663                  'div[E|align][#|Y]' + 
2664                  'ul[E|type|compact][li]' + 
2665                  'li[E|type|value][#|Y]' + 
2666                  'ol[E|type|compact|start][li]' + 
2667                  'dl[E|compact][dt|dd]' + 
2668                  'dt[E][#|S]' + 
2669                  'dd[E][#|Y]' + 
2670                  'menu[E|compact][li]' + 
2671                  'dir[E|compact][li]' + 
2672                  'pre[E|width|xml:space][#|ZA]' + 
2673                  'hr[E|align|noshade|size|width][]' + 
2674                  'blockquote[E|cite][#|Y]' + 
2675                  'address[E][#|S|p]' + 
2676                  'center[E][#|Y]' + 
2677                  'noframes[E][#|Y]' + 
2678                  'isindex[A|B|prompt][]' + 
2679                  'fieldset[E][#|legend|Y]' + 
2680                  'legend[E|accesskey|align][#|S]' + 
2681                  'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2682                  'caption[E|align][#|S]' + 
2683                  'col[ZG][]' + 
2684                  'colgroup[ZG][col]' + 
2685                  'thead[ZF][tr]' + 
2686                  'tr[ZF|bgcolor][th|td]' + 
2687                  'th[E|ZE][#|Y]' + 
2688                  'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2689                  'noscript[E][#|Y]' + 
2690                  'td[E|ZE][#|Y]' + 
2691                  'tfoot[ZF][tr]' + 
2692                  'tbody[ZF][tr]' + 
2693                  'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2694                  'base[id|href|target][]' + 
2695                  'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2696              );
2697          }
2698  
2699          return html4;
2700      };
2701  
2702      tinymce.html.Schema = function(settings) {
2703          var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2704          var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2705  
2706          // Creates an lookup table map object for the specified option or the default value
2707  		function createLookupTable(option, default_value, extend) {
2708              var value = settings[option];
2709  
2710              if (!value) {
2711                  // Get cached default map or make it if needed
2712                  value = mapCache[option];
2713  
2714                  if (!value) {
2715                      value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2716                      value = tinymce.extend(value, extend);
2717  
2718                      mapCache[option] = value;
2719                  }
2720              } else {
2721                  // Create custom map
2722                  value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2723              }
2724  
2725              return value;
2726          };
2727  
2728          settings = settings || {};
2729          schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2730  
2731          // Allow all elements and attributes if verify_html is set to false
2732          if (settings.verify_html === false)
2733              settings.valid_elements = '*[*]';
2734  
2735          // Build styles list
2736          if (settings.valid_styles) {
2737              validStyles = {};
2738  
2739              // Convert styles into a rule list
2740              each(settings.valid_styles, function(value, key) {
2741                  validStyles[key] = tinymce.explode(value);
2742              });
2743          }
2744  
2745          // Setup map objects
2746          whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea');
2747          selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2748          shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2749          boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2750          nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
2751          textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 
2752                          'blockquote center dir fieldset header footer article section hgroup aside nav figure');
2753          blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 
2754                          'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap);
2755  
2756          // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2757  		function patternToRegExp(str) {
2758              return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2759          };
2760  
2761          // Parses the specified valid_elements string and adds to the current rules
2762          // This function is a bit hard to read since it's heavily optimized for speed
2763  		function addValidElements(valid_elements) {
2764              var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2765                  prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2766                  elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2767                  attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2768                  hasPatternsRegExp = /[*?+]/;
2769  
2770              if (valid_elements) {
2771                  // Split valid elements into an array with rules
2772                  valid_elements = split(valid_elements);
2773  
2774                  if (elements['@']) {
2775                      globalAttributes = elements['@'].attributes;
2776                      globalAttributesOrder = elements['@'].attributesOrder;
2777                  }
2778  
2779                  // Loop all rules
2780                  for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2781                      // Parse element rule
2782                      matches = elementRuleRegExp.exec(valid_elements[ei]);
2783                      if (matches) {
2784                          // Setup local names for matches
2785                          prefix = matches[1];
2786                          elementName = matches[2];
2787                          outputName = matches[3];
2788                          attrData = matches[4];
2789  
2790                          // Create new attributes and attributesOrder
2791                          attributes = {};
2792                          attributesOrder = [];
2793  
2794                          // Create the new element
2795                          element = {
2796                              attributes : attributes,
2797                              attributesOrder : attributesOrder
2798                          };
2799  
2800                          // Padd empty elements prefix
2801                          if (prefix === '#')
2802                              element.paddEmpty = true;
2803  
2804                          // Remove empty elements prefix
2805                          if (prefix === '-')
2806                              element.removeEmpty = true;
2807  
2808                          // Copy attributes from global rule into current rule
2809                          if (globalAttributes) {
2810                              for (key in globalAttributes)
2811                                  attributes[key] = globalAttributes[key];
2812  
2813                              attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2814                          }
2815  
2816                          // Attributes defined
2817                          if (attrData) {
2818                              attrData = split(attrData, '|');
2819                              for (ai = 0, al = attrData.length; ai < al; ai++) {
2820                                  matches = attrRuleRegExp.exec(attrData[ai]);
2821                                  if (matches) {
2822                                      attr = {};
2823                                      attrType = matches[1];
2824                                      attrName = matches[2].replace(/::/g, ':');
2825                                      prefix = matches[3];
2826                                      value = matches[4];
2827  
2828                                      // Required
2829                                      if (attrType === '!') {
2830                                          element.attributesRequired = element.attributesRequired || [];
2831                                          element.attributesRequired.push(attrName);
2832                                          attr.required = true;
2833                                      }
2834  
2835                                      // Denied from global
2836                                      if (attrType === '-') {
2837                                          delete attributes[attrName];
2838                                          attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2839                                          continue;
2840                                      }
2841  
2842                                      // Default value
2843                                      if (prefix) {
2844                                          // Default value
2845                                          if (prefix === '=') {
2846                                              element.attributesDefault = element.attributesDefault || [];
2847                                              element.attributesDefault.push({name: attrName, value: value});
2848                                              attr.defaultValue = value;
2849                                          }
2850  
2851                                          // Forced value
2852                                          if (prefix === ':') {
2853                                              element.attributesForced = element.attributesForced || [];
2854                                              element.attributesForced.push({name: attrName, value: value});
2855                                              attr.forcedValue = value;
2856                                          }
2857  
2858                                          // Required values
2859                                          if (prefix === '<')
2860                                              attr.validValues = makeMap(value, '?');
2861                                      }
2862  
2863                                      // Check for attribute patterns
2864                                      if (hasPatternsRegExp.test(attrName)) {
2865                                          element.attributePatterns = element.attributePatterns || [];
2866                                          attr.pattern = patternToRegExp(attrName);
2867                                          element.attributePatterns.push(attr);
2868                                      } else {
2869                                          // Add attribute to order list if it doesn't already exist
2870                                          if (!attributes[attrName])
2871                                              attributesOrder.push(attrName);
2872  
2873                                          attributes[attrName] = attr;
2874                                      }
2875                                  }
2876                              }
2877                          }
2878  
2879                          // Global rule, store away these for later usage
2880                          if (!globalAttributes && elementName == '@') {
2881                              globalAttributes = attributes;
2882                              globalAttributesOrder = attributesOrder;
2883                          }
2884  
2885                          // Handle substitute elements such as b/strong
2886                          if (outputName) {
2887                              element.outputName = elementName;
2888                              elements[outputName] = element;
2889                          }
2890  
2891                          // Add pattern or exact element
2892                          if (hasPatternsRegExp.test(elementName)) {
2893                              element.pattern = patternToRegExp(elementName);
2894                              patternElements.push(element);
2895                          } else
2896                              elements[elementName] = element;
2897                      }
2898                  }
2899              }
2900          };
2901  
2902  		function setValidElements(valid_elements) {
2903              elements = {};
2904              patternElements = [];
2905  
2906              addValidElements(valid_elements);
2907  
2908              each(schemaItems, function(element, name) {
2909                  children[name] = element.children;
2910              });
2911          };
2912  
2913          // Adds custom non HTML elements to the schema
2914  		function addCustomElements(custom_elements) {
2915              var customElementRegExp = /^(~)?(.+)$/;
2916  
2917              if (custom_elements) {
2918                  each(split(custom_elements), function(rule) {
2919                      var matches = customElementRegExp.exec(rule),
2920                          inline = matches[1] === '~',
2921                          cloneName = inline ? 'span' : 'div',
2922                          name = matches[2];
2923  
2924                      children[name] = children[cloneName];
2925                      customElementsMap[name] = cloneName;
2926  
2927                      // If it's not marked as inline then add it to valid block elements
2928                      if (!inline) {
2929                          blockElementsMap[name.toUpperCase()] = {};
2930                          blockElementsMap[name] = {};
2931                      }
2932  
2933                      // Add elements clone if needed
2934                      if (!elements[name]) {
2935                          elements[name] = elements[cloneName];
2936                      }
2937  
2938                      // Add custom elements at span/div positions
2939                      each(children, function(element, child) {
2940                          if (element[cloneName])
2941                              element[name] = element[cloneName];
2942                      });
2943                  });
2944              }
2945          };
2946  
2947          // Adds valid children to the schema object
2948  		function addValidChildren(valid_children) {
2949              var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2950  
2951              if (valid_children) {
2952                  each(split(valid_children), function(rule) {
2953                      var matches = childRuleRegExp.exec(rule), parent, prefix;
2954  
2955                      if (matches) {
2956                          prefix = matches[1];
2957  
2958                          // Add/remove items from default
2959                          if (prefix)
2960                              parent = children[matches[2]];
2961                          else
2962                              parent = children[matches[2]] = {'#comment' : {}};
2963  
2964                          parent = children[matches[2]];
2965  
2966                          each(split(matches[3], '|'), function(child) {
2967                              if (prefix === '-')
2968                                  delete parent[child];
2969                              else
2970                                  parent[child] = {};
2971                          });
2972                      }
2973                  });
2974              }
2975          };
2976  
2977  		function getElementRule(name) {
2978              var element = elements[name], i;
2979  
2980              // Exact match found
2981              if (element)
2982                  return element;
2983  
2984              // No exact match then try the patterns
2985              i = patternElements.length;
2986              while (i--) {
2987                  element = patternElements[i];
2988  
2989                  if (element.pattern.test(name))
2990                      return element;
2991              }
2992          };
2993  
2994          if (!settings.valid_elements) {
2995              // No valid elements defined then clone the elements from the schema spec
2996              each(schemaItems, function(element, name) {
2997                  elements[name] = {
2998                      attributes : element.attributes,
2999                      attributesOrder : element.attributesOrder
3000                  };
3001  
3002                  children[name] = element.children;
3003              });
3004  
3005              // Switch these on HTML4
3006              if (settings.schema != "html5") {
3007                  each(split('strong/b,em/i'), function(item) {
3008                      item = split(item, '/');
3009                      elements[item[1]].outputName = item[0];
3010                  });
3011              }
3012  
3013              // Add default alt attribute for images
3014              elements.img.attributesDefault = [{name: 'alt', value: ''}];
3015  
3016              // Remove these if they are empty by default
3017              each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3018                  if (elements[name]) {
3019                      elements[name].removeEmpty = true;
3020                  }
3021              });
3022  
3023              // Padd these by default
3024              each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3025                  elements[name].paddEmpty = true;
3026              });
3027          } else
3028              setValidElements(settings.valid_elements);
3029  
3030          addCustomElements(settings.custom_elements);
3031          addValidChildren(settings.valid_children);
3032          addValidElements(settings.extended_valid_elements);
3033  
3034          // Todo: Remove this when we fix list handling to be valid
3035          addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3036  
3037          // Delete invalid elements
3038          if (settings.invalid_elements) {
3039              tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3040                  if (elements[item])
3041                      delete elements[item];
3042              });
3043          }
3044  
3045          // If the user didn't allow span only allow internal spans
3046          if (!getElementRule('span'))
3047              addValidElements('span[!data-mce-type|*]');
3048  
3049          self.children = children;
3050  
3051          self.styles = validStyles;
3052  
3053          self.getBoolAttrs = function() {
3054              return boolAttrMap;
3055          };
3056  
3057          self.getBlockElements = function() {
3058              return blockElementsMap;
3059          };
3060  
3061          self.getTextBlockElements = function() {
3062              return textBlockElementsMap;
3063          };
3064  
3065          self.getShortEndedElements = function() {
3066              return shortEndedElementsMap;
3067          };
3068  
3069          self.getSelfClosingElements = function() {
3070              return selfClosingElementsMap;
3071          };
3072  
3073          self.getNonEmptyElements = function() {
3074              return nonEmptyElementsMap;
3075          };
3076  
3077          self.getWhiteSpaceElements = function() {
3078              return whiteSpaceElementsMap;
3079          };
3080  
3081          self.isValidChild = function(name, child) {
3082              var parent = children[name];
3083  
3084              return !!(parent && parent[child]);
3085          };
3086  
3087          self.isValid = function(name, attr) {
3088              var attrPatterns, i, rule = getElementRule(name);
3089  
3090              // Check if it's a valid element
3091              if (rule) {
3092                  if (attr) {
3093                      // Check if attribute name exists
3094                      if (rule.attributes[attr]) {
3095                          return true;
3096                      }
3097  
3098                      // Check if attribute matches a regexp pattern
3099                      attrPatterns = rule.attributePatterns;
3100                      if (attrPatterns) {
3101                          i = attrPatterns.length;
3102                          while (i--) {
3103                              if (attrPatterns[i].pattern.test(name)) {
3104                                  return true;
3105                              }
3106                          }
3107                      }
3108                  } else {
3109                      return true;
3110                  }
3111              }
3112  
3113              // No match
3114              return false;
3115          };
3116          
3117          self.getElementRule = getElementRule;
3118  
3119          self.getCustomElements = function() {
3120              return customElementsMap;
3121          };
3122  
3123          self.addValidElements = addValidElements;
3124  
3125          self.setValidElements = setValidElements;
3126  
3127          self.addCustomElements = addCustomElements;
3128  
3129          self.addValidChildren = addValidChildren;
3130  
3131          self.elements = elements;
3132      };
3133  })(tinymce);
3134  (function(tinymce) {
3135      tinymce.html.SaxParser = function(settings, schema) {
3136          var self = this, noop = function() {};
3137  
3138          settings = settings || {};
3139          self.schema = schema = schema || new tinymce.html.Schema();
3140  
3141          if (settings.fix_self_closing !== false)
3142              settings.fix_self_closing = true;
3143  
3144          // Add handler functions from settings and setup default handlers
3145          tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3146              if (name)
3147                  self[name] = settings[name] || noop;
3148          });
3149  
3150          self.parse = function(html) {
3151              var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3152                  shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3153                  validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3154                  tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3155  
3156  			function processEndTag(name) {
3157                  var pos, i;
3158  
3159                  // Find position of parent of the same type
3160                  pos = stack.length;
3161                  while (pos--) {
3162                      if (stack[pos].name === name)
3163                          break;                        
3164                  }
3165  
3166                  // Found parent
3167                  if (pos >= 0) {
3168                      // Close all the open elements
3169                      for (i = stack.length - 1; i >= pos; i--) {
3170                          name = stack[i];
3171  
3172                          if (name.valid)
3173                              self.end(name.name);
3174                      }
3175  
3176                      // Remove the open elements from the stack
3177                      stack.length = pos;
3178                  }
3179              };
3180  
3181  			function parseAttribute(match, name, value, val2, val3) {
3182                  var attrRule, i;
3183  
3184                  name = name.toLowerCase();
3185                  value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3186  
3187                  // Validate name and value
3188                  if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3189                      attrRule = validAttributesMap[name];
3190  
3191                      // Find rule by pattern matching
3192                      if (!attrRule && validAttributePatterns) {
3193                          i = validAttributePatterns.length;
3194                          while (i--) {
3195                              attrRule = validAttributePatterns[i];
3196                              if (attrRule.pattern.test(name))
3197                                  break;
3198                          }
3199  
3200                          // No rule matched
3201                          if (i === -1)
3202                              attrRule = null;
3203                      }
3204  
3205                      // No attribute rule found
3206                      if (!attrRule)
3207                          return;
3208  
3209                      // Validate value
3210                      if (attrRule.validValues && !(value in attrRule.validValues))
3211                          return;
3212                  }
3213  
3214                  // Add attribute to list and map
3215                  attrList.map[name] = value;
3216                  attrList.push({
3217                      name: name,
3218                      value: value
3219                  });
3220              };
3221  
3222              // Precompile RegExps and map objects
3223              tokenRegExp = new RegExp('<(?:' +
3224                  '(?:!--([\\w\\W]*?)-->)|' + // Comment
3225                  '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3226                  '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3227                  '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3228                  '(?:\\/([^>]+)>)|' + // End element
3229                  '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3230              ')', 'g');
3231  
3232              attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
3233              specialElements = {
3234                  'script' : /<\/script[^>]*>/gi,
3235                  'style' : /<\/style[^>]*>/gi,
3236                  'noscript' : /<\/noscript[^>]*>/gi
3237              };
3238  
3239              // Setup lookup tables for empty elements and boolean attributes
3240              shortEndedElements = schema.getShortEndedElements();
3241              selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3242              fillAttrsMap = schema.getBoolAttrs();
3243              validate = settings.validate;
3244              removeInternalElements = settings.remove_internals;
3245              fixSelfClosing = settings.fix_self_closing;
3246              isIE = tinymce.isIE;
3247              invalidPrefixRegExp = /^:/;
3248  
3249              while (matches = tokenRegExp.exec(html)) {
3250                  // Text
3251                  if (index < matches.index)
3252                      self.text(decode(html.substr(index, matches.index - index)));
3253  
3254                  if (value = matches[6]) { // End element
3255                      value = value.toLowerCase();
3256  
3257                      // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3258                      if (isIE && invalidPrefixRegExp.test(value))
3259                          value = value.substr(1);
3260  
3261                      processEndTag(value);
3262                  } else if (value = matches[7]) { // Start element
3263                      value = value.toLowerCase();
3264  
3265                      // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3266                      if (isIE && invalidPrefixRegExp.test(value))
3267                          value = value.substr(1);
3268  
3269                      isShortEnded = value in shortEndedElements;
3270  
3271                      // Is self closing tag for example an <li> after an open <li>
3272                      if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3273                          processEndTag(value);
3274  
3275                      // Validate element
3276                      if (!validate || (elementRule = schema.getElementRule(value))) {
3277                          isValidElement = true;
3278  
3279                          // Grab attributes map and patters when validation is enabled
3280                          if (validate) {
3281                              validAttributesMap = elementRule.attributes;
3282                              validAttributePatterns = elementRule.attributePatterns;
3283                          }
3284  
3285                          // Parse attributes
3286                          if (attribsValue = matches[8]) {
3287                              isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3288  
3289                              // If the element has internal attributes then remove it if we are told to do so
3290                              if (isInternalElement && removeInternalElements)
3291                                  isValidElement = false;
3292  
3293                              attrList = [];
3294                              attrList.map = {};
3295  
3296                              attribsValue.replace(attrRegExp, parseAttribute);
3297                          } else {
3298                              attrList = [];
3299                              attrList.map = {};
3300                          }
3301  
3302                          // Process attributes if validation is enabled
3303                          if (validate && !isInternalElement) {
3304                              attributesRequired = elementRule.attributesRequired;
3305                              attributesDefault = elementRule.attributesDefault;
3306                              attributesForced = elementRule.attributesForced;
3307  
3308                              // Handle forced attributes
3309                              if (attributesForced) {
3310                                  i = attributesForced.length;
3311                                  while (i--) {
3312                                      attr = attributesForced[i];
3313                                      name = attr.name;
3314                                      attrValue = attr.value;
3315  
3316                                      if (attrValue === '{$uid}')
3317                                          attrValue = 'mce_' + idCount++;
3318  
3319                                      attrList.map[name] = attrValue;
3320                                      attrList.push({name: name, value: attrValue});
3321                                  }
3322                              }
3323  
3324                              // Handle default attributes
3325                              if (attributesDefault) {
3326                                  i = attributesDefault.length;
3327                                  while (i--) {
3328                                      attr = attributesDefault[i];
3329                                      name = attr.name;
3330  
3331                                      if (!(name in attrList.map)) {
3332                                          attrValue = attr.value;
3333  
3334                                          if (attrValue === '{$uid}')
3335                                              attrValue = 'mce_' + idCount++;
3336  
3337                                          attrList.map[name] = attrValue;
3338                                          attrList.push({name: name, value: attrValue});
3339                                      }
3340                                  }
3341                              }
3342  
3343                              // Handle required attributes
3344                              if (attributesRequired) {
3345                                  i = attributesRequired.length;
3346                                  while (i--) {
3347                                      if (attributesRequired[i] in attrList.map)
3348                                          break;
3349                                  }
3350  
3351                                  // None of the required attributes where found
3352                                  if (i === -1)
3353                                      isValidElement = false;
3354                              }
3355  
3356                              // Invalidate element if it's marked as bogus
3357                              if (attrList.map['data-mce-bogus'])
3358                                  isValidElement = false;
3359                          }
3360  
3361                          if (isValidElement)
3362                              self.start(value, attrList, isShortEnded);
3363                      } else
3364                          isValidElement = false;
3365  
3366                      // Treat script, noscript and style a bit different since they may include code that looks like elements
3367                      if (endRegExp = specialElements[value]) {
3368                          endRegExp.lastIndex = index = matches.index + matches[0].length;
3369  
3370                          if (matches = endRegExp.exec(html)) {
3371                              if (isValidElement)
3372                                  text = html.substr(index, matches.index - index);
3373  
3374                              index = matches.index + matches[0].length;
3375                          } else {
3376                              text = html.substr(index);
3377                              index = html.length;
3378                          }
3379  
3380                          if (isValidElement && text.length > 0)
3381                              self.text(text, true);
3382  
3383                          if (isValidElement)
3384                              self.end(value);
3385  
3386                          tokenRegExp.lastIndex = index;
3387                          continue;
3388                      }
3389  
3390                      // Push value on to stack
3391                      if (!isShortEnded) {
3392                          if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3393                              stack.push({name: value, valid: isValidElement});
3394                          else if (isValidElement)
3395                              self.end(value);
3396                      }
3397                  } else if (value = matches[1]) { // Comment
3398                      self.comment(value);
3399                  } else if (value = matches[2]) { // CDATA
3400                      self.cdata(value);
3401                  } else if (value = matches[3]) { // DOCTYPE
3402                      self.doctype(value);
3403                  } else if (value = matches[4]) { // PI
3404                      self.pi(value, matches[5]);
3405                  }
3406  
3407                  index = matches.index + matches[0].length;
3408              }
3409  
3410              // Text
3411              if (index < html.length)
3412                  self.text(decode(html.substr(index)));
3413  
3414              // Close any open elements
3415              for (i = stack.length - 1; i >= 0; i--) {
3416                  value = stack[i];
3417  
3418                  if (value.valid)
3419                      self.end(value.name);
3420              }
3421          };
3422      }
3423  })(tinymce);
3424  (function(tinymce) {
3425      var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3426          '#text' : 3,
3427          '#comment' : 8,
3428          '#cdata' : 4,
3429          '#pi' : 7,
3430          '#doctype' : 10,
3431          '#document-fragment' : 11
3432      };
3433  
3434      // Walks the tree left/right
3435  	function walk(node, root_node, prev) {
3436          var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3437  
3438          // Walk into nodes if it has a start
3439          if (node[startName])
3440              return node[startName];
3441  
3442          // Return the sibling if it has one
3443          if (node !== root_node) {
3444              sibling = node[siblingName];
3445  
3446              if (sibling)
3447                  return sibling;
3448  
3449              // Walk up the parents to look for siblings
3450              for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3451                  sibling = parent[siblingName];
3452  
3453                  if (sibling)
3454                      return sibling;
3455              }
3456          }
3457      };
3458  
3459  	function Node(name, type) {
3460          this.name = name;
3461          this.type = type;
3462  
3463          if (type === 1) {
3464              this.attributes = [];
3465              this.attributes.map = {};
3466          }
3467      }
3468  
3469      tinymce.extend(Node.prototype, {
3470          replace : function(node) {
3471              var self = this;
3472  
3473              if (node.parent)
3474                  node.remove();
3475  
3476              self.insert(node, self);
3477              self.remove();
3478  
3479              return self;
3480          },
3481  
3482          attr : function(name, value) {
3483              var self = this, attrs, i, undef;
3484  
3485              if (typeof name !== "string") {
3486                  for (i in name)
3487                      self.attr(i, name[i]);
3488  
3489                  return self;
3490              }
3491  
3492              if (attrs = self.attributes) {
3493                  if (value !== undef) {
3494                      // Remove attribute
3495                      if (value === null) {
3496                          if (name in attrs.map) {
3497                              delete attrs.map[name];
3498  
3499                              i = attrs.length;
3500                              while (i--) {
3501                                  if (attrs[i].name === name) {
3502                                      attrs = attrs.splice(i, 1);
3503                                      return self;
3504                                  }
3505                              }
3506                          }
3507  
3508                          return self;
3509                      }
3510  
3511                      // Set attribute
3512                      if (name in attrs.map) {
3513                          // Set attribute
3514                          i = attrs.length;
3515                          while (i--) {
3516                              if (attrs[i].name === name) {
3517                                  attrs[i].value = value;
3518                                  break;
3519                              }
3520                          }
3521                      } else
3522                          attrs.push({name: name, value: value});
3523  
3524                      attrs.map[name] = value;
3525  
3526                      return self;
3527                  } else {
3528                      return attrs.map[name];
3529                  }
3530              }
3531          },
3532  
3533          clone : function() {
3534              var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3535  
3536              // Clone element attributes
3537              if (selfAttrs = self.attributes) {
3538                  cloneAttrs = [];
3539                  cloneAttrs.map = {};
3540  
3541                  for (i = 0, l = selfAttrs.length; i < l; i++) {
3542                      selfAttr = selfAttrs[i];
3543  
3544                      // Clone everything except id
3545                      if (selfAttr.name !== 'id') {
3546                          cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3547                          cloneAttrs.map[selfAttr.name] = selfAttr.value;
3548                      }
3549                  }
3550  
3551                  clone.attributes = cloneAttrs;
3552              }
3553  
3554              clone.value = self.value;
3555              clone.shortEnded = self.shortEnded;
3556  
3557              return clone;
3558          },
3559  
3560          wrap : function(wrapper) {
3561              var self = this;
3562  
3563              self.parent.insert(wrapper, self);
3564              wrapper.append(self);
3565  
3566              return self;
3567          },
3568  
3569          unwrap : function() {
3570              var self = this, node, next;
3571  
3572              for (node = self.firstChild; node; ) {
3573                  next = node.next;
3574                  self.insert(node, self, true);
3575                  node = next;
3576              }
3577  
3578              self.remove();
3579          },
3580  
3581          remove : function() {
3582              var self = this, parent = self.parent, next = self.next, prev = self.prev;
3583  
3584              if (parent) {
3585                  if (parent.firstChild === self) {
3586                      parent.firstChild = next;
3587  
3588                      if (next)
3589                          next.prev = null;
3590                  } else {
3591                      prev.next = next;
3592                  }
3593  
3594                  if (parent.lastChild === self) {
3595                      parent.lastChild = prev;
3596  
3597                      if (prev)
3598                          prev.next = null;
3599                  } else {
3600                      next.prev = prev;
3601                  }
3602  
3603                  self.parent = self.next = self.prev = null;
3604              }
3605  
3606              return self;
3607          },
3608  
3609          append : function(node) {
3610              var self = this, last;
3611  
3612              if (node.parent)
3613                  node.remove();
3614  
3615              last = self.lastChild;
3616              if (last) {
3617                  last.next = node;
3618                  node.prev = last;
3619                  self.lastChild = node;
3620              } else
3621                  self.lastChild = self.firstChild = node;
3622  
3623              node.parent = self;
3624  
3625              return node;
3626          },
3627  
3628          insert : function(node, ref_node, before) {
3629              var parent;
3630  
3631              if (node.parent)
3632                  node.remove();
3633  
3634              parent = ref_node.parent || this;
3635  
3636              if (before) {
3637                  if (ref_node === parent.firstChild)
3638                      parent.firstChild = node;
3639                  else
3640                      ref_node.prev.next = node;
3641  
3642                  node.prev = ref_node.prev;
3643                  node.next = ref_node;
3644                  ref_node.prev = node;
3645              } else {
3646                  if (ref_node === parent.lastChild)
3647                      parent.lastChild = node;
3648                  else
3649                      ref_node.next.prev = node;
3650  
3651                  node.next = ref_node.next;
3652                  node.prev = ref_node;
3653                  ref_node.next = node;
3654              }
3655  
3656              node.parent = parent;
3657  
3658              return node;
3659          },
3660  
3661          getAll : function(name) {
3662              var self = this, node, collection = [];
3663  
3664              for (node = self.firstChild; node; node = walk(node, self)) {
3665                  if (node.name === name)
3666                      collection.push(node);
3667              }
3668  
3669              return collection;
3670          },
3671  
3672          empty : function() {
3673              var self = this, nodes, i, node;
3674  
3675              // Remove all children
3676              if (self.firstChild) {
3677                  nodes = [];
3678  
3679                  // Collect the children
3680                  for (node = self.firstChild; node; node = walk(node, self))
3681                      nodes.push(node);
3682  
3683                  // Remove the children
3684                  i = nodes.length;
3685                  while (i--) {
3686                      node = nodes[i];
3687                      node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3688                  }
3689              }
3690  
3691              self.firstChild = self.lastChild = null;
3692  
3693              return self;
3694          },
3695  
3696          isEmpty : function(elements) {
3697              var self = this, node = self.firstChild, i, name;
3698  
3699              if (node) {
3700                  do {
3701                      if (node.type === 1) {
3702                          // Ignore bogus elements
3703                          if (node.attributes.map['data-mce-bogus'])
3704                              continue;
3705  
3706                          // Keep empty elements like <img />
3707                          if (elements[node.name])
3708                              return false;
3709  
3710                          // Keep elements with data attributes or name attribute like <a name="1"></a>
3711                          i = node.attributes.length;
3712                          while (i--) {
3713                              name = node.attributes[i].name;
3714                              if (name === "name" || name.indexOf('data-mce-') === 0)
3715                                  return false;
3716                          }
3717                      }
3718  
3719                      // Keep comments
3720                      if (node.type === 8)
3721                          return false;
3722                      
3723                      // Keep non whitespace text nodes
3724                      if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3725                          return false;
3726                  } while (node = walk(node, self));
3727              }
3728  
3729              return true;
3730          },
3731  
3732          walk : function(prev) {
3733              return walk(this, null, prev);
3734          }
3735      });
3736  
3737      tinymce.extend(Node, {
3738          create : function(name, attrs) {
3739              var node, attrName;
3740  
3741              // Create node
3742              node = new Node(name, typeLookup[name] || 1);
3743  
3744              // Add attributes if needed
3745              if (attrs) {
3746                  for (attrName in attrs)
3747                      node.attr(attrName, attrs[attrName]);
3748              }
3749  
3750              return node;
3751          }
3752      });
3753  
3754      tinymce.html.Node = Node;
3755  })(tinymce);
3756  (function(tinymce) {
3757      var Node = tinymce.html.Node;
3758  
3759      tinymce.html.DomParser = function(settings, schema) {
3760          var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3761  
3762          settings = settings || {};
3763          settings.validate = "validate" in settings ? settings.validate : true;
3764          settings.root_name = settings.root_name || 'body';
3765          self.schema = schema = schema || new tinymce.html.Schema();
3766  
3767  		function fixInvalidChildren(nodes) {
3768              var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3769                  childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
3770  
3771              nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3772              nonEmptyElements = schema.getNonEmptyElements();
3773              textBlockElements = schema.getTextBlockElements();
3774  
3775              for (ni = 0; ni < nodes.length; ni++) {
3776                  node = nodes[ni];
3777  
3778                  // Already removed or fixed
3779                  if (!node.parent || node.fixed)
3780                      continue;
3781  
3782                  // If the invalid element is a text block and the text block is within a parent LI element
3783                  // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
3784                  if (textBlockElements[node.name] && node.parent.name == 'li') {
3785                      // Move sibling text blocks after LI element
3786                      sibling = node.next;
3787                      while (sibling) {
3788                          if (textBlockElements[sibling.name]) {
3789                              sibling.name = 'li';
3790                              sibling.fixed = true;
3791                              node.parent.insert(sibling, node.parent);
3792                          } else {
3793                              break;
3794                          }
3795  
3796                          sibling = sibling.next;
3797                      }
3798  
3799                      // Unwrap current text block
3800                      node.unwrap(node);
3801                      continue;
3802                  }
3803  
3804                  // Get list of all parent nodes until we find a valid parent to stick the child into
3805                  parents = [node];
3806                  for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3807                      parents.push(parent);
3808  
3809                  // Found a suitable parent
3810                  if (parent && parents.length > 1) {
3811                      // Reverse the array since it makes looping easier
3812                      parents.reverse();
3813  
3814                      // Clone the related parent and insert that after the moved node
3815                      newParent = currentNode = self.filterNode(parents[0].clone());
3816  
3817                      // Start cloning and moving children on the left side of the target node
3818                      for (i = 0; i < parents.length - 1; i++) {
3819                          if (schema.isValidChild(currentNode.name, parents[i].name)) {
3820                              tempNode = self.filterNode(parents[i].clone());
3821                              currentNode.append(tempNode);
3822                          } else
3823                              tempNode = currentNode;
3824  
3825                          for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3826                              nextNode = childNode.next;
3827                              tempNode.append(childNode);
3828                              childNode = nextNode;
3829                          }
3830  
3831                          currentNode = tempNode;
3832                      }
3833  
3834                      if (!newParent.isEmpty(nonEmptyElements)) {
3835                          parent.insert(newParent, parents[0], true);
3836                          parent.insert(node, newParent);
3837                      } else {
3838                          parent.insert(node, parents[0], true);
3839                      }
3840  
3841                      // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3842                      parent = parents[0];
3843                      if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3844                          parent.empty().remove();
3845                      }
3846                  } else if (node.parent) {
3847                      // If it's an LI try to find a UL/OL for it or wrap it
3848                      if (node.name === 'li') {
3849                          sibling = node.prev;
3850                          if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3851                              sibling.append(node);
3852                              continue;
3853                          }
3854  
3855                          sibling = node.next;
3856                          if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3857                              sibling.insert(node, sibling.firstChild, true);
3858                              continue;
3859                          }
3860  
3861                          node.wrap(self.filterNode(new Node('ul', 1)));
3862                          continue;
3863                      }
3864  
3865                      // Try wrapping the element in a DIV
3866                      if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3867                          node.wrap(self.filterNode(new Node('div', 1)));
3868                      } else {
3869                          // We failed wrapping it, then remove or unwrap it
3870                          if (node.name === 'style' || node.name === 'script')
3871                              node.empty().remove();
3872                          else
3873                              node.unwrap();
3874                      }
3875                  }
3876              }
3877          };
3878  
3879          self.filterNode = function(node) {
3880              var i, name, list;
3881  
3882              // Run element filters
3883              if (name in nodeFilters) {
3884                  list = matchedNodes[name];
3885  
3886                  if (list)
3887                      list.push(node);
3888                  else
3889                      matchedNodes[name] = [node];
3890              }
3891  
3892              // Run attribute filters
3893              i = attributeFilters.length;
3894              while (i--) {
3895                  name = attributeFilters[i].name;
3896  
3897                  if (name in node.attributes.map) {
3898                      list = matchedAttributes[name];
3899  
3900                      if (list)
3901                          list.push(node);
3902                      else
3903                          matchedAttributes[name] = [node];
3904                  }
3905              }
3906  
3907              return node;
3908          };
3909  
3910          self.addNodeFilter = function(name, callback) {
3911              tinymce.each(tinymce.explode(name), function(name) {
3912                  var list = nodeFilters[name];
3913  
3914                  if (!list)
3915                      nodeFilters[name] = list = [];
3916  
3917                  list.push(callback);
3918              });
3919          };
3920  
3921          self.addAttributeFilter = function(name, callback) {
3922              tinymce.each(tinymce.explode(name), function(name) {
3923                  var i;
3924  
3925                  for (i = 0; i < attributeFilters.length; i++) {
3926                      if (attributeFilters[i].name === name) {
3927                          attributeFilters[i].callbacks.push(callback);
3928                          return;
3929                      }
3930                  }
3931  
3932                  attributeFilters.push({name: name, callbacks: [callback]});
3933              });
3934          };
3935  
3936          self.parse = function(html, args) {
3937              var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3938                  blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3939                  endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3940  
3941              args = args || {};
3942              matchedNodes = {};
3943              matchedAttributes = {};
3944              blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3945              nonEmptyElements = schema.getNonEmptyElements();
3946              children = schema.children;
3947              validate = settings.validate;
3948              rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3949  
3950              whiteSpaceElements = schema.getWhiteSpaceElements();
3951              startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3952              endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3953              allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3954              isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3955  
3956  			function addRootBlocks() {
3957                  var node = rootNode.firstChild, next, rootBlockNode;
3958  
3959                  while (node) {
3960                      next = node.next;
3961  
3962                      if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3963                          if (!rootBlockNode) {
3964                              // Create a new root block element
3965                              rootBlockNode = createNode(rootBlockName, 1);
3966                              rootNode.insert(rootBlockNode, node);
3967                              rootBlockNode.append(node);
3968                          } else
3969                              rootBlockNode.append(node);
3970                      } else {
3971                          rootBlockNode = null;
3972                      }
3973  
3974                      node = next;
3975                  };
3976              };
3977  
3978  			function createNode(name, type) {
3979                  var node = new Node(name, type), list;
3980  
3981                  if (name in nodeFilters) {
3982                      list = matchedNodes[name];
3983  
3984                      if (list)
3985                          list.push(node);
3986                      else
3987                          matchedNodes[name] = [node];
3988                  }
3989  
3990                  return node;
3991              };
3992  
3993  			function removeWhitespaceBefore(node) {
3994                  var textNode, textVal, sibling;
3995  
3996                  for (textNode = node.prev; textNode && textNode.type === 3; ) {
3997                      textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3998  
3999                      if (textVal.length > 0) {
4000                          textNode.value = textVal;
4001                          textNode = textNode.prev;
4002                      } else {
4003                          sibling = textNode.prev;
4004                          textNode.remove();
4005                          textNode = sibling;
4006                      }
4007                  }
4008              };
4009  
4010  			function cloneAndExcludeBlocks(input) {
4011                  var name, output = {};
4012  
4013                  for (name in input) {
4014                      if (name !== 'li' && name != 'p') {
4015                          output[name] = input[name];
4016                      }
4017                  }
4018  
4019                  return output;
4020              };
4021  
4022              parser = new tinymce.html.SaxParser({
4023                  validate : validate,
4024  
4025                  // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
4026                  self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
4027  
4028                  cdata: function(text) {
4029                      node.append(createNode('#cdata', 4)).value = text;
4030                  },
4031  
4032                  text: function(text, raw) {
4033                      var textNode;
4034  
4035                      // Trim all redundant whitespace on non white space elements
4036                      if (!isInWhiteSpacePreservedElement) {
4037                          text = text.replace(allWhiteSpaceRegExp, ' ');
4038  
4039                          if (node.lastChild && blockElements[node.lastChild.name])
4040                              text = text.replace(startWhiteSpaceRegExp, '');
4041                      }
4042  
4043                      // Do we need to create the node
4044                      if (text.length !== 0) {
4045                          textNode = createNode('#text', 3);
4046                          textNode.raw = !!raw;
4047                          node.append(textNode).value = text;
4048                      }
4049                  },
4050  
4051                  comment: function(text) {
4052                      node.append(createNode('#comment', 8)).value = text;
4053                  },
4054  
4055                  pi: function(name, text) {
4056                      node.append(createNode(name, 7)).value = text;
4057                      removeWhitespaceBefore(node);
4058                  },
4059  
4060                  doctype: function(text) {
4061                      var newNode;
4062          
4063                      newNode = node.append(createNode('#doctype', 10));
4064                      newNode.value = text;
4065                      removeWhitespaceBefore(node);
4066                  },
4067  
4068                  start: function(name, attrs, empty) {
4069                      var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4070  
4071                      elementRule = validate ? schema.getElementRule(name) : {};
4072                      if (elementRule) {
4073                          newNode = createNode(elementRule.outputName || name, 1);
4074                          newNode.attributes = attrs;
4075                          newNode.shortEnded = empty;
4076  
4077                          node.append(newNode);
4078  
4079                          // Check if node is valid child of the parent node is the child is
4080                          // unknown we don't collect it since it's probably a custom element
4081                          parent = children[node.name];
4082                          if (parent && children[newNode.name] && !parent[newNode.name])
4083                              invalidChildren.push(newNode);
4084  
4085                          attrFiltersLen = attributeFilters.length;
4086                          while (attrFiltersLen--) {
4087                              attrName = attributeFilters[attrFiltersLen].name;
4088  
4089                              if (attrName in attrs.map) {
4090                                  list = matchedAttributes[attrName];
4091  
4092                                  if (list)
4093                                      list.push(newNode);
4094                                  else
4095                                      matchedAttributes[attrName] = [newNode];
4096                              }
4097                          }
4098  
4099                          // Trim whitespace before block
4100                          if (blockElements[name])
4101                              removeWhitespaceBefore(newNode);
4102  
4103                          // Change current node if the element wasn't empty i.e not <br /> or <img />
4104                          if (!empty)
4105                              node = newNode;
4106  
4107                          // Check if we are inside a whitespace preserved element
4108                          if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4109                              isInWhiteSpacePreservedElement = true;
4110                          }
4111                      }
4112                  },
4113  
4114                  end: function(name) {
4115                      var textNode, elementRule, text, sibling, tempNode;
4116  
4117                      elementRule = validate ? schema.getElementRule(name) : {};
4118                      if (elementRule) {
4119                          if (blockElements[name]) {
4120                              if (!isInWhiteSpacePreservedElement) {
4121                                  // Trim whitespace of the first node in a block
4122                                  textNode = node.firstChild;
4123                                  if (textNode && textNode.type === 3) {
4124                                      text = textNode.value.replace(startWhiteSpaceRegExp, '');
4125  
4126                                      // Any characters left after trim or should we remove it
4127                                      if (text.length > 0) {
4128                                          textNode.value = text;
4129                                          textNode = textNode.next;
4130                                      } else {
4131                                          sibling = textNode.next;
4132                                          textNode.remove();
4133                                          textNode = sibling;
4134                                      }
4135  
4136                                      // Remove any pure whitespace siblings
4137                                      while (textNode && textNode.type === 3) {
4138                                          text = textNode.value;
4139                                          sibling = textNode.next;
4140  
4141                                          if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4142                                              textNode.remove();
4143                                              textNode = sibling;
4144                                          }
4145  
4146                                          textNode = sibling;
4147                                      }
4148                                  }
4149  
4150                                  // Trim whitespace of the last node in a block
4151                                  textNode = node.lastChild;
4152                                  if (textNode && textNode.type === 3) {
4153                                      text = textNode.value.replace(endWhiteSpaceRegExp, '');
4154  
4155                                      // Any characters left after trim or should we remove it
4156                                      if (text.length > 0) {
4157                                          textNode.value = text;
4158                                          textNode = textNode.prev;
4159                                      } else {
4160                                          sibling = textNode.prev;
4161                                          textNode.remove();
4162                                          textNode = sibling;
4163                                      }
4164  
4165                                      // Remove any pure whitespace siblings
4166                                      while (textNode && textNode.type === 3) {
4167                                          text = textNode.value;
4168                                          sibling = textNode.prev;
4169  
4170                                          if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4171                                              textNode.remove();
4172                                              textNode = sibling;
4173                                          }
4174  
4175                                          textNode = sibling;
4176                                      }
4177                                  }
4178                              }
4179  
4180                              // Trim start white space
4181                              // Removed due to: #5424
4182                              /*textNode = node.prev;
4183                              if (textNode && textNode.type === 3) {
4184                                  text = textNode.value.replace(startWhiteSpaceRegExp, '');
4185  
4186                                  if (text.length > 0)
4187                                      textNode.value = text;
4188                                  else
4189                                      textNode.remove();
4190                              }*/
4191                          }
4192  
4193                          // Check if we exited a whitespace preserved element
4194                          if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4195                              isInWhiteSpacePreservedElement = false;
4196                          }
4197  
4198                          // Handle empty nodes
4199                          if (elementRule.removeEmpty || elementRule.paddEmpty) {
4200                              if (node.isEmpty(nonEmptyElements)) {
4201                                  if (elementRule.paddEmpty)
4202                                      node.empty().append(new Node('#text', '3')).value = '\u00a0';
4203                                  else {
4204                                      // Leave nodes that have a name like <a name="name">
4205                                      if (!node.attributes.map.name && !node.attributes.map.id) {
4206                                          tempNode = node.parent;
4207                                          node.empty().remove();
4208                                          node = tempNode;
4209                                          return;
4210                                      }
4211                                  }
4212                              }
4213                          }
4214  
4215                          node = node.parent;
4216                      }
4217                  }
4218              }, schema);
4219  
4220              rootNode = node = new Node(args.context || settings.root_name, 11);
4221  
4222              parser.parse(html);
4223  
4224              // Fix invalid children or report invalid children in a contextual parsing
4225              if (validate && invalidChildren.length) {
4226                  if (!args.context)
4227                      fixInvalidChildren(invalidChildren);
4228                  else
4229                      args.invalid = true;
4230              }
4231  
4232              // Wrap nodes in the root into block elements if the root is body
4233              if (rootBlockName && rootNode.name == 'body')
4234                  addRootBlocks();
4235  
4236              // Run filters only when the contents is valid
4237              if (!args.invalid) {
4238                  // Run node filters
4239                  for (name in matchedNodes) {
4240                      list = nodeFilters[name];
4241                      nodes = matchedNodes[name];
4242  
4243                      // Remove already removed children
4244                      fi = nodes.length;
4245                      while (fi--) {
4246                          if (!nodes[fi].parent)
4247                              nodes.splice(fi, 1);
4248                      }
4249  
4250                      for (i = 0, l = list.length; i < l; i++)
4251                          list[i](nodes, name, args);
4252                  }
4253  
4254                  // Run attribute filters
4255                  for (i = 0, l = attributeFilters.length; i < l; i++) {
4256                      list = attributeFilters[i];
4257  
4258                      if (list.name in matchedAttributes) {
4259                          nodes = matchedAttributes[list.name];
4260  
4261                          // Remove already removed children
4262                          fi = nodes.length;
4263                          while (fi--) {
4264                              if (!nodes[fi].parent)
4265                                  nodes.splice(fi, 1);
4266                          }
4267  
4268                          for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4269                              list.callbacks[fi](nodes, list.name, args);
4270                      }
4271                  }
4272              }
4273  
4274              return rootNode;
4275          };
4276  
4277          // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4278          // make it possible to place the caret inside empty blocks. This logic tries to remove
4279          // these elements and keep br elements that where intended to be there intact
4280          if (settings.remove_trailing_brs) {
4281              self.addNodeFilter('br', function(nodes, name) {
4282                  var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4283                      nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4284  
4285                  // Remove brs from body element as well
4286                  blockElements.body = 1;
4287  
4288                  // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4289                  for (i = 0; i < l; i++) {
4290                      node = nodes[i];
4291                      parent = node.parent;
4292  
4293                      if (blockElements[node.parent.name] && node === parent.lastChild) {
4294                          // Loop all nodes to the left of the current node and check for other BR elements
4295                          // excluding bookmarks since they are invisible
4296                          prev = node.prev;
4297                          while (prev) {
4298                              prevName = prev.name;
4299  
4300                              // Ignore bookmarks
4301                              if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4302                                  // Found a non BR element
4303                                  if (prevName !== "br")
4304                                      break;
4305      
4306                                  // Found another br it's a <br><br> structure then don't remove anything
4307                                  if (prevName === 'br') {
4308                                      node = null;
4309                                      break;
4310                                  }
4311                              }
4312  
4313                              prev = prev.prev;
4314                          }
4315  
4316                          if (node) {
4317                              node.remove();
4318  
4319                              // Is the parent to be considered empty after we removed the BR
4320                              if (parent.isEmpty(nonEmptyElements)) {
4321                                  elementRule = schema.getElementRule(parent.name);
4322  
4323                                  // Remove or padd the element depending on schema rule
4324                                  if (elementRule) {
4325                                      if (elementRule.removeEmpty)
4326                                          parent.remove();
4327                                      else if (elementRule.paddEmpty)
4328                                          parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4329                                  }
4330                              }
4331                          }
4332                      } else {
4333                          // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i>&nbsp;</i></b></p> 
4334                          lastParent = node;
4335                          while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4336                              lastParent = parent;
4337  
4338                              if (blockElements[parent.name]) {
4339                                  break;
4340                              }
4341  
4342                              parent = parent.parent;
4343                          }
4344  
4345                          if (lastParent === parent) {
4346                              textNode = new tinymce.html.Node('#text', 3);
4347                              textNode.value = '\u00a0';
4348                              node.replace(textNode);
4349                          }
4350                      }
4351                  }
4352              });
4353          }
4354  
4355          // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4356          if (!settings.allow_html_in_named_anchor) {
4357              self.addAttributeFilter('id,name', function(nodes, name) {
4358                  var i = nodes.length, sibling, prevSibling, parent, node;
4359  
4360                  while (i--) {
4361                      node = nodes[i];
4362                      if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4363                          parent = node.parent;
4364  
4365                          // Move children after current node
4366                          sibling = node.lastChild;
4367                          do {
4368                              prevSibling = sibling.prev;
4369                              parent.insert(sibling, node);
4370                              sibling = prevSibling;
4371                          } while (sibling);
4372                      }
4373                  }
4374              });
4375          }
4376      }
4377  })(tinymce);
4378  tinymce.html.Writer = function(settings) {
4379      var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4380  
4381      settings = settings || {};
4382      indent = settings.indent;
4383      indentBefore = tinymce.makeMap(settings.indent_before || '');
4384      indentAfter = tinymce.makeMap(settings.indent_after || '');
4385      encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4386      htmlOutput = settings.element_format == "html";
4387  
4388      return {
4389          start: function(name, attrs, empty) {
4390              var i, l, attr, value;
4391  
4392              if (indent && indentBefore[name] && html.length > 0) {
4393                  value = html[html.length - 1];
4394  
4395                  if (value.length > 0 && value !== '\n')
4396                      html.push('\n');
4397              }
4398  
4399              html.push('<', name);
4400  
4401              if (attrs) {
4402                  for (i = 0, l = attrs.length; i < l; i++) {
4403                      attr = attrs[i];
4404                      html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4405                  }
4406              }
4407  
4408              if (!empty || htmlOutput)
4409                  html[html.length] = '>';
4410              else
4411                  html[html.length] = ' />';
4412  
4413              if (empty && indent && indentAfter[name] && html.length > 0) {
4414                  value = html[html.length - 1];
4415  
4416                  if (value.length > 0 && value !== '\n')
4417                      html.push('\n');
4418              }
4419          },
4420  
4421          end: function(name) {
4422              var value;
4423  
4424              /*if (indent && indentBefore[name] && html.length > 0) {
4425                  value = html[html.length - 1];
4426  
4427                  if (value.length > 0 && value !== '\n')
4428                      html.push('\n');
4429              }*/
4430  
4431              html.push('</', name, '>');
4432  
4433              if (indent && indentAfter[name] && html.length > 0) {
4434                  value = html[html.length - 1];
4435  
4436                  if (value.length > 0 && value !== '\n')
4437                      html.push('\n');
4438              }
4439          },
4440  
4441          text: function(text, raw) {
4442              if (text.length > 0)
4443                  html[html.length] = raw ? text : encode(text);
4444          },
4445  
4446          cdata: function(text) {
4447              html.push('<![CDATA[', text, ']]>');
4448          },
4449  
4450          comment: function(text) {
4451              html.push('<!--', text, '-->');
4452          },
4453  
4454          pi: function(name, text) {
4455              if (text)
4456                  html.push('<?', name, ' ', text, '?>');
4457              else
4458                  html.push('<?', name, '?>');
4459  
4460              if (indent)
4461                  html.push('\n');
4462          },
4463  
4464          doctype: function(text) {
4465              html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4466          },
4467  
4468          reset: function() {
4469              html.length = 0;
4470          },
4471  
4472          getContent: function() {
4473              return html.join('').replace(/\n$/, '');
4474          }
4475      };
4476  };
4477  (function(tinymce) {
4478      tinymce.html.Serializer = function(settings, schema) {
4479          var self = this, writer = new tinymce.html.Writer(settings);
4480  
4481          settings = settings || {};
4482          settings.validate = "validate" in settings ? settings.validate : true;
4483  
4484          self.schema = schema = schema || new tinymce.html.Schema();
4485          self.writer = writer;
4486  
4487          self.serialize = function(node) {
4488              var handlers, validate;
4489  
4490              validate = settings.validate;
4491  
4492              handlers = {
4493                  // #text
4494                  3: function(node, raw) {
4495                      writer.text(node.value, node.raw);
4496                  },
4497  
4498                  // #comment
4499                  8: function(node) {
4500                      writer.comment(node.value);
4501                  },
4502  
4503                  // Processing instruction
4504                  7: function(node) {
4505                      writer.pi(node.name, node.value);
4506                  },
4507  
4508                  // Doctype
4509                  10: function(node) {
4510                      writer.doctype(node.value);
4511                  },
4512  
4513                  // CDATA
4514                  4: function(node) {
4515                      writer.cdata(node.value);
4516                  },
4517  
4518                  // Document fragment
4519                  11: function(node) {
4520                      if ((node = node.firstChild)) {
4521                          do {
4522                              walk(node);
4523                          } while (node = node.next);
4524                      }
4525                  }
4526              };
4527  
4528              writer.reset();
4529  
4530  			function walk(node) {
4531                  var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4532  
4533                  if (!handler) {
4534                      name = node.name;
4535                      isEmpty = node.shortEnded;
4536                      attrs = node.attributes;
4537  
4538                      // Sort attributes
4539                      if (validate && attrs && attrs.length > 1) {
4540                          sortedAttrs = [];
4541                          sortedAttrs.map = {};
4542  
4543                          elementRule = schema.getElementRule(node.name);
4544                          for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4545                              attrName = elementRule.attributesOrder[i];
4546  
4547                              if (attrName in attrs.map) {
4548                                  attrValue = attrs.map[attrName];
4549                                  sortedAttrs.map[attrName] = attrValue;
4550                                  sortedAttrs.push({name: attrName, value: attrValue});
4551                              }
4552                          }
4553  
4554                          for (i = 0, l = attrs.length; i < l; i++) {
4555                              attrName = attrs[i].name;
4556  
4557                              if (!(attrName in sortedAttrs.map)) {
4558                                  attrValue = attrs.map[attrName];
4559                                  sortedAttrs.map[attrName] = attrValue;
4560                                  sortedAttrs.push({name: attrName, value: attrValue});
4561                              }
4562                          }
4563  
4564                          attrs = sortedAttrs;
4565                      }
4566  
4567                      writer.start(node.name, attrs, isEmpty);
4568  
4569                      if (!isEmpty) {
4570                          if ((node = node.firstChild)) {
4571                              do {
4572                                  walk(node);
4573                              } while (node = node.next);
4574                          }
4575  
4576                          writer.end(name);
4577                      }
4578                  } else
4579                      handler(node);
4580              }
4581  
4582              // Serialize element and treat all non elements as fragments
4583              if (node.type == 1 && !settings.inner)
4584                  walk(node);
4585              else
4586                  handlers[11](node);
4587  
4588              return writer.getContent();
4589          };
4590      }
4591  })(tinymce);
4592  // JSLint defined globals
4593  /*global tinymce:false, window:false */
4594  
4595  tinymce.dom = {};
4596  
4597  (function(namespace, expando) {
4598      var w3cEventModel = !!document.addEventListener;
4599  
4600  	function addEvent(target, name, callback, capture) {
4601          if (target.addEventListener) {
4602              target.addEventListener(name, callback, capture || false);
4603          } else if (target.attachEvent) {
4604              target.attachEvent('on' + name, callback);
4605          }
4606      }
4607  
4608  	function removeEvent(target, name, callback, capture) {
4609          if (target.removeEventListener) {
4610              target.removeEventListener(name, callback, capture || false);
4611          } else if (target.detachEvent) {
4612              target.detachEvent('on' + name, callback);
4613          }
4614      }
4615  
4616  	function fix(original_event, data) {
4617          var name, event = data || {};
4618  
4619          // Dummy function that gets replaced on the delegation state functions
4620  		function returnFalse() {
4621              return false;
4622          }
4623  
4624          // Dummy function that gets replaced on the delegation state functions
4625  		function returnTrue() {
4626              return true;
4627          }
4628  
4629          // Copy all properties from the original event
4630          for (name in original_event) {
4631              // layerX/layerY is deprecated in Chrome and produces a warning
4632              if (name !== "layerX" && name !== "layerY") {
4633                  event[name] = original_event[name];
4634              }
4635          }
4636  
4637          // Normalize target IE uses srcElement
4638          if (!event.target) {
4639              event.target = event.srcElement || document;
4640          }
4641  
4642          // Add preventDefault method
4643          event.preventDefault = function() {
4644              event.isDefaultPrevented = returnTrue;
4645  
4646              // Execute preventDefault on the original event object
4647              if (original_event) {
4648                  if (original_event.preventDefault) {
4649                      original_event.preventDefault();
4650                  } else {
4651                      original_event.returnValue = false; // IE
4652                  }
4653              }
4654          };
4655  
4656          // Add stopPropagation
4657          event.stopPropagation = function() {
4658              event.isPropagationStopped = returnTrue;
4659  
4660              // Execute stopPropagation on the original event object
4661              if (original_event) {
4662                  if (original_event.stopPropagation) {
4663                      original_event.stopPropagation();
4664                  } else {
4665                      original_event.cancelBubble = true; // IE
4666                  }
4667               }
4668          };
4669  
4670          // Add stopImmediatePropagation
4671          event.stopImmediatePropagation = function() {
4672              event.isImmediatePropagationStopped = returnTrue;
4673              event.stopPropagation();
4674          };
4675  
4676          // Add event delegation states
4677          if (!event.isDefaultPrevented) {
4678              event.isDefaultPrevented = returnFalse;
4679              event.isPropagationStopped = returnFalse;
4680              event.isImmediatePropagationStopped = returnFalse;
4681          }
4682  
4683          return event;
4684      }
4685  
4686  	function bindOnReady(win, callback, event_utils) {
4687          var doc = win.document, event = {type: 'ready'};
4688  
4689          // Gets called when the DOM is ready
4690  		function readyHandler() {
4691              if (!event_utils.domLoaded) {
4692                  event_utils.domLoaded = true;
4693                  callback(event);
4694              }
4695          }
4696  
4697          // Page already loaded then fire it directly
4698          if (doc.readyState == "complete") {
4699              readyHandler();
4700              return;
4701          }
4702  
4703          // Use W3C method
4704          if (w3cEventModel) {
4705              addEvent(win, 'DOMContentLoaded', readyHandler);
4706          } else {
4707              // Use IE method
4708              addEvent(doc, "readystatechange", function() {
4709                  if (doc.readyState === "complete") {
4710                      removeEvent(doc, "readystatechange", arguments.callee);
4711                      readyHandler();
4712                  }
4713              });
4714  
4715              // Wait until we can scroll, when we can the DOM is initialized
4716              if (doc.documentElement.doScroll && win === win.top) {
4717                  (function() {
4718                      try {
4719                          // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4720                          // http://javascript.nwbox.com/IEContentLoaded/
4721                          doc.documentElement.doScroll("left");
4722                      } catch (ex) {
4723                          setTimeout(arguments.callee, 0);
4724                          return;
4725                      }
4726  
4727                      readyHandler();
4728                  })();
4729              }
4730          }
4731  
4732          // Fallback if any of the above methods should fail for some odd reason
4733          addEvent(win, 'load', readyHandler);
4734      }
4735  
4736  	function EventUtils(proxy) {
4737          var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4738  
4739          hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4740          hasFocusIn = "onfocusin" in document.documentElement;
4741          mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4742          count = 1;
4743  
4744          // State if the DOMContentLoaded was executed or not
4745          self.domLoaded = false;
4746          self.events = events;
4747  
4748  		function executeHandlers(evt, id) {
4749              var callbackList, i, l, callback;
4750  
4751              callbackList = events[id][evt.type];
4752              if (callbackList) {
4753                  for (i = 0, l = callbackList.length; i < l; i++) {
4754                      callback = callbackList[i];
4755                      
4756                      // Check if callback exists might be removed if a unbind is called inside the callback
4757                      if (callback && callback.func.call(callback.scope, evt) === false) {
4758                          evt.preventDefault();
4759                      }
4760  
4761                      // Should we stop propagation to immediate listeners
4762                      if (evt.isImmediatePropagationStopped()) {
4763                          return;
4764                      }
4765                  }
4766              }
4767          }
4768  
4769          self.bind = function(target, names, callback, scope) {
4770              var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4771  
4772              // Native event handler function patches the event and executes the callbacks for the expando
4773  			function defaultNativeHandler(evt) {
4774                  executeHandlers(fix(evt || win.event), id);
4775              }
4776  
4777              // Don't bind to text nodes or comments
4778              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4779                  return;
4780              }
4781  
4782              // Create or get events id for the target
4783              if (!target[expando]) {
4784                  id = count++;
4785                  target[expando] = id;
4786                  events[id] = {};
4787              } else {
4788                  id = target[expando];
4789  
4790                  if (!events[id]) {
4791                      events[id] = {};
4792                  }
4793              }
4794  
4795              // Setup the specified scope or use the target as a default
4796              scope = scope || target;
4797  
4798              // Split names and bind each event, enables you to bind multiple events with one call
4799              names = names.split(' ');
4800              i = names.length;
4801              while (i--) {
4802                  name = names[i];
4803                  nativeHandler = defaultNativeHandler;
4804                  fakeName = capture = false;
4805  
4806                  // Use ready instead of DOMContentLoaded
4807                  if (name === "DOMContentLoaded") {
4808                      name = "ready";
4809                  }
4810  
4811                  // DOM is already ready
4812                  if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4813                      self.domLoaded = true;
4814                      callback.call(scope, fix({type: name}));
4815                      continue;
4816                  }
4817  
4818                  // Handle mouseenter/mouseleaver
4819                  if (!hasMouseEnterLeave) {
4820                      fakeName = mouseEnterLeave[name];
4821  
4822                      if (fakeName) {
4823                          nativeHandler = function(evt) {
4824                              var current, related;
4825  
4826                              current = evt.currentTarget;
4827                              related = evt.relatedTarget;
4828  
4829                              // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4830                              if (related && current.contains) {
4831                                  // Use contains for performance
4832                                  related = current.contains(related);
4833                              } else {
4834                                  while (related && related !== current) {
4835                                      related = related.parentNode;
4836                                  }
4837                              }
4838  
4839                              // Fire fake event
4840                              if (!related) {
4841                                  evt = fix(evt || win.event);
4842                                  evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4843                                  evt.target = current;
4844                                  executeHandlers(evt, id);
4845                              }
4846                          };
4847                      }
4848                  }
4849  
4850                  // Fake bubbeling of focusin/focusout
4851                  if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4852                      capture = true;
4853                      fakeName = name === "focusin" ? "focus" : "blur";
4854                      nativeHandler = function(evt) {
4855                          evt = fix(evt || win.event);
4856                          evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4857                          executeHandlers(evt, id);
4858                      };
4859                  }
4860  
4861                  // Setup callback list and bind native event
4862                  callbackList = events[id][name];
4863                  if (!callbackList) {
4864                      events[id][name] = callbackList = [{func: callback, scope: scope}];
4865                      callbackList.fakeName = fakeName;
4866                      callbackList.capture = capture;
4867  
4868                      // Add the nativeHandler to the callback list so that we can later unbind it
4869                      callbackList.nativeHandler = nativeHandler;
4870                      if (!w3cEventModel) {
4871                          callbackList.proxyHandler = proxy(id);
4872                      }
4873  
4874                      // Check if the target has native events support
4875                      if (name === "ready") {
4876                          bindOnReady(target, nativeHandler, self);
4877                      } else {
4878                          addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4879                      }
4880                  } else {
4881                      // If it already has an native handler then just push the callback
4882                      callbackList.push({func: callback, scope: scope});
4883                  }
4884              }
4885  
4886              target = callbackList = 0; // Clean memory for IE
4887  
4888              return callback;
4889          };
4890  
4891          self.unbind = function(target, names, callback) {
4892              var id, callbackList, i, ci, name, eventMap;
4893  
4894              // Don't bind to text nodes or comments
4895              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4896                  return self;
4897              }
4898  
4899              // Unbind event or events if the target has the expando
4900              id = target[expando];
4901              if (id) {
4902                  eventMap = events[id];
4903  
4904                  // Specific callback
4905                  if (names) {
4906                      names = names.split(' ');
4907                      i = names.length;
4908                      while (i--) {
4909                          name = names[i];
4910                          callbackList = eventMap[name];
4911  
4912                          // Unbind the event if it exists in the map
4913                          if (callbackList) {
4914                              // Remove specified callback
4915                              if (callback) {
4916                                  ci = callbackList.length;
4917                                  while (ci--) {
4918                                      if (callbackList[ci].func === callback) {
4919                                          callbackList.splice(ci, 1);
4920                                      }
4921                                  }
4922                              }
4923  
4924                              // Remove all callbacks if there isn't a specified callback or there is no callbacks left
4925                              if (!callback || callbackList.length === 0) {
4926                                  delete eventMap[name];
4927                                  removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4928                              }
4929                          }
4930                      }
4931                  } else {
4932                      // All events for a specific element
4933                      for (name in eventMap) {
4934                          callbackList = eventMap[name];
4935                          removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4936                      }
4937  
4938                      eventMap = {};
4939                  }
4940  
4941                  // Check if object is empty, if it isn't then we won't remove the expando map
4942                  for (name in eventMap) {
4943                      return self;
4944                  }
4945  
4946                  // Delete event object
4947                  delete events[id];
4948  
4949                  // Remove expando from target
4950                  try {
4951                      // IE will fail here since it can't delete properties from window
4952                      delete target[expando];
4953                  } catch (ex) {
4954                      // IE will set it to null
4955                      target[expando] = null;
4956                  }
4957              }
4958  
4959              return self;
4960          };
4961  
4962          self.fire = function(target, name, args) {
4963              var id, event;
4964  
4965              // Don't bind to text nodes or comments
4966              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4967                  return self;
4968              }
4969  
4970              // Build event object by patching the args
4971              event = fix(null, args);
4972              event.type = name;
4973  
4974              do {
4975                  // Found an expando that means there is listeners to execute
4976                  id = target[expando];
4977                  if (id) {
4978                      executeHandlers(event, id);
4979                  }
4980  
4981                  // Walk up the DOM
4982                  target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4983              } while (target && !event.isPropagationStopped());
4984  
4985              return self;
4986          };
4987  
4988          self.clean = function(target) {
4989              var i, children, unbind = self.unbind;
4990      
4991              // Don't bind to text nodes or comments
4992              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4993                  return self;
4994              }
4995  
4996              // Unbind any element on the specificed target
4997              if (target[expando]) {
4998                  unbind(target);
4999              }
5000  
5001              // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
5002              if (!target.getElementsByTagName) {
5003                  target = target.document;
5004              }
5005  
5006              // Remove events from each child element
5007              if (target && target.getElementsByTagName) {
5008                  unbind(target);
5009  
5010                  children = target.getElementsByTagName('*');
5011                  i = children.length;
5012                  while (i--) {
5013                      target = children[i];
5014  
5015                      if (target[expando]) {
5016                          unbind(target);
5017                      }
5018                  }
5019              }
5020  
5021              return self;
5022          };
5023  
5024          self.callNativeHandler = function(id, evt) {
5025              if (events) {
5026                  events[id][evt.type].nativeHandler(evt);
5027              }
5028          };
5029  
5030          self.destory = function() {
5031              events = {};
5032          };
5033  
5034          // Legacy function calls
5035  
5036          self.add = function(target, events, func, scope) {
5037              // Old API supported direct ID assignment
5038              if (typeof(target) === "string") {
5039                  target = document.getElementById(target);
5040              }
5041  
5042              // Old API supported multiple targets
5043              if (target && target instanceof Array) {
5044                  var i = target.length;
5045  
5046                  while (i--) {
5047                      self.add(target[i], events, func, scope);
5048                  }
5049  
5050                  return;
5051              }
5052  
5053              // Old API called ready init
5054              if (events === "init") {
5055                  events = "ready";
5056              }
5057  
5058              return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5059          };
5060  
5061          self.remove = function(target, events, func, scope) {
5062              if (!target) {
5063                  return self;
5064              }
5065  
5066              // Old API supported direct ID assignment
5067              if (typeof(target) === "string") {
5068                  target = document.getElementById(target);
5069              }
5070  
5071              // Old API supported multiple targets
5072              if (target instanceof Array) {
5073                  var i = target.length;
5074  
5075                  while (i--) {
5076                      self.remove(target[i], events, func, scope);
5077                  }
5078  
5079                  return self;
5080              }
5081  
5082              return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5083          };
5084  
5085          self.clear = function(target) {
5086              // Old API supported direct ID assignment
5087              if (typeof(target) === "string") {
5088                  target = document.getElementById(target);
5089              }
5090  
5091              return self.clean(target);
5092          };
5093  
5094          self.cancel = function(e) {
5095              if (e) {
5096                  self.prevent(e);
5097                  self.stop(e);
5098              }
5099  
5100              return false;
5101          };
5102  
5103          self.prevent = function(e) {
5104              if (!e.preventDefault) {
5105                  e = fix(e);
5106              }
5107  
5108              e.preventDefault();
5109  
5110              return false;
5111          };
5112  
5113          self.stop = function(e) {
5114              if (!e.stopPropagation) {
5115                  e = fix(e);
5116              }
5117  
5118              e.stopPropagation();
5119  
5120              return false;
5121          };
5122      }
5123  
5124      namespace.EventUtils = EventUtils;
5125  
5126      namespace.Event = new EventUtils(function(id) {
5127          return function(evt) {
5128              tinymce.dom.Event.callNativeHandler(id, evt);
5129          };
5130      });
5131  
5132      // Bind ready event when tinymce script is loaded
5133      namespace.Event.bind(window, 'ready', function() {});
5134  
5135      namespace = 0;
5136  })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5137  tinymce.dom.TreeWalker = function(start_node, root_node) {
5138      var node = start_node;
5139  
5140  	function findSibling(node, start_name, sibling_name, shallow) {
5141          var sibling, parent;
5142  
5143          if (node) {
5144              // Walk into nodes if it has a start
5145              if (!shallow && node[start_name])
5146                  return node[start_name];
5147  
5148              // Return the sibling if it has one
5149              if (node != root_node) {
5150                  sibling = node[sibling_name];
5151                  if (sibling)
5152                      return sibling;
5153  
5154                  // Walk up the parents to look for siblings
5155                  for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5156                      sibling = parent[sibling_name];
5157                      if (sibling)
5158                          return sibling;
5159                  }
5160              }
5161          }
5162      };
5163  
5164      this.current = function() {
5165          return node;
5166      };
5167  
5168      this.next = function(shallow) {
5169          return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5170      };
5171  
5172      this.prev = function(shallow) {
5173          return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5174      };
5175  };
5176  (function(tinymce) {
5177      // Shorten names
5178      var each = tinymce.each,
5179          is = tinymce.is,
5180          isWebKit = tinymce.isWebKit,
5181          isIE = tinymce.isIE,
5182          Entities = tinymce.html.Entities,
5183          simpleSelectorRe = /^([a-z0-9],?)+$/i,
5184          whiteSpaceRegExp = /^[ \t\r\n]*$/;
5185  
5186      tinymce.create('tinymce.dom.DOMUtils', {
5187          doc : null,
5188          root : null,
5189          files : null,
5190          pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5191          props : {
5192              "for" : "htmlFor",
5193              "class" : "className",
5194              className : "className",
5195              checked : "checked",
5196              disabled : "disabled",
5197              maxlength : "maxLength",
5198              readonly : "readOnly",
5199              selected : "selected",
5200              value : "value",
5201              id : "id",
5202              name : "name",
5203              type : "type"
5204          },
5205  
5206          DOMUtils : function(d, s) {
5207              var t = this, globalStyle, name, blockElementsMap;
5208  
5209              t.doc = d;
5210              t.win = window;
5211              t.files = {};
5212              t.cssFlicker = false;
5213              t.counter = 0;
5214              t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5215              t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5216              t.hasOuterHTML = "outerHTML" in d.createElement("a");
5217  
5218              t.settings = s = tinymce.extend({
5219                  keep_values : false,
5220                  hex_colors : 1
5221              }, s);
5222              
5223              t.schema = s.schema;
5224              t.styles = new tinymce.html.Styles({
5225                  url_converter : s.url_converter,
5226                  url_converter_scope : s.url_converter_scope
5227              }, s.schema);
5228  
5229              // Fix IE6SP2 flicker and check it failed for pre SP2
5230              if (tinymce.isIE6) {
5231                  try {
5232                      d.execCommand('BackgroundImageCache', false, true);
5233                  } catch (e) {
5234                      t.cssFlicker = true;
5235                  }
5236              }
5237  
5238              t.fixDoc(d);
5239              t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5240              tinymce.addUnload(t.destroy, t);
5241              blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5242  
5243              t.isBlock = function(node) {
5244                  // Fix for #5446
5245                  if (!node) {
5246                      return false;
5247                  }
5248  
5249                  // This function is called in module pattern style since it might be executed with the wrong this scope
5250                  var type = node.nodeType;
5251  
5252                  // If it's a node then check the type and use the nodeName
5253                  if (type)
5254                      return !!(type === 1 && blockElementsMap[node.nodeName]);
5255  
5256                  return !!blockElementsMap[node];
5257              };
5258          },
5259  
5260          fixDoc: function(doc) {
5261              var settings = this.settings, name;
5262  
5263              if (isIE && !tinymce.isIE11 && settings.schema) {
5264                  // Add missing HTML 4/5 elements to IE
5265                  ('abbr article aside audio canvas ' +
5266                  'details figcaption figure footer ' +
5267                  'header hgroup mark menu meter nav ' +
5268                  'output progress section summary ' +
5269                  'time video').replace(/\w+/g, function(name) {
5270                      doc.createElement(name);
5271                  });
5272  
5273                  // Create all custom elements
5274                  for (name in settings.schema.getCustomElements()) {
5275                      doc.createElement(name);
5276                  }
5277              }
5278          },
5279  
5280          clone: function(node, deep) {
5281              var self = this, clone, doc;
5282  
5283              // TODO: Add feature detection here in the future
5284              if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) {
5285                  return node.cloneNode(deep);
5286              }
5287  
5288              doc = self.doc;
5289  
5290              // Make a HTML5 safe shallow copy
5291              if (!deep) {
5292                  clone = doc.createElement(node.nodeName);
5293  
5294                  // Copy attribs
5295                  each(self.getAttribs(node), function(attr) {
5296                      self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5297                  });
5298  
5299                  return clone;
5300              }
5301  /*
5302              // Setup HTML5 patched document fragment
5303              if (!self.frag) {
5304                  self.frag = doc.createDocumentFragment();
5305                  self.fixDoc(self.frag);
5306              }
5307  
5308              // Make a deep copy by adding it to the document fragment then removing it this removed the :section
5309              clone = doc.createElement('div');
5310              self.frag.appendChild(clone);
5311              clone.innerHTML = node.outerHTML;
5312              self.frag.removeChild(clone);
5313  */
5314              return clone.firstChild;
5315          },
5316  
5317          getRoot : function() {
5318              var t = this, s = t.settings;
5319  
5320              return (s && t.get(s.root_element)) || t.doc.body;
5321          },
5322  
5323          getViewPort : function(w) {
5324              var d, b;
5325  
5326              w = !w ? this.win : w;
5327              d = w.document;
5328              b = this.boxModel ? d.documentElement : d.body;
5329  
5330              // Returns viewport size excluding scrollbars
5331              return {
5332                  x : w.pageXOffset || b.scrollLeft,
5333                  y : w.pageYOffset || b.scrollTop,
5334                  w : w.innerWidth || b.clientWidth,
5335                  h : w.innerHeight || b.clientHeight
5336              };
5337          },
5338  
5339          getRect : function(e) {
5340              var p, t = this, sr;
5341  
5342              e = t.get(e);
5343              p = t.getPos(e);
5344              sr = t.getSize(e);
5345  
5346              return {
5347                  x : p.x,
5348                  y : p.y,
5349                  w : sr.w,
5350                  h : sr.h
5351              };
5352          },
5353  
5354          getSize : function(e) {
5355              var t = this, w, h;
5356  
5357              e = t.get(e);
5358              w = t.getStyle(e, 'width');
5359              h = t.getStyle(e, 'height');
5360  
5361              // Non pixel value, then force offset/clientWidth
5362              if (w.indexOf('px') === -1)
5363                  w = 0;
5364  
5365              // Non pixel value, then force offset/clientWidth
5366              if (h.indexOf('px') === -1)
5367                  h = 0;
5368  
5369              return {
5370                  w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5371                  h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5372              };
5373          },
5374  
5375          getParent : function(n, f, r) {
5376              return this.getParents(n, f, r, false);
5377          },
5378  
5379          getParents : function(n, f, r, c) {
5380              var t = this, na, se = t.settings, o = [];
5381  
5382              n = t.get(n);
5383              c = c === undefined;
5384  
5385              if (se.strict_root)
5386                  r = r || t.getRoot();
5387  
5388              // Wrap node name as func
5389              if (is(f, 'string')) {
5390                  na = f;
5391  
5392                  if (f === '*') {
5393                      f = function(n) {return n.nodeType == 1;};
5394                  } else {
5395                      f = function(n) {
5396                          return t.is(n, na);
5397                      };
5398                  }
5399              }
5400  
5401              while (n) {
5402                  if (n == r || !n.nodeType || n.nodeType === 9)
5403                      break;
5404  
5405                  if (!f || f(n)) {
5406                      if (c)
5407                          o.push(n);
5408                      else
5409                          return n;
5410                  }
5411  
5412                  n = n.parentNode;
5413              }
5414  
5415              return c ? o : null;
5416          },
5417  
5418          get : function(e) {
5419              var n;
5420  
5421              if (e && this.doc && typeof(e) == 'string') {
5422                  n = e;
5423                  e = this.doc.getElementById(e);
5424  
5425                  // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5426                  if (e && e.id !== n)
5427                      return this.doc.getElementsByName(n)[1];
5428              }
5429  
5430              return e;
5431          },
5432  
5433          getNext : function(node, selector) {
5434              return this._findSib(node, selector, 'nextSibling');
5435          },
5436  
5437          getPrev : function(node, selector) {
5438              return this._findSib(node, selector, 'previousSibling');
5439          },
5440  
5441  
5442          select : function(pa, s) {
5443              var t = this;
5444  
5445              return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5446          },
5447  
5448          is : function(n, selector) {
5449              var i;
5450  
5451              // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5452              if (n.length === undefined) {
5453                  // Simple all selector
5454                  if (selector === '*')
5455                      return n.nodeType == 1;
5456  
5457                  // Simple selector just elements
5458                  if (simpleSelectorRe.test(selector)) {
5459                      selector = selector.toLowerCase().split(/,/);
5460                      n = n.nodeName.toLowerCase();
5461  
5462                      for (i = selector.length - 1; i >= 0; i--) {
5463                          if (selector[i] == n)
5464                              return true;
5465                      }
5466  
5467                      return false;
5468                  }
5469              }
5470  
5471              return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5472          },
5473  
5474  
5475          add : function(p, n, a, h, c) {
5476              var t = this;
5477  
5478              return this.run(p, function(p) {
5479                  var e, k;
5480  
5481                  e = is(n, 'string') ? t.doc.createElement(n) : n;
5482                  t.setAttribs(e, a);
5483  
5484                  if (h) {
5485                      if (h.nodeType)
5486                          e.appendChild(h);
5487                      else
5488                          t.setHTML(e, h);
5489                  }
5490  
5491                  return !c ? p.appendChild(e) : e;
5492              });
5493          },
5494  
5495          create : function(n, a, h) {
5496              return this.add(this.doc.createElement(n), n, a, h, 1);
5497          },
5498  
5499          createHTML : function(n, a, h) {
5500              var o = '', t = this, k;
5501  
5502              o += '<' + n;
5503  
5504              for (k in a) {
5505                  if (a.hasOwnProperty(k))
5506                      o += ' ' + k + '="' + t.encode(a[k]) + '"';
5507              }
5508  
5509              // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5510              if (typeof(h) != "undefined")
5511                  return o + '>' + h + '</' + n + '>';
5512  
5513              return o + ' />';
5514          },
5515  
5516          remove : function(node, keep_children) {
5517              return this.run(node, function(node) {
5518                  var child, parent = node.parentNode;
5519  
5520                  if (!parent)
5521                      return null;
5522  
5523                  if (keep_children) {
5524                      while (child = node.firstChild) {
5525                          // IE 8 will crash if you don't remove completely empty text nodes
5526                          if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5527                              parent.insertBefore(child, node);
5528                          else
5529                              node.removeChild(child);
5530                      }
5531                  }
5532  
5533                  return parent.removeChild(node);
5534              });
5535          },
5536  
5537          setStyle : function(n, na, v) {
5538              var t = this;
5539  
5540              return t.run(n, function(e) {
5541                  var s, i;
5542  
5543                  s = e.style;
5544  
5545                  // Camelcase it, if needed
5546                  na = na.replace(/-(\D)/g, function(a, b){
5547                      return b.toUpperCase();
5548                  });
5549  
5550                  // Default px suffix on these
5551                  if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5552                      v += 'px';
5553  
5554                  switch (na) {
5555                      case 'opacity':
5556                          // IE specific opacity
5557                          if (isIE && ! tinymce.isIE11) {
5558                              s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5559  
5560                              if (!n.currentStyle || !n.currentStyle.hasLayout)
5561                                  s.display = 'inline-block';
5562                          }
5563  
5564                          // Fix for older browsers
5565                          s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5566                          break;
5567  
5568                      case 'float':
5569                          (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v;
5570                          break;
5571                      
5572                      default:
5573                          s[na] = v || '';
5574                  }
5575  
5576                  // Force update of the style data
5577                  if (t.settings.update_styles)
5578                      t.setAttrib(e, 'data-mce-style');
5579              });
5580          },
5581  
5582          getStyle : function(n, na, c) {
5583              n = this.get(n);
5584  
5585              if (!n)
5586                  return;
5587  
5588              // Gecko
5589              if (this.doc.defaultView && c) {
5590                  // Remove camelcase
5591                  na = na.replace(/[A-Z]/g, function(a){
5592                      return '-' + a;
5593                  });
5594  
5595                  try {
5596                      return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5597                  } catch (ex) {
5598                      // Old safari might fail
5599                      return null;
5600                  }
5601              }
5602  
5603              // Camelcase it, if needed
5604              na = na.replace(/-(\D)/g, function(a, b){
5605                  return b.toUpperCase();
5606              });
5607  
5608              if (na == 'float')
5609                  na = isIE ? 'styleFloat' : 'cssFloat';
5610  
5611              // IE & Opera
5612              if (n.currentStyle && c)
5613                  return n.currentStyle[na];
5614  
5615              return n.style ? n.style[na] : undefined;
5616          },
5617  
5618          setStyles : function(e, o) {
5619              var t = this, s = t.settings, ol;
5620  
5621              ol = s.update_styles;
5622              s.update_styles = 0;
5623  
5624              each(o, function(v, n) {
5625                  t.setStyle(e, n, v);
5626              });
5627  
5628              // Update style info
5629              s.update_styles = ol;
5630              if (s.update_styles)
5631                  t.setAttrib(e, s.cssText);
5632          },
5633  
5634          removeAllAttribs: function(e) {
5635              return this.run(e, function(e) {
5636                  var i, attrs = e.attributes;
5637                  for (i = attrs.length - 1; i >= 0; i--) {
5638                      e.removeAttributeNode(attrs.item(i));
5639                  }
5640              });
5641          },
5642  
5643          setAttrib : function(e, n, v) {
5644              var t = this;
5645  
5646              // Whats the point
5647              if (!e || !n)
5648                  return;
5649  
5650              // Strict XML mode
5651              if (t.settings.strict)
5652                  n = n.toLowerCase();
5653  
5654              return this.run(e, function(e) {
5655                  var s = t.settings;
5656                  var originalValue = e.getAttribute(n);
5657                  if (v !== null) {
5658                      switch (n) {
5659                          case "style":
5660                              if (!is(v, 'string')) {
5661                                  each(v, function(v, n) {
5662                                      t.setStyle(e, n, v);
5663                                  });
5664  
5665                                  return;
5666                              }
5667  
5668                              // No mce_style for elements with these since they might get resized by the user
5669                              if (s.keep_values) {
5670                                  if (v && !t._isRes(v))
5671                                      e.setAttribute('data-mce-style', v, 2);
5672                                  else
5673                                      e.removeAttribute('data-mce-style', 2);
5674                              }
5675  
5676                              e.style.cssText = v;
5677                              break;
5678  
5679                          case "class":
5680                              e.className = v || ''; // Fix IE null bug
5681                              break;
5682  
5683                          case "src":
5684                          case "href":
5685                              if (s.keep_values) {
5686                                  if (s.url_converter)
5687                                      v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5688  
5689                                  t.setAttrib(e, 'data-mce-' + n, v, 2);
5690                              }
5691  
5692                              break;
5693  
5694                          case "shape":
5695                              e.setAttribute('data-mce-style', v);
5696                              break;
5697                      }
5698                  }
5699                  if (is(v) && v !== null && v.length !== 0)
5700                      e.setAttribute(n, '' + v, 2);
5701                  else
5702                      e.removeAttribute(n, 2);
5703  
5704                  // fire onChangeAttrib event for attributes that have changed
5705                  if (tinyMCE.activeEditor && originalValue != v) {
5706                      var ed = tinyMCE.activeEditor;
5707                      ed.onSetAttrib.dispatch(ed, e, n, v);
5708                  }
5709              });
5710          },
5711  
5712          setAttribs : function(e, o) {
5713              var t = this;
5714  
5715              return this.run(e, function(e) {
5716                  each(o, function(v, n) {
5717                      t.setAttrib(e, n, v);
5718                  });
5719              });
5720          },
5721  
5722          getAttrib : function(e, n, dv) {
5723              var v, t = this, undef;
5724  
5725              e = t.get(e);
5726  
5727              if (!e || e.nodeType !== 1)
5728                  return dv === undef ? false : dv;
5729  
5730              if (!is(dv))
5731                  dv = '';
5732  
5733              // Try the mce variant for these
5734              if (/^(src|href|style|coords|shape)$/.test(n)) {
5735                  v = e.getAttribute("data-mce-" + n);
5736  
5737                  if (v)
5738                      return v;
5739              }
5740  
5741              if (isIE && t.props[n]) {
5742                  v = e[t.props[n]];
5743                  v = v && v.nodeValue ? v.nodeValue : v;
5744              }
5745  
5746              if (!v)
5747                  v = e.getAttribute(n, 2);
5748  
5749              // Check boolean attribs
5750              if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5751                  if (e[t.props[n]] === true && v === '')
5752                      return n;
5753  
5754                  return v ? n : '';
5755              }
5756  
5757              // Inner input elements will override attributes on form elements
5758              if (e.nodeName === "FORM" && e.getAttributeNode(n))
5759                  return e.getAttributeNode(n).nodeValue;
5760  
5761              if (n === 'style') {
5762                  v = v || e.style.cssText;
5763  
5764                  if (v) {
5765                      v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5766  
5767                      if (t.settings.keep_values && !t._isRes(v))
5768                          e.setAttribute('data-mce-style', v);
5769                  }
5770              }
5771  
5772              // Remove Apple and WebKit stuff
5773              if (isWebKit && n === "class" && v)
5774                  v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5775  
5776              // Handle IE issues
5777              if (isIE) {
5778                  switch (n) {
5779                      case 'rowspan':
5780                      case 'colspan':
5781                          // IE returns 1 as default value
5782                          if (v === 1)
5783                              v = '';
5784  
5785                          break;
5786  
5787                      case 'size':
5788                          // IE returns +0 as default value for size
5789                          if (v === '+0' || v === 20 || v === 0)
5790                              v = '';
5791  
5792                          break;
5793  
5794                      case 'width':
5795                      case 'height':
5796                      case 'vspace':
5797                      case 'checked':
5798                      case 'disabled':
5799                      case 'readonly':
5800                          if (v === 0)
5801                              v = '';
5802  
5803                          break;
5804  
5805                      case 'hspace':
5806                          // IE returns -1 as default value
5807                          if (v === -1)
5808                              v = '';
5809  
5810                          break;
5811  
5812                      case 'maxlength':
5813                      case 'tabindex':
5814                          // IE returns default value
5815                          if (v === 32768 || v === 2147483647 || v === '32768')
5816                              v = '';
5817  
5818                          break;
5819  
5820                      case 'multiple':
5821                      case 'compact':
5822                      case 'noshade':
5823                      case 'nowrap':
5824                          if (v === 65535)
5825                              return n;
5826  
5827                          return dv;
5828  
5829                      case 'shape':
5830                          v = v.toLowerCase();
5831                          break;
5832  
5833                      default:
5834                          // IE has odd anonymous function for event attributes
5835                          if (n.indexOf('on') === 0 && v)
5836                              v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5837                  }
5838              }
5839  
5840              return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5841          },
5842  
5843          getPos : function(n, ro) {
5844              var t = this, x = 0, y = 0, e, d = t.doc, r;
5845  
5846              n = t.get(n);
5847              ro = ro || d.body;
5848  
5849              if (n) {
5850                  // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5851                  if (n.getBoundingClientRect) {
5852                      n = n.getBoundingClientRect();
5853                      e = t.boxModel ? d.documentElement : d.body;
5854  
5855                      // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5856                      // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5857                      x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5858                      y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5859  
5860                      return {x : x, y : y};
5861                  }
5862  
5863                  r = n;
5864                  while (r && r != ro && r.nodeType) {
5865                      x += r.offsetLeft || 0;
5866                      y += r.offsetTop || 0;
5867                      r = r.offsetParent;
5868                  }
5869  
5870                  r = n.parentNode;
5871                  while (r && r != ro && r.nodeType) {
5872                      x -= r.scrollLeft || 0;
5873                      y -= r.scrollTop || 0;
5874                      r = r.parentNode;
5875                  }
5876              }
5877  
5878              return {x : x, y : y};
5879          },
5880  
5881          parseStyle : function(st) {
5882              return this.styles.parse(st);
5883          },
5884  
5885          serializeStyle : function(o, name) {
5886              return this.styles.serialize(o, name);
5887          },
5888  
5889          addStyle: function(cssText) {
5890              var doc = this.doc, head;
5891  
5892              // Create style element if needed
5893              styleElm = doc.getElementById('mceDefaultStyles');
5894              if (!styleElm) {
5895                  styleElm = doc.createElement('style'),
5896                  styleElm.id = 'mceDefaultStyles';
5897                  styleElm.type = 'text/css';
5898  
5899                  head = doc.getElementsByTagName('head')[0];
5900                  if (head.firstChild) {
5901                      head.insertBefore(styleElm, head.firstChild);
5902                  } else {
5903                      head.appendChild(styleElm);
5904                  }
5905              }
5906  
5907              // Append style data to old or new style element
5908              if (styleElm.styleSheet) {
5909                  styleElm.styleSheet.cssText += cssText;
5910              } else {
5911                  styleElm.appendChild(doc.createTextNode(cssText));
5912              }
5913          },
5914  
5915          loadCSS : function(u) {
5916              var t = this, d = t.doc, head;
5917  
5918              if (!u)
5919                  u = '';
5920  
5921              head = d.getElementsByTagName('head')[0];
5922  
5923              each(u.split(','), function(u) {
5924                  var link;
5925  
5926                  if (t.files[u])
5927                      return;
5928  
5929                  t.files[u] = true;
5930                  link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5931  
5932                  // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5933                  // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5934                  // It's ugly but it seems to work fine.
5935                  if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) {
5936                      link.onload = function() {
5937                          if (d.recalc)
5938                              d.recalc();
5939  
5940                          link.onload = null;
5941                      };
5942                  }
5943  
5944                  head.appendChild(link);
5945              });
5946          },
5947  
5948          addClass : function(e, c) {
5949              return this.run(e, function(e) {
5950                  var o;
5951  
5952                  if (!c)
5953                      return 0;
5954  
5955                  if (this.hasClass(e, c))
5956                      return e.className;
5957  
5958                  o = this.removeClass(e, c);
5959  
5960                  return e.className = (o != '' ? (o + ' ') : '') + c;
5961              });
5962          },
5963  
5964          removeClass : function(e, c) {
5965              var t = this, re;
5966  
5967              return t.run(e, function(e) {
5968                  var v;
5969  
5970                  if (t.hasClass(e, c)) {
5971                      if (!re)
5972                          re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5973  
5974                      v = e.className.replace(re, ' ');
5975                      v = tinymce.trim(v != ' ' ? v : '');
5976  
5977                      e.className = v;
5978  
5979                      // Empty class attr
5980                      if (!v) {
5981                          e.removeAttribute('class');
5982                          e.removeAttribute('className');
5983                      }
5984  
5985                      return v;
5986                  }
5987  
5988                  return e.className;
5989              });
5990          },
5991  
5992          hasClass : function(n, c) {
5993              n = this.get(n);
5994  
5995              if (!n || !c)
5996                  return false;
5997  
5998              return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5999          },
6000  
6001          show : function(e) {
6002              return this.setStyle(e, 'display', 'block');
6003          },
6004  
6005          hide : function(e) {
6006              return this.setStyle(e, 'display', 'none');
6007          },
6008  
6009          isHidden : function(e) {
6010              e = this.get(e);
6011  
6012              return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
6013          },
6014  
6015          uniqueId : function(p) {
6016              return (!p ? 'mce_' : p) + (this.counter++);
6017          },
6018  
6019          setHTML : function(element, html) {
6020              var self = this;
6021  
6022              return self.run(element, function(element) {
6023                  if (isIE) {
6024                      // Remove all child nodes, IE keeps empty text nodes in DOM
6025                      while (element.firstChild)
6026                          element.removeChild(element.firstChild);
6027  
6028                      try {
6029                          // IE will remove comments from the beginning
6030                          // unless you padd the contents with something
6031                          element.innerHTML = '<br />' + html;
6032                          element.removeChild(element.firstChild);
6033                      } catch (ex) {
6034                          // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
6035                          // This seems to fix this problem
6036  
6037                          // Create new div with HTML contents and a BR infront to keep comments
6038                          var newElement = self.create('div');
6039                          newElement.innerHTML = '<br />' + html;
6040  
6041                          // Add all children from div to target
6042                          each (tinymce.grep(newElement.childNodes), function(node, i) {
6043                              // Skip br element
6044                              if (i && element.canHaveHTML)
6045                                  element.appendChild(node);
6046                          });
6047                      }
6048                  } else
6049                      element.innerHTML = html;
6050  
6051                  return html;
6052              });
6053          },
6054  
6055          getOuterHTML : function(elm) {
6056              var doc, self = this;
6057  
6058              elm = self.get(elm);
6059  
6060              if (!elm)
6061                  return null;
6062  
6063              if (elm.nodeType === 1 && self.hasOuterHTML)
6064                  return elm.outerHTML;
6065  
6066              doc = (elm.ownerDocument || self.doc).createElement("body");
6067              doc.appendChild(elm.cloneNode(true));
6068  
6069              return doc.innerHTML;
6070          },
6071  
6072          setOuterHTML : function(e, h, d) {
6073              var t = this;
6074  
6075  			function setHTML(e, h, d) {
6076                  var n, tp;
6077  
6078                  tp = d.createElement("body");
6079                  tp.innerHTML = h;
6080  
6081                  n = tp.lastChild;
6082                  while (n) {
6083                      t.insertAfter(n.cloneNode(true), e);
6084                      n = n.previousSibling;
6085                  }
6086  
6087                  t.remove(e);
6088              };
6089  
6090              return this.run(e, function(e) {
6091                  e = t.get(e);
6092  
6093                  // Only set HTML on elements
6094                  if (e.nodeType == 1) {
6095                      d = d || e.ownerDocument || t.doc;
6096  
6097                      if (isIE) {
6098                          try {
6099                              // Try outerHTML for IE it sometimes produces an unknown runtime error
6100                              if (isIE && e.nodeType == 1)
6101                                  e.outerHTML = h;
6102                              else
6103                                  setHTML(e, h, d);
6104                          } catch (ex) {
6105                              // Fix for unknown runtime error
6106                              setHTML(e, h, d);
6107                          }
6108                      } else
6109                          setHTML(e, h, d);
6110                  }
6111              });
6112          },
6113  
6114          decode : Entities.decode,
6115  
6116          encode : Entities.encodeAllRaw,
6117  
6118          insertAfter : function(node, reference_node) {
6119              reference_node = this.get(reference_node);
6120  
6121              return this.run(node, function(node) {
6122                  var parent, nextSibling;
6123  
6124                  parent = reference_node.parentNode;
6125                  nextSibling = reference_node.nextSibling;
6126  
6127                  if (nextSibling)
6128                      parent.insertBefore(node, nextSibling);
6129                  else
6130                      parent.appendChild(node);
6131  
6132                  return node;
6133              });
6134          },
6135  
6136          replace : function(n, o, k) {
6137              var t = this;
6138  
6139              if (is(o, 'array'))
6140                  n = n.cloneNode(true);
6141  
6142              return t.run(o, function(o) {
6143                  if (k) {
6144                      each(tinymce.grep(o.childNodes), function(c) {
6145                          n.appendChild(c);
6146                      });
6147                  }
6148  
6149                  return o.parentNode.replaceChild(n, o);
6150              });
6151          },
6152  
6153          rename : function(elm, name) {
6154              var t = this, newElm;
6155  
6156              if (elm.nodeName != name.toUpperCase()) {
6157                  // Rename block element
6158                  newElm = t.create(name);
6159  
6160                  // Copy attribs to new block
6161                  each(t.getAttribs(elm), function(attr_node) {
6162                      t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6163                  });
6164  
6165                  // Replace block
6166                  t.replace(newElm, elm, 1);
6167              }
6168  
6169              return newElm || elm;
6170          },
6171  
6172          findCommonAncestor : function(a, b) {
6173              var ps = a, pe;
6174  
6175              while (ps) {
6176                  pe = b;
6177  
6178                  while (pe && ps != pe)
6179                      pe = pe.parentNode;
6180  
6181                  if (ps == pe)
6182                      break;
6183  
6184                  ps = ps.parentNode;
6185              }
6186  
6187              if (!ps && a.ownerDocument)
6188                  return a.ownerDocument.documentElement;
6189  
6190              return ps;
6191          },
6192  
6193          toHex : function(s) {
6194              var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6195  
6196  			function hex(s) {
6197                  s = parseInt(s, 10).toString(16);
6198  
6199                  return s.length > 1 ? s : '0' + s; // 0 -> 00
6200              };
6201  
6202              if (c) {
6203                  s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6204  
6205                  return s;
6206              }
6207  
6208              return s;
6209          },
6210  
6211          getClasses : function() {
6212              var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6213  
6214              if (t.classes)
6215                  return t.classes;
6216  
6217  			function addClasses(s) {
6218                  // IE style imports
6219                  each(s.imports, function(r) {
6220                      addClasses(r);
6221                  });
6222  
6223                  each(s.cssRules || s.rules, function(r) {
6224                      // Real type or fake it on IE
6225                      switch (r.type || 1) {
6226                          // Rule
6227                          case 1:
6228                              if (r.selectorText) {
6229                                  each(r.selectorText.split(','), function(v) {
6230                                      v = v.replace(/^\s*|\s*$|^\s\./g, "");
6231  
6232                                      // Is internal or it doesn't contain a class
6233                                      if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6234                                          return;
6235  
6236                                      // Remove everything but class name
6237                                      ov = v;
6238                                      v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6239  
6240                                      // Filter classes
6241                                      if (f && !(v = f(v, ov)))
6242                                          return;
6243  
6244                                      if (!lo[v]) {
6245                                          cl.push({'class' : v});
6246                                          lo[v] = 1;
6247                                      }
6248                                  });
6249                              }
6250                              break;
6251  
6252                          // Import
6253                          case 3:
6254                              try {
6255                                  addClasses(r.styleSheet);
6256                              } catch (ex) {
6257                                  // Ignore
6258                              }
6259  
6260                              break;
6261                      }
6262                  });
6263              };
6264  
6265              try {
6266                  each(t.doc.styleSheets, addClasses);
6267              } catch (ex) {
6268                  // Ignore
6269              }
6270  
6271              if (cl.length > 0)
6272                  t.classes = cl;
6273  
6274              return cl;
6275          },
6276  
6277          run : function(e, f, s) {
6278              var t = this, o;
6279  
6280              if (t.doc && typeof(e) === 'string')
6281                  e = t.get(e);
6282  
6283              if (!e)
6284                  return false;
6285  
6286              s = s || this;
6287              if (!e.nodeType && (e.length || e.length === 0)) {
6288                  o = [];
6289  
6290                  each(e, function(e, i) {
6291                      if (e) {
6292                          if (typeof(e) == 'string')
6293                              e = t.doc.getElementById(e);
6294  
6295                          o.push(f.call(s, e, i));
6296                      }
6297                  });
6298  
6299                  return o;
6300              }
6301  
6302              return f.call(s, e);
6303          },
6304  
6305          getAttribs : function(n) {
6306              var o;
6307  
6308              n = this.get(n);
6309  
6310              if (!n)
6311                  return [];
6312  
6313              if (isIE) {
6314                  o = [];
6315  
6316                  // Object will throw exception in IE
6317                  if (n.nodeName == 'OBJECT')
6318                      return n.attributes;
6319  
6320                  // IE doesn't keep the selected attribute if you clone option elements
6321                  if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6322                      o.push({specified : 1, nodeName : 'selected'});
6323  
6324                  // It's crazy that this is faster in IE but it's because it returns all attributes all the time
6325                  n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6326                      o.push({specified : 1, nodeName : a});
6327                  });
6328  
6329                  return o;
6330              }
6331  
6332              return n.attributes;
6333          },
6334  
6335          isEmpty : function(node, elements) {
6336              var self = this, i, attributes, type, walker, name, brCount = 0;
6337  
6338              node = node.firstChild;
6339              if (node) {
6340                  walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6341                  elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6342  
6343                  do {
6344                      type = node.nodeType;
6345  
6346                      if (type === 1) {
6347                          // Ignore bogus elements
6348                          if (node.getAttribute('data-mce-bogus'))
6349                              continue;
6350  
6351                          // Keep empty elements like <img />
6352                          name = node.nodeName.toLowerCase();
6353                          if (elements && elements[name]) {
6354                              // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6355                              if (name === 'br') {
6356                                  brCount++;
6357                                  continue;
6358                              }
6359  
6360                              return false;
6361                          }
6362  
6363                          // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6364                          attributes = self.getAttribs(node);
6365                          i = node.attributes.length;
6366                          while (i--) {
6367                              name = node.attributes[i].nodeName;
6368                              if (name === "name" || name === 'data-mce-bookmark')
6369                                  return false;
6370                          }
6371                      }
6372  
6373                      // Keep comment nodes
6374                      if (type == 8)
6375                          return false;
6376  
6377                      // Keep non whitespace text nodes
6378                      if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6379                          return false;
6380                  } while (node = walker.next());
6381              }
6382  
6383              return brCount <= 1;
6384          },
6385  
6386          destroy : function(s) {
6387              var t = this;
6388  
6389              t.win = t.doc = t.root = t.events = t.frag = null;
6390  
6391              // Manual destroy then remove unload handler
6392              if (!s)
6393                  tinymce.removeUnload(t.destroy);
6394          },
6395  
6396          createRng : function() {
6397              var d = this.doc;
6398  
6399              return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6400          },
6401  
6402          nodeIndex : function(node, normalized) {
6403              var idx = 0, lastNodeType, lastNode, nodeType;
6404  
6405              if (node) {
6406                  for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6407                      nodeType = node.nodeType;
6408  
6409                      // Normalize text nodes
6410                      if (normalized && nodeType == 3) {
6411                          if (nodeType == lastNodeType || !node.nodeValue.length)
6412                              continue;
6413                      }
6414                      idx++;
6415                      lastNodeType = nodeType;
6416                  }
6417              }
6418  
6419              return idx;
6420          },
6421  
6422          split : function(pe, e, re) {
6423              var t = this, r = t.createRng(), bef, aft, pa;
6424  
6425              // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6426              // but we don't want that in our code since it serves no purpose for the end user
6427              // For example if this is chopped:
6428              //   <p>text 1<span><b>CHOP</b></span>text 2</p>
6429              // would produce:
6430              //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6431              // this function will then trim of empty edges and produce:
6432              //   <p>text 1</p><b>CHOP</b><p>text 2</p>
6433  			function trim(node) {
6434                  var i, children = node.childNodes, type = node.nodeType;
6435  
6436  				function surroundedBySpans(node) {
6437                      var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6438                      var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6439                      return previousIsSpan && nextIsSpan;
6440                  }
6441  
6442                  if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6443                      return;
6444  
6445                  for (i = children.length - 1; i >= 0; i--)
6446                      trim(children[i]);
6447  
6448                  if (type != 9) {
6449                      // Keep non whitespace text nodes
6450                      if (type == 3 && node.nodeValue.length > 0) {
6451                          // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6452                          // Also keep text nodes with only spaces if surrounded by spans.
6453                          // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6454                          var trimmedLength = tinymce.trim(node.nodeValue).length;
6455                          if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6456                              return;
6457                      } else if (type == 1) {
6458                          // If the only child is a bookmark then move it up
6459                          children = node.childNodes;
6460                          if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6461                              node.parentNode.insertBefore(children[0], node);
6462  
6463                          // Keep non empty elements or img, hr etc
6464                          if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6465                              return;
6466                      }
6467  
6468                      t.remove(node);
6469                  }
6470  
6471                  return node;
6472              };
6473  
6474              if (pe && e) {
6475                  // Get before chunk
6476                  r.setStart(pe.parentNode, t.nodeIndex(pe));
6477                  r.setEnd(e.parentNode, t.nodeIndex(e));
6478                  bef = r.extractContents();
6479  
6480                  // Get after chunk
6481                  r = t.createRng();
6482                  r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6483                  r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6484                  aft = r.extractContents();
6485  
6486                  // Insert before chunk
6487                  pa = pe.parentNode;
6488                  pa.insertBefore(trim(bef), pe);
6489  
6490                  // Insert middle chunk
6491                  if (re)
6492                  pa.replaceChild(re, e);
6493              else
6494                  pa.insertBefore(e, pe);
6495  
6496                  // Insert after chunk
6497                  pa.insertBefore(trim(aft), pe);
6498                  t.remove(pe);
6499  
6500                  return re || e;
6501              }
6502          },
6503  
6504          bind : function(target, name, func, scope) {
6505              return this.events.add(target, name, func, scope || this);
6506          },
6507  
6508          unbind : function(target, name, func) {
6509              return this.events.remove(target, name, func);
6510          },
6511  
6512          fire : function(target, name, evt) {
6513              return this.events.fire(target, name, evt);
6514          },
6515  
6516          // Returns the content editable state of a node
6517          getContentEditable: function(node) {
6518              var contentEditable;
6519  
6520              // Check type
6521              if (node.nodeType != 1) {
6522                  return null;
6523              }
6524  
6525              // Check for fake content editable
6526              contentEditable = node.getAttribute("data-mce-contenteditable");
6527              if (contentEditable && contentEditable !== "inherit") {
6528                  return contentEditable;
6529              }
6530  
6531              // Check for real content editable
6532              return node.contentEditable !== "inherit" ? node.contentEditable : null;
6533          },
6534  
6535  
6536          _findSib : function(node, selector, name) {
6537              var t = this, f = selector;
6538  
6539              if (node) {
6540                  // If expression make a function of it using is
6541                  if (is(f, 'string')) {
6542                      f = function(node) {
6543                          return t.is(node, selector);
6544                      };
6545                  }
6546  
6547                  // Loop all siblings
6548                  for (node = node[name]; node; node = node[name]) {
6549                      if (f(node))
6550                          return node;
6551                  }
6552              }
6553  
6554              return null;
6555          },
6556  
6557          _isRes : function(c) {
6558              // Is live resizble element
6559              return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6560          }
6561  
6562          /*
6563          walk : function(n, f, s) {
6564              var d = this.doc, w;
6565  
6566              if (d.createTreeWalker) {
6567                  w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6568  
6569                  while ((n = w.nextNode()) != null)
6570                      f.call(s || this, n);
6571              } else
6572                  tinymce.walk(n, f, 'childNodes', s);
6573          }
6574          */
6575  
6576          /*
6577          toRGB : function(s) {
6578              var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6579  
6580              if (c) {
6581                  // #FFF -> #FFFFFF
6582                  if (!is(c[3]))
6583                      c[3] = c[2] = c[1];
6584  
6585                  return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6586              }
6587  
6588              return s;
6589          }
6590          */
6591      });
6592  
6593      tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6594  })(tinymce);
6595  (function(ns) {
6596      // Range constructor
6597  	function Range(dom) {
6598          var t = this,
6599              doc = dom.doc,
6600              EXTRACT = 0,
6601              CLONE = 1,
6602              DELETE = 2,
6603              TRUE = true,
6604              FALSE = false,
6605              START_OFFSET = 'startOffset',
6606              START_CONTAINER = 'startContainer',
6607              END_CONTAINER = 'endContainer',
6608              END_OFFSET = 'endOffset',
6609              extend = tinymce.extend,
6610              nodeIndex = dom.nodeIndex;
6611  
6612          extend(t, {
6613              // Inital states
6614              startContainer : doc,
6615              startOffset : 0,
6616              endContainer : doc,
6617              endOffset : 0,
6618              collapsed : TRUE,
6619              commonAncestorContainer : doc,
6620  
6621              // Range constants
6622              START_TO_START : 0,
6623              START_TO_END : 1,
6624              END_TO_END : 2,
6625              END_TO_START : 3,
6626  
6627              // Public methods
6628              setStart : setStart,
6629              setEnd : setEnd,
6630              setStartBefore : setStartBefore,
6631              setStartAfter : setStartAfter,
6632              setEndBefore : setEndBefore,
6633              setEndAfter : setEndAfter,
6634              collapse : collapse,
6635              selectNode : selectNode,
6636              selectNodeContents : selectNodeContents,
6637              compareBoundaryPoints : compareBoundaryPoints,
6638              deleteContents : deleteContents,
6639              extractContents : extractContents,
6640              cloneContents : cloneContents,
6641              insertNode : insertNode,
6642              surroundContents : surroundContents,
6643              cloneRange : cloneRange,
6644              toStringIE : toStringIE
6645          });
6646  
6647  		function createDocumentFragment() {
6648              return doc.createDocumentFragment();
6649          };
6650  
6651  		function setStart(n, o) {
6652              _setEndPoint(TRUE, n, o);
6653          };
6654  
6655  		function setEnd(n, o) {
6656              _setEndPoint(FALSE, n, o);
6657          };
6658  
6659  		function setStartBefore(n) {
6660              setStart(n.parentNode, nodeIndex(n));
6661          };
6662  
6663  		function setStartAfter(n) {
6664              setStart(n.parentNode, nodeIndex(n) + 1);
6665          };
6666  
6667  		function setEndBefore(n) {
6668              setEnd(n.parentNode, nodeIndex(n));
6669          };
6670  
6671  		function setEndAfter(n) {
6672              setEnd(n.parentNode, nodeIndex(n) + 1);
6673          };
6674  
6675  		function collapse(ts) {
6676              if (ts) {
6677                  t[END_CONTAINER] = t[START_CONTAINER];
6678                  t[END_OFFSET] = t[START_OFFSET];
6679              } else {
6680                  t[START_CONTAINER] = t[END_CONTAINER];
6681                  t[START_OFFSET] = t[END_OFFSET];
6682              }
6683  
6684              t.collapsed = TRUE;
6685          };
6686  
6687  		function selectNode(n) {
6688              setStartBefore(n);
6689              setEndAfter(n);
6690          };
6691  
6692  		function selectNodeContents(n) {
6693              setStart(n, 0);
6694              setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6695          };
6696  
6697  		function compareBoundaryPoints(h, r) {
6698              var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6699              rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6700  
6701              // Check START_TO_START
6702              if (h === 0)
6703                  return _compareBoundaryPoints(sc, so, rsc, rso);
6704      
6705              // Check START_TO_END
6706              if (h === 1)
6707                  return _compareBoundaryPoints(ec, eo, rsc, rso);
6708      
6709              // Check END_TO_END
6710              if (h === 2)
6711                  return _compareBoundaryPoints(ec, eo, rec, reo);
6712      
6713              // Check END_TO_START
6714              if (h === 3) 
6715                  return _compareBoundaryPoints(sc, so, rec, reo);
6716          };
6717  
6718  		function deleteContents() {
6719              _traverse(DELETE);
6720          };
6721  
6722  		function extractContents() {
6723              return _traverse(EXTRACT);
6724          };
6725  
6726  		function cloneContents() {
6727              return _traverse(CLONE);
6728          };
6729  
6730  		function insertNode(n) {
6731              var startContainer = this[START_CONTAINER],
6732                  startOffset = this[START_OFFSET], nn, o;
6733  
6734              // Node is TEXT_NODE or CDATA
6735              if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6736                  if (!startOffset) {
6737                      // At the start of text
6738                      startContainer.parentNode.insertBefore(n, startContainer);
6739                  } else if (startOffset >= startContainer.nodeValue.length) {
6740                      // At the end of text
6741                      dom.insertAfter(n, startContainer);
6742                  } else {
6743                      // Middle, need to split
6744                      nn = startContainer.splitText(startOffset);
6745                      startContainer.parentNode.insertBefore(n, nn);
6746                  }
6747              } else {
6748                  // Insert element node
6749                  if (startContainer.childNodes.length > 0)
6750                      o = startContainer.childNodes[startOffset];
6751  
6752                  if (o)
6753                      startContainer.insertBefore(n, o);
6754                  else
6755                      startContainer.appendChild(n);
6756              }
6757          };
6758  
6759  		function surroundContents(n) {
6760              var f = t.extractContents();
6761  
6762              t.insertNode(n);
6763              n.appendChild(f);
6764              t.selectNode(n);
6765          };
6766  
6767  		function cloneRange() {
6768              return extend(new Range(dom), {
6769                  startContainer : t[START_CONTAINER],
6770                  startOffset : t[START_OFFSET],
6771                  endContainer : t[END_CONTAINER],
6772                  endOffset : t[END_OFFSET],
6773                  collapsed : t.collapsed,
6774                  commonAncestorContainer : t.commonAncestorContainer
6775              });
6776          };
6777  
6778          // Private methods
6779  
6780  		function _getSelectedNode(container, offset) {
6781              var child;
6782  
6783              if (container.nodeType == 3 /* TEXT_NODE */)
6784                  return container;
6785  
6786              if (offset < 0)
6787                  return container;
6788  
6789              child = container.firstChild;
6790              while (child && offset > 0) {
6791                  --offset;
6792                  child = child.nextSibling;
6793              }
6794  
6795              if (child)
6796                  return child;
6797  
6798              return container;
6799          };
6800  
6801  		function _isCollapsed() {
6802              return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6803          };
6804  
6805  		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6806              var c, offsetC, n, cmnRoot, childA, childB;
6807              
6808              // In the first case the boundary-points have the same container. A is before B
6809              // if its offset is less than the offset of B, A is equal to B if its offset is
6810              // equal to the offset of B, and A is after B if its offset is greater than the
6811              // offset of B.
6812              if (containerA == containerB) {
6813                  if (offsetA == offsetB)
6814                      return 0; // equal
6815  
6816                  if (offsetA < offsetB)
6817                      return -1; // before
6818  
6819                  return 1; // after
6820              }
6821  
6822              // In the second case a child node C of the container of A is an ancestor
6823              // container of B. In this case, A is before B if the offset of A is less than or
6824              // equal to the index of the child node C and A is after B otherwise.
6825              c = containerB;
6826              while (c && c.parentNode != containerA)
6827                  c = c.parentNode;
6828  
6829              if (c) {
6830                  offsetC = 0;
6831                  n = containerA.firstChild;
6832  
6833                  while (n != c && offsetC < offsetA) {
6834                      offsetC++;
6835                      n = n.nextSibling;
6836                  }
6837  
6838                  if (offsetA <= offsetC)
6839                      return -1; // before
6840  
6841                  return 1; // after
6842              }
6843  
6844              // In the third case a child node C of the container of B is an ancestor container
6845              // of A. In this case, A is before B if the index of the child node C is less than
6846              // the offset of B and A is after B otherwise.
6847              c = containerA;
6848              while (c && c.parentNode != containerB) {
6849                  c = c.parentNode;
6850              }
6851  
6852              if (c) {
6853                  offsetC = 0;
6854                  n = containerB.firstChild;
6855  
6856                  while (n != c && offsetC < offsetB) {
6857                      offsetC++;
6858                      n = n.nextSibling;
6859                  }
6860  
6861                  if (offsetC < offsetB)
6862                      return -1; // before
6863  
6864                  return 1; // after
6865              }
6866  
6867              // In the fourth case, none of three other cases hold: the containers of A and B
6868              // are siblings or descendants of sibling nodes. In this case, A is before B if
6869              // the container of A is before the container of B in a pre-order traversal of the
6870              // Ranges' context tree and A is after B otherwise.
6871              cmnRoot = dom.findCommonAncestor(containerA, containerB);
6872              childA = containerA;
6873  
6874              while (childA && childA.parentNode != cmnRoot)
6875                  childA = childA.parentNode;
6876  
6877              if (!childA)
6878                  childA = cmnRoot;
6879  
6880              childB = containerB;
6881              while (childB && childB.parentNode != cmnRoot)
6882                  childB = childB.parentNode;
6883  
6884              if (!childB)
6885                  childB = cmnRoot;
6886  
6887              if (childA == childB)
6888                  return 0; // equal
6889  
6890              n = cmnRoot.firstChild;
6891              while (n) {
6892                  if (n == childA)
6893                      return -1; // before
6894  
6895                  if (n == childB)
6896                      return 1; // after
6897  
6898                  n = n.nextSibling;
6899              }
6900          };
6901  
6902  		function _setEndPoint(st, n, o) {
6903              var ec, sc;
6904  
6905              if (st) {
6906                  t[START_CONTAINER] = n;
6907                  t[START_OFFSET] = o;
6908              } else {
6909                  t[END_CONTAINER] = n;
6910                  t[END_OFFSET] = o;
6911              }
6912  
6913              // If one boundary-point of a Range is set to have a root container
6914              // other than the current one for the Range, the Range is collapsed to
6915              // the new position. This enforces the restriction that both boundary-
6916              // points of a Range must have the same root container.
6917              ec = t[END_CONTAINER];
6918              while (ec.parentNode)
6919                  ec = ec.parentNode;
6920  
6921              sc = t[START_CONTAINER];
6922              while (sc.parentNode)
6923                  sc = sc.parentNode;
6924  
6925              if (sc == ec) {
6926                  // The start position of a Range is guaranteed to never be after the
6927                  // end position. To enforce this restriction, if the start is set to
6928                  // be at a position after the end, the Range is collapsed to that
6929                  // position.
6930                  if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6931                      t.collapse(st);
6932              } else
6933                  t.collapse(st);
6934  
6935              t.collapsed = _isCollapsed();
6936              t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6937          };
6938  
6939  		function _traverse(how) {
6940              var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6941  
6942              if (t[START_CONTAINER] == t[END_CONTAINER])
6943                  return _traverseSameContainer(how);
6944  
6945              for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6946                  if (p == t[START_CONTAINER])
6947                      return _traverseCommonStartContainer(c, how);
6948  
6949                  ++endContainerDepth;
6950              }
6951  
6952              for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6953                  if (p == t[END_CONTAINER])
6954                      return _traverseCommonEndContainer(c, how);
6955  
6956                  ++startContainerDepth;
6957              }
6958  
6959              depthDiff = startContainerDepth - endContainerDepth;
6960  
6961              startNode = t[START_CONTAINER];
6962              while (depthDiff > 0) {
6963                  startNode = startNode.parentNode;
6964                  depthDiff--;
6965              }
6966  
6967              endNode = t[END_CONTAINER];
6968              while (depthDiff < 0) {
6969                  endNode = endNode.parentNode;
6970                  depthDiff++;
6971              }
6972  
6973              // ascend the ancestor hierarchy until we have a common parent.
6974              for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6975                  startNode = sp;
6976                  endNode = ep;
6977              }
6978  
6979              return _traverseCommonAncestors(startNode, endNode, how);
6980          };
6981  
6982  		 function _traverseSameContainer(how) {
6983              var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6984  
6985              if (how != DELETE)
6986                  frag = createDocumentFragment();
6987  
6988              // If selection is empty, just return the fragment
6989              if (t[START_OFFSET] == t[END_OFFSET])
6990                  return frag;
6991  
6992              // Text node needs special case handling
6993              if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6994                  // get the substring
6995                  s = t[START_CONTAINER].nodeValue;
6996                  sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6997  
6998                  // set the original text node to its new value
6999                  if (how != CLONE) {
7000                      n = t[START_CONTAINER];
7001                      start = t[START_OFFSET];
7002                      len = t[END_OFFSET] - t[START_OFFSET];
7003  
7004                      if (start === 0 && len >= n.nodeValue.length - 1) {
7005                          n.parentNode.removeChild(n);
7006                      } else {
7007                          n.deleteData(start, len);
7008                      }
7009  
7010                      // Nothing is partially selected, so collapse to start point
7011                      t.collapse(TRUE);
7012                  }
7013  
7014                  if (how == DELETE)
7015                      return;
7016  
7017                  if (sub.length > 0) {
7018                      frag.appendChild(doc.createTextNode(sub));
7019                  }
7020  
7021                  return frag;
7022              }
7023  
7024              // Copy nodes between the start/end offsets.
7025              n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
7026              cnt = t[END_OFFSET] - t[START_OFFSET];
7027  
7028              while (n && cnt > 0) {
7029                  sibling = n.nextSibling;
7030                  xferNode = _traverseFullySelected(n, how);
7031  
7032                  if (frag)
7033                      frag.appendChild( xferNode );
7034  
7035                  --cnt;
7036                  n = sibling;
7037              }
7038  
7039              // Nothing is partially selected, so collapse to start point
7040              if (how != CLONE)
7041                  t.collapse(TRUE);
7042  
7043              return frag;
7044          };
7045  
7046  		function _traverseCommonStartContainer(endAncestor, how) {
7047              var frag, n, endIdx, cnt, sibling, xferNode;
7048  
7049              if (how != DELETE)
7050                  frag = createDocumentFragment();
7051  
7052              n = _traverseRightBoundary(endAncestor, how);
7053  
7054              if (frag)
7055                  frag.appendChild(n);
7056  
7057              endIdx = nodeIndex(endAncestor);
7058              cnt = endIdx - t[START_OFFSET];
7059  
7060              if (cnt <= 0) {
7061                  // Collapse to just before the endAncestor, which
7062                  // is partially selected.
7063                  if (how != CLONE) {
7064                      t.setEndBefore(endAncestor);
7065                      t.collapse(FALSE);
7066                  }
7067  
7068                  return frag;
7069              }
7070  
7071              n = endAncestor.previousSibling;
7072              while (cnt > 0) {
7073                  sibling = n.previousSibling;
7074                  xferNode = _traverseFullySelected(n, how);
7075  
7076                  if (frag)
7077                      frag.insertBefore(xferNode, frag.firstChild);
7078  
7079                  --cnt;
7080                  n = sibling;
7081              }
7082  
7083              // Collapse to just before the endAncestor, which
7084              // is partially selected.
7085              if (how != CLONE) {
7086                  t.setEndBefore(endAncestor);
7087                  t.collapse(FALSE);
7088              }
7089  
7090              return frag;
7091          };
7092  
7093  		function _traverseCommonEndContainer(startAncestor, how) {
7094              var frag, startIdx, n, cnt, sibling, xferNode;
7095  
7096              if (how != DELETE)
7097                  frag = createDocumentFragment();
7098  
7099              n = _traverseLeftBoundary(startAncestor, how);
7100              if (frag)
7101                  frag.appendChild(n);
7102  
7103              startIdx = nodeIndex(startAncestor);
7104              ++startIdx; // Because we already traversed it
7105  
7106              cnt = t[END_OFFSET] - startIdx;
7107              n = startAncestor.nextSibling;
7108              while (n && cnt > 0) {
7109                  sibling = n.nextSibling;
7110                  xferNode = _traverseFullySelected(n, how);
7111  
7112                  if (frag)
7113                      frag.appendChild(xferNode);
7114  
7115                  --cnt;
7116                  n = sibling;
7117              }
7118  
7119              if (how != CLONE) {
7120                  t.setStartAfter(startAncestor);
7121                  t.collapse(TRUE);
7122              }
7123  
7124              return frag;
7125          };
7126  
7127  		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7128              var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7129  
7130              if (how != DELETE)
7131                  frag = createDocumentFragment();
7132  
7133              n = _traverseLeftBoundary(startAncestor, how);
7134              if (frag)
7135                  frag.appendChild(n);
7136  
7137              commonParent = startAncestor.parentNode;
7138              startOffset = nodeIndex(startAncestor);
7139              endOffset = nodeIndex(endAncestor);
7140              ++startOffset;
7141  
7142              cnt = endOffset - startOffset;
7143              sibling = startAncestor.nextSibling;
7144  
7145              while (cnt > 0) {
7146                  nextSibling = sibling.nextSibling;
7147                  n = _traverseFullySelected(sibling, how);
7148  
7149                  if (frag)
7150                      frag.appendChild(n);
7151  
7152                  sibling = nextSibling;
7153                  --cnt;
7154              }
7155  
7156              n = _traverseRightBoundary(endAncestor, how);
7157  
7158              if (frag)
7159                  frag.appendChild(n);
7160  
7161              if (how != CLONE) {
7162                  t.setStartAfter(startAncestor);
7163                  t.collapse(TRUE);
7164              }
7165  
7166              return frag;
7167          };
7168  
7169  		function _traverseRightBoundary(root, how) {
7170              var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7171  
7172              if (next == root)
7173                  return _traverseNode(next, isFullySelected, FALSE, how);
7174  
7175              parent = next.parentNode;
7176              clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7177  
7178              while (parent) {
7179                  while (next) {
7180                      prevSibling = next.previousSibling;
7181                      clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7182  
7183                      if (how != DELETE)
7184                          clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7185  
7186                      isFullySelected = TRUE;
7187                      next = prevSibling;
7188                  }
7189  
7190                  if (parent == root)
7191                      return clonedParent;
7192  
7193                  next = parent.previousSibling;
7194                  parent = parent.parentNode;
7195  
7196                  clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7197  
7198                  if (how != DELETE)
7199                      clonedGrandParent.appendChild(clonedParent);
7200  
7201                  clonedParent = clonedGrandParent;
7202              }
7203          };
7204  
7205  		function _traverseLeftBoundary(root, how) {
7206              var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7207  
7208              if (next == root)
7209                  return _traverseNode(next, isFullySelected, TRUE, how);
7210  
7211              parent = next.parentNode;
7212              clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7213  
7214              while (parent) {
7215                  while (next) {
7216                      nextSibling = next.nextSibling;
7217                      clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7218  
7219                      if (how != DELETE)
7220                          clonedParent.appendChild(clonedChild);
7221  
7222                      isFullySelected = TRUE;
7223                      next = nextSibling;
7224                  }
7225  
7226                  if (parent == root)
7227                      return clonedParent;
7228  
7229                  next = parent.nextSibling;
7230                  parent = parent.parentNode;
7231  
7232                  clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7233  
7234                  if (how != DELETE)
7235                      clonedGrandParent.appendChild(clonedParent);
7236  
7237                  clonedParent = clonedGrandParent;
7238              }
7239          };
7240  
7241  		function _traverseNode(n, isFullySelected, isLeft, how) {
7242              var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7243  
7244              if (isFullySelected)
7245                  return _traverseFullySelected(n, how);
7246  
7247              if (n.nodeType == 3 /* TEXT_NODE */) {
7248                  txtValue = n.nodeValue;
7249  
7250                  if (isLeft) {
7251                      offset = t[START_OFFSET];
7252                      newNodeValue = txtValue.substring(offset);
7253                      oldNodeValue = txtValue.substring(0, offset);
7254                  } else {
7255                      offset = t[END_OFFSET];
7256                      newNodeValue = txtValue.substring(0, offset);
7257                      oldNodeValue = txtValue.substring(offset);
7258                  }
7259  
7260                  if (how != CLONE)
7261                      n.nodeValue = oldNodeValue;
7262  
7263                  if (how == DELETE)
7264                      return;
7265  
7266                  newNode = dom.clone(n, FALSE);
7267                  newNode.nodeValue = newNodeValue;
7268  
7269                  return newNode;
7270              }
7271  
7272              if (how == DELETE)
7273                  return;
7274  
7275              return dom.clone(n, FALSE);
7276          };
7277  
7278  		function _traverseFullySelected(n, how) {
7279              if (how != DELETE)
7280                  return how == CLONE ? dom.clone(n, TRUE) : n;
7281  
7282              n.parentNode.removeChild(n);
7283          };
7284  
7285  		function toStringIE() {
7286              return dom.create('body', null, cloneContents()).outerText;
7287          }
7288          
7289          return t;
7290      };
7291  
7292      ns.Range = Range;
7293  
7294      // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7295      Range.prototype.toString = function() {
7296          return this.toStringIE();
7297      };
7298  })(tinymce.dom);
7299  (function() {
7300  	function Selection(selection) {
7301          var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7302  
7303  		function getPosition(rng, start) {
7304              var checkRng, startIndex = 0, endIndex, inside,
7305                  children, child, offset, index, position = -1, parent;
7306  
7307              // Setup test range, collapse it and get the parent
7308              checkRng = rng.duplicate();
7309              checkRng.collapse(start);
7310              parent = checkRng.parentElement();
7311  
7312              // Check if the selection is within the right document
7313              if (parent.ownerDocument !== selection.dom.doc)
7314                  return;
7315  
7316              // IE will report non editable elements as it's parent so look for an editable one
7317              while (parent.contentEditable === "false") {
7318                  parent = parent.parentNode;
7319              }
7320  
7321              // If parent doesn't have any children then return that we are inside the element
7322              if (!parent.hasChildNodes()) {
7323                  return {node : parent, inside : 1};
7324              }
7325  
7326              // Setup node list and endIndex
7327              children = parent.children;
7328              endIndex = children.length - 1;
7329  
7330              // Perform a binary search for the position
7331              while (startIndex <= endIndex) {
7332                  index = Math.floor((startIndex + endIndex) / 2);
7333  
7334                  // Move selection to node and compare the ranges
7335                  child = children[index];
7336                  checkRng.moveToElementText(child);
7337                  position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7338  
7339                  // Before/after or an exact match
7340                  if (position > 0) {
7341                      endIndex = index - 1;
7342                  } else if (position < 0) {
7343                      startIndex = index + 1;
7344                  } else {
7345                      return {node : child};
7346                  }
7347              }
7348  
7349              // Check if child position is before or we didn't find a position
7350              if (position < 0) {
7351                  // No element child was found use the parent element and the offset inside that
7352                  if (!child) {
7353                      checkRng.moveToElementText(parent);
7354                      checkRng.collapse(true);
7355                      child = parent;
7356                      inside = true;
7357                  } else
7358                      checkRng.collapse(false);
7359  
7360                  // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7361                  // We need to walk char by char since rng.text or rng.htmlText will trim line endings
7362                  offset = 0;
7363                  while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7364                      if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7365                          break;
7366                      }
7367  
7368                      offset++;
7369                  }
7370              } else {
7371                  // Child position is after the selection endpoint
7372                  checkRng.collapse(true);
7373  
7374                  // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7375                  offset = 0;
7376                  while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7377                      if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7378                          break;
7379                      }
7380  
7381                      offset++;
7382                  }
7383              }
7384  
7385              return {node : child, position : position, offset : offset, inside : inside};
7386          };
7387  
7388          // Returns a W3C DOM compatible range object by using the IE Range API
7389  		function getRange() {
7390              var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7391  
7392              // If selection is outside the current document just return an empty range
7393              element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7394              if (element.ownerDocument != dom.doc)
7395                  return domRange;
7396  
7397              collapsed = selection.isCollapsed();
7398  
7399              // Handle control selection
7400              if (ieRange.item) {
7401                  domRange.setStart(element.parentNode, dom.nodeIndex(element));
7402                  domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7403  
7404                  return domRange;
7405              }
7406  
7407  			function findEndPoint(start) {
7408                  var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7409  
7410                  container = endPoint.node;
7411                  offset = endPoint.offset;
7412  
7413                  if (endPoint.inside && !container.hasChildNodes()) {
7414                      domRange[start ? 'setStart' : 'setEnd'](container, 0);
7415                      return;
7416                  }
7417  
7418                  if (offset === undef) {
7419                      domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7420                      return;
7421                  }
7422  
7423                  if (endPoint.position < 0) {
7424                      sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7425  
7426                      if (!sibling) {
7427                          domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7428                          return;
7429                      }
7430  
7431                      if (!offset) {
7432                          if (sibling.nodeType == 3)
7433                              domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7434                          else
7435                              domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7436  
7437                          return;
7438                      }
7439  
7440                      // Find the text node and offset
7441                      while (sibling) {
7442                          nodeValue = sibling.nodeValue;
7443                          textNodeOffset += nodeValue.length;
7444  
7445                          // We are at or passed the position we where looking for
7446                          if (textNodeOffset >= offset) {
7447                              container = sibling;
7448                              textNodeOffset -= offset;
7449                              textNodeOffset = nodeValue.length - textNodeOffset;
7450                              break;
7451                          }
7452  
7453                          sibling = sibling.nextSibling;
7454                      }
7455                  } else {
7456                      // Find the text node and offset
7457                      sibling = container.previousSibling;
7458  
7459                      if (!sibling)
7460                          return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7461  
7462                      // If there isn't any text to loop then use the first position
7463                      if (!offset) {
7464                          if (container.nodeType == 3)
7465                              domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7466                          else
7467                              domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7468  
7469                          return;
7470                      }
7471  
7472                      while (sibling) {
7473                          textNodeOffset += sibling.nodeValue.length;
7474  
7475                          // We are at or passed the position we where looking for
7476                          if (textNodeOffset >= offset) {
7477                              container = sibling;
7478                              textNodeOffset -= offset;
7479                              break;
7480                          }
7481  
7482                          sibling = sibling.previousSibling;
7483                      }
7484                  }
7485  
7486                  domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7487              };
7488  
7489              try {
7490                  // Find start point
7491                  findEndPoint(true);
7492  
7493                  // Find end point if needed
7494                  if (!collapsed)
7495                      findEndPoint();
7496              } catch (ex) {
7497                  // IE has a nasty bug where text nodes might throw "invalid argument" when you
7498                  // access the nodeValue or other properties of text nodes. This seems to happend when
7499                  // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7500                  if (ex.number == -2147024809) {
7501                      // Get the current selection
7502                      bookmark = self.getBookmark(2);
7503  
7504                      // Get start element
7505                      tmpRange = ieRange.duplicate();
7506                      tmpRange.collapse(true);
7507                      element = tmpRange.parentElement();
7508  
7509                      // Get end element
7510                      if (!collapsed) {
7511                          tmpRange = ieRange.duplicate();
7512                          tmpRange.collapse(false);
7513                          element2 = tmpRange.parentElement();
7514                          element2.innerHTML = element2.innerHTML;
7515                      }
7516  
7517                      // Remove the broken elements
7518                      element.innerHTML = element.innerHTML;
7519  
7520                      // Restore the selection
7521                      self.moveToBookmark(bookmark);
7522  
7523                      // Since the range has moved we need to re-get it
7524                      ieRange = selection.getRng();
7525  
7526                      // Find start point
7527                      findEndPoint(true);
7528  
7529                      // Find end point if needed
7530                      if (!collapsed)
7531                          findEndPoint();
7532                  } else
7533                      throw ex; // Throw other errors
7534              }
7535  
7536              return domRange;
7537          };
7538  
7539          this.getBookmark = function(type) {
7540              var rng = selection.getRng(), start, end, bookmark = {};
7541  
7542  			function getIndexes(node) {
7543                  var parent, root, children, i, indexes = [];
7544  
7545                  parent = node.parentNode;
7546                  root = dom.getRoot().parentNode;
7547  
7548                  while (parent != root && parent.nodeType !== 9) {
7549                      children = parent.children;
7550  
7551                      i = children.length;
7552                      while (i--) {
7553                          if (node === children[i]) {
7554                              indexes.push(i);
7555                              break;
7556                          }
7557                      }
7558  
7559                      node = parent;
7560                      parent = parent.parentNode;
7561                  }
7562  
7563                  return indexes;
7564              };
7565  
7566  			function getBookmarkEndPoint(start) {
7567                  var position;
7568  
7569                  position = getPosition(rng, start);
7570                  if (position) {
7571                      return {
7572                          position : position.position,
7573                          offset : position.offset,
7574                          indexes : getIndexes(position.node),
7575                          inside : position.inside
7576                      };
7577                  }
7578              };
7579  
7580              // Non ubstructive bookmark
7581              if (type === 2) {
7582                  // Handle text selection
7583                  if (!rng.item) {
7584                      bookmark.start = getBookmarkEndPoint(true);
7585  
7586                      if (!selection.isCollapsed())
7587                          bookmark.end = getBookmarkEndPoint();
7588                  } else
7589                      bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7590              }
7591  
7592              return bookmark;
7593          };
7594  
7595          this.moveToBookmark = function(bookmark) {
7596              var rng, body = dom.doc.body;
7597  
7598  			function resolveIndexes(indexes) {
7599                  var node, i, idx, children;
7600  
7601                  node = dom.getRoot();
7602                  for (i = indexes.length - 1; i >= 0; i--) {
7603                      children = node.children;
7604                      idx = indexes[i];
7605  
7606                      if (idx <= children.length - 1) {
7607                          node = children[idx];
7608                      }
7609                  }
7610  
7611                  return node;
7612              };
7613              
7614  			function setBookmarkEndPoint(start) {
7615                  var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7616  
7617                  if (endPoint) {
7618                      moveLeft = endPoint.position > 0;
7619  
7620                      moveRng = body.createTextRange();
7621                      moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7622  
7623                      offset = endPoint.offset;
7624                      if (offset !== undef) {
7625                          moveRng.collapse(endPoint.inside || moveLeft);
7626                          moveRng.moveStart('character', moveLeft ? -offset : offset);
7627                      } else
7628                          moveRng.collapse(start);
7629  
7630                      rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7631  
7632                      if (start)
7633                          rng.collapse(true);
7634                  }
7635              };
7636  
7637              if (bookmark.start) {
7638                  if (bookmark.start.ctrl) {
7639                      rng = body.createControlRange();
7640                      rng.addElement(resolveIndexes(bookmark.start.indexes));
7641                      rng.select();
7642                  } else {
7643                      rng = body.createTextRange();
7644                      setBookmarkEndPoint(true);
7645                      setBookmarkEndPoint();
7646                      rng.select();
7647                  }
7648              }
7649          };
7650  
7651          this.addRange = function(rng) {
7652              var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
7653                  doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
7654  
7655  			function setEndPoint(start) {
7656                  var container, offset, marker, tmpRng, nodes;
7657  
7658                  marker = dom.create('a');
7659                  container = start ? startContainer : endContainer;
7660                  offset = start ? startOffset : endOffset;
7661                  tmpRng = ieRng.duplicate();
7662  
7663                  if (container == doc || container == doc.documentElement) {
7664                      container = body;
7665                      offset = 0;
7666                  }
7667  
7668                  if (container.nodeType == 3) {
7669                      container.parentNode.insertBefore(marker, container);
7670                      tmpRng.moveToElementText(marker);
7671                      tmpRng.moveStart('character', offset);
7672                      dom.remove(marker);
7673                      ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7674                  } else {
7675                      nodes = container.childNodes;
7676  
7677                      if (nodes.length) {
7678                          if (offset >= nodes.length) {
7679                              dom.insertAfter(marker, nodes[nodes.length - 1]);
7680                          } else {
7681                              container.insertBefore(marker, nodes[offset]);
7682                          }
7683  
7684                          tmpRng.moveToElementText(marker);
7685                      } else if (container.canHaveHTML) {
7686                          // Empty node selection for example <div>|</div>
7687                          // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7688                          container.innerHTML = '<span>\uFEFF</span>';
7689                          marker = container.firstChild;
7690                          tmpRng.moveToElementText(marker);
7691                          tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7692                      }
7693  
7694                      ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7695                      dom.remove(marker);
7696                  }
7697              }
7698  
7699              // Setup some shorter versions
7700              startContainer = rng.startContainer;
7701              startOffset = rng.startOffset;
7702              endContainer = rng.endContainer;
7703              endOffset = rng.endOffset;
7704              ieRng = body.createTextRange();
7705  
7706              // If single element selection then try making a control selection out of it
7707              if (startContainer == endContainer && startContainer.nodeType == 1) {
7708                  // Trick to place the caret inside an empty block element like <p></p>
7709                  if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7710                      if (startContainer.canHaveHTML) {
7711                          // Check if previous sibling is an empty block if it is then we need to render it
7712                          // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7713                          // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7714                          sibling = startContainer.previousSibling;
7715                          if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7716                              sibling.innerHTML = '\uFEFF';
7717                          } else {
7718                              sibling = null;
7719                          }
7720  
7721                          startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7722                          ieRng.moveToElementText(startContainer.lastChild);
7723                          ieRng.select();
7724                          dom.doc.selection.clear();
7725                          startContainer.innerHTML = '';
7726  
7727                          if (sibling) {
7728                              sibling.innerHTML = '';
7729                          }
7730                          return;
7731                      } else {
7732                          startOffset = dom.nodeIndex(startContainer);
7733                          startContainer = startContainer.parentNode;
7734                      }
7735                  }
7736  
7737                  if (startOffset == endOffset - 1) {
7738                      try {
7739                          ctrlElm = startContainer.childNodes[startOffset];
7740                          ctrlRng = body.createControlRange();
7741                          ctrlRng.addElement(ctrlElm);
7742                          ctrlRng.select();
7743  
7744                          // Check if the range produced is on the correct element and is a control range
7745                          // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
7746                          nativeRng = selection.getRng();
7747                          if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
7748                              return;
7749                          }
7750                      } catch (ex) {
7751                          // Ignore
7752                      }
7753                  }
7754              }
7755  
7756              // Set start/end point of selection
7757              setEndPoint(true);
7758              setEndPoint();
7759  
7760              // Select the new range and scroll it into view
7761              ieRng.select();
7762          };
7763  
7764          // Expose range method
7765          this.getRangeAt = getRange;
7766      };
7767  
7768      // Expose the selection object
7769      tinymce.dom.TridentSelection = Selection;
7770  })();
7771  
7772  /*
7773   * Sizzle CSS Selector Engine
7774   *  Copyright, The Dojo Foundation
7775   *  Released under the MIT, BSD, and GPL Licenses.
7776   *  More information: http://sizzlejs.com/
7777   */
7778  (function(){
7779  
7780  var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7781      expando = "sizcache",
7782      done = 0,
7783      toString = Object.prototype.toString,
7784      hasDuplicate = false,
7785      baseHasDuplicate = true,
7786      rBackslash = /\\/g,
7787      rReturn = /\r\n/g,
7788      rNonWord = /\W/;
7789  
7790  // Here we check if the JavaScript engine is using some sort of
7791  // optimization where it does not always call our comparision
7792  // function. If that is the case, discard the hasDuplicate value.
7793  //   Thus far that includes Google Chrome.
7794  [0, 0].sort(function() {
7795      baseHasDuplicate = false;
7796      return 0;
7797  });
7798  
7799  var Sizzle = function( selector, context, results, seed ) {
7800      results = results || [];
7801      context = context || document;
7802  
7803      var origContext = context;
7804  
7805      if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7806          return [];
7807      }
7808  
7809      if ( !selector || typeof selector !== "string" ) {
7810          return results;
7811      }
7812  
7813      var m, set, checkSet, extra, ret, cur, pop, i,
7814          prune = true,
7815          contextXML = Sizzle.isXML( context ),
7816          parts = [],
7817          soFar = selector;
7818  
7819      // Reset the position of the chunker regexp (start from head)
7820      do {
7821          chunker.exec( "" );
7822          m = chunker.exec( soFar );
7823  
7824          if ( m ) {
7825              soFar = m[3];
7826  
7827              parts.push( m[1] );
7828  
7829              if ( m[2] ) {
7830                  extra = m[3];
7831                  break;
7832              }
7833          }
7834      } while ( m );
7835  
7836      if ( parts.length > 1 && origPOS.exec( selector ) ) {
7837  
7838          if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7839              set = posProcess( parts[0] + parts[1], context, seed );
7840  
7841          } else {
7842              set = Expr.relative[ parts[0] ] ?
7843                  [ context ] :
7844                  Sizzle( parts.shift(), context );
7845  
7846              while ( parts.length ) {
7847                  selector = parts.shift();
7848  
7849                  if ( Expr.relative[ selector ] ) {
7850                      selector += parts.shift();
7851                  }
7852  
7853                  set = posProcess( selector, set, seed );
7854              }
7855          }
7856  
7857      } else {
7858          // Take a shortcut and set the context if the root selector is an ID
7859          // (but not if it'll be faster if the inner selector is an ID)
7860          if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7861                  Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7862  
7863              ret = Sizzle.find( parts.shift(), context, contextXML );
7864              context = ret.expr ?
7865                  Sizzle.filter( ret.expr, ret.set )[0] :
7866                  ret.set[0];
7867          }
7868  
7869          if ( context ) {
7870              ret = seed ?
7871                  { expr: parts.pop(), set: makeArray(seed) } :
7872                  Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7873  
7874              set = ret.expr ?
7875                  Sizzle.filter( ret.expr, ret.set ) :
7876                  ret.set;
7877  
7878              if ( parts.length > 0 ) {
7879                  checkSet = makeArray( set );
7880  
7881              } else {
7882                  prune = false;
7883              }
7884  
7885              while ( parts.length ) {
7886                  cur = parts.pop();
7887                  pop = cur;
7888  
7889                  if ( !Expr.relative[ cur ] ) {
7890                      cur = "";
7891                  } else {
7892                      pop = parts.pop();
7893                  }
7894  
7895                  if ( pop == null ) {
7896                      pop = context;
7897                  }
7898  
7899                  Expr.relative[ cur ]( checkSet, pop, contextXML );
7900              }
7901  
7902          } else {
7903              checkSet = parts = [];
7904          }
7905      }
7906  
7907      if ( !checkSet ) {
7908          checkSet = set;
7909      }
7910  
7911      if ( !checkSet ) {
7912          Sizzle.error( cur || selector );
7913      }
7914  
7915      if ( toString.call(checkSet) === "[object Array]" ) {
7916          if ( !prune ) {
7917              results.push.apply( results, checkSet );
7918  
7919          } else if ( context && context.nodeType === 1 ) {
7920              for ( i = 0; checkSet[i] != null; i++ ) {
7921                  if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7922                      results.push( set[i] );
7923                  }
7924              }
7925  
7926          } else {
7927              for ( i = 0; checkSet[i] != null; i++ ) {
7928                  if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7929                      results.push( set[i] );
7930                  }
7931              }
7932          }
7933  
7934      } else {
7935          makeArray( checkSet, results );
7936      }
7937  
7938      if ( extra ) {
7939          Sizzle( extra, origContext, results, seed );
7940          Sizzle.uniqueSort( results );
7941      }
7942  
7943      return results;
7944  };
7945  
7946  Sizzle.uniqueSort = function( results ) {
7947      if ( sortOrder ) {
7948          hasDuplicate = baseHasDuplicate;
7949          results.sort( sortOrder );
7950  
7951          if ( hasDuplicate ) {
7952              for ( var i = 1; i < results.length; i++ ) {
7953                  if ( results[i] === results[ i - 1 ] ) {
7954                      results.splice( i--, 1 );
7955                  }
7956              }
7957          }
7958      }
7959  
7960      return results;
7961  };
7962  
7963  Sizzle.matches = function( expr, set ) {
7964      return Sizzle( expr, null, null, set );
7965  };
7966  
7967  Sizzle.matchesSelector = function( node, expr ) {
7968      return Sizzle( expr, null, null, [node] ).length > 0;
7969  };
7970  
7971  Sizzle.find = function( expr, context, isXML ) {
7972      var set, i, len, match, type, left;
7973  
7974      if ( !expr ) {
7975          return [];
7976      }
7977  
7978      for ( i = 0, len = Expr.order.length; i < len; i++ ) {
7979          type = Expr.order[i];
7980  
7981          if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
7982              left = match[1];
7983              match.splice( 1, 1 );
7984  
7985              if ( left.substr( left.length - 1 ) !== "\\" ) {
7986                  match[1] = (match[1] || "").replace( rBackslash, "" );
7987                  set = Expr.find[ type ]( match, context, isXML );
7988  
7989                  if ( set != null ) {
7990                      expr = expr.replace( Expr.match[ type ], "" );
7991                      break;
7992                  }
7993              }
7994          }
7995      }
7996  
7997      if ( !set ) {
7998          set = typeof context.getElementsByTagName !== "undefined" ?
7999              context.getElementsByTagName( "*" ) :
8000              [];
8001      }
8002  
8003      return { set: set, expr: expr };
8004  };
8005  
8006  Sizzle.filter = function( expr, set, inplace, not ) {
8007      var match, anyFound,
8008          type, found, item, filter, left,
8009          i, pass,
8010          old = expr,
8011          result = [],
8012          curLoop = set,
8013          isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
8014  
8015      while ( expr && set.length ) {
8016          for ( type in Expr.filter ) {
8017              if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
8018                  filter = Expr.filter[ type ];
8019                  left = match[1];
8020  
8021                  anyFound = false;
8022  
8023                  match.splice(1,1);
8024  
8025                  if ( left.substr( left.length - 1 ) === "\\" ) {
8026                      continue;
8027                  }
8028  
8029                  if ( curLoop === result ) {
8030                      result = [];
8031                  }
8032  
8033                  if ( Expr.preFilter[ type ] ) {
8034                      match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
8035  
8036                      if ( !match ) {
8037                          anyFound = found = true;
8038  
8039                      } else if ( match === true ) {
8040                          continue;
8041                      }
8042                  }
8043  
8044                  if ( match ) {
8045                      for ( i = 0; (item = curLoop[i]) != null; i++ ) {
8046                          if ( item ) {
8047                              found = filter( item, match, i, curLoop );
8048                              pass = not ^ found;
8049  
8050                              if ( inplace && found != null ) {
8051                                  if ( pass ) {
8052                                      anyFound = true;
8053  
8054                                  } else {
8055                                      curLoop[i] = false;
8056                                  }
8057  
8058                              } else if ( pass ) {
8059                                  result.push( item );
8060                                  anyFound = true;
8061                              }
8062                          }
8063                      }
8064                  }
8065  
8066                  if ( found !== undefined ) {
8067                      if ( !inplace ) {
8068                          curLoop = result;
8069                      }
8070  
8071                      expr = expr.replace( Expr.match[ type ], "" );
8072  
8073                      if ( !anyFound ) {
8074                          return [];
8075                      }
8076  
8077                      break;
8078                  }
8079              }
8080          }
8081  
8082          // Improper expression
8083          if ( expr === old ) {
8084              if ( anyFound == null ) {
8085                  Sizzle.error( expr );
8086  
8087              } else {
8088                  break;
8089              }
8090          }
8091  
8092          old = expr;
8093      }
8094  
8095      return curLoop;
8096  };
8097  
8098  Sizzle.error = function( msg ) {
8099      throw new Error( "Syntax error, unrecognized expression: " + msg );
8100  };
8101  
8102  var getText = Sizzle.getText = function( elem ) {
8103      var i, node,
8104          nodeType = elem.nodeType,
8105          ret = "";
8106  
8107      if ( nodeType ) {
8108          if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
8109              // Use textContent || innerText for elements
8110              if ( typeof elem.textContent === 'string' ) {
8111                  return elem.textContent;
8112              } else if ( typeof elem.innerText === 'string' ) {
8113                  // Replace IE's carriage returns
8114                  return elem.innerText.replace( rReturn, '' );
8115              } else {
8116                  // Traverse it's children
8117                  for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
8118                      ret += getText( elem );
8119                  }
8120              }
8121          } else if ( nodeType === 3 || nodeType === 4 ) {
8122              return elem.nodeValue;
8123          }
8124      } else {
8125  
8126          // If no nodeType, this is expected to be an array
8127          for ( i = 0; (node = elem[i]); i++ ) {
8128              // Do not traverse comment nodes
8129              if ( node.nodeType !== 8 ) {
8130                  ret += getText( node );
8131              }
8132          }
8133      }
8134      return ret;
8135  };
8136  
8137  var Expr = Sizzle.selectors = {
8138      order: [ "ID", "NAME", "TAG" ],
8139  
8140      match: {
8141          ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8142          CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8143          NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
8144          ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
8145          TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
8146          CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
8147          POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
8148          PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
8149      },
8150  
8151      leftMatch: {},
8152  
8153      attrMap: {
8154          "class": "className",
8155          "for": "htmlFor"
8156      },
8157  
8158      attrHandle: {
8159          href: function( elem ) {
8160              return elem.getAttribute( "href" );
8161          },
8162          type: function( elem ) {
8163              return elem.getAttribute( "type" );
8164          }
8165      },
8166  
8167      relative: {
8168          "+": function(checkSet, part){
8169              var isPartStr = typeof part === "string",
8170                  isTag = isPartStr && !rNonWord.test( part ),
8171                  isPartStrNotTag = isPartStr && !isTag;
8172  
8173              if ( isTag ) {
8174                  part = part.toLowerCase();
8175              }
8176  
8177              for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
8178                  if ( (elem = checkSet[i]) ) {
8179                      while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
8180  
8181                      checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
8182                          elem || false :
8183                          elem === part;
8184                  }
8185              }
8186  
8187              if ( isPartStrNotTag ) {
8188                  Sizzle.filter( part, checkSet, true );
8189              }
8190          },
8191  
8192          ">": function( checkSet, part ) {
8193              var elem,
8194                  isPartStr = typeof part === "string",
8195                  i = 0,
8196                  l = checkSet.length;
8197  
8198              if ( isPartStr && !rNonWord.test( part ) ) {
8199                  part = part.toLowerCase();
8200  
8201                  for ( ; i < l; i++ ) {
8202                      elem = checkSet[i];
8203  
8204                      if ( elem ) {
8205                          var parent = elem.parentNode;
8206                          checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
8207                      }
8208                  }
8209  
8210              } else {
8211                  for ( ; i < l; i++ ) {
8212                      elem = checkSet[i];
8213  
8214                      if ( elem ) {
8215                          checkSet[i] = isPartStr ?
8216                              elem.parentNode :
8217                              elem.parentNode === part;
8218                      }
8219                  }
8220  
8221                  if ( isPartStr ) {
8222                      Sizzle.filter( part, checkSet, true );
8223                  }
8224              }
8225          },
8226  
8227          "": function(checkSet, part, isXML){
8228              var nodeCheck,
8229                  doneName = done++,
8230                  checkFn = dirCheck;
8231  
8232              if ( typeof part === "string" && !rNonWord.test( part ) ) {
8233                  part = part.toLowerCase();
8234                  nodeCheck = part;
8235                  checkFn = dirNodeCheck;
8236              }
8237  
8238              checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
8239          },
8240  
8241          "~": function( checkSet, part, isXML ) {
8242              var nodeCheck,
8243                  doneName = done++,
8244                  checkFn = dirCheck;
8245  
8246              if ( typeof part === "string" && !rNonWord.test( part ) ) {
8247                  part = part.toLowerCase();
8248                  nodeCheck = part;
8249                  checkFn = dirNodeCheck;
8250              }
8251  
8252              checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
8253          }
8254      },
8255  
8256      find: {
8257          ID: function( match, context, isXML ) {
8258              if ( typeof context.getElementById !== "undefined" && !isXML ) {
8259                  var m = context.getElementById(match[1]);
8260                  // Check parentNode to catch when Blackberry 4.6 returns
8261                  // nodes that are no longer in the document #6963
8262                  return m && m.parentNode ? [m] : [];
8263              }
8264          },
8265  
8266          NAME: function( match, context ) {
8267              if ( typeof context.getElementsByName !== "undefined" ) {
8268                  var ret = [],
8269                      results = context.getElementsByName( match[1] );
8270  
8271                  for ( var i = 0, l = results.length; i < l; i++ ) {
8272                      if ( results[i].getAttribute("name") === match[1] ) {
8273                          ret.push( results[i] );
8274                      }
8275                  }
8276  
8277                  return ret.length === 0 ? null : ret;
8278              }
8279          },
8280  
8281          TAG: function( match, context ) {
8282              if ( typeof context.getElementsByTagName !== "undefined" ) {
8283                  return context.getElementsByTagName( match[1] );
8284              }
8285          }
8286      },
8287      preFilter: {
8288          CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8289              match = " " + match[1].replace( rBackslash, "" ) + " ";
8290  
8291              if ( isXML ) {
8292                  return match;
8293              }
8294  
8295              for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8296                  if ( elem ) {
8297                      if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8298                          if ( !inplace ) {
8299                              result.push( elem );
8300                          }
8301  
8302                      } else if ( inplace ) {
8303                          curLoop[i] = false;
8304                      }
8305                  }
8306              }
8307  
8308              return false;
8309          },
8310  
8311          ID: function( match ) {
8312              return match[1].replace( rBackslash, "" );
8313          },
8314  
8315          TAG: function( match, curLoop ) {
8316              return match[1].replace( rBackslash, "" ).toLowerCase();
8317          },
8318  
8319          CHILD: function( match ) {
8320              if ( match[1] === "nth" ) {
8321                  if ( !match[2] ) {
8322                      Sizzle.error( match[0] );
8323                  }
8324  
8325                  match[2] = match[2].replace(/^\+|\s*/g, '');
8326  
8327                  // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8328                  var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8329                      match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8330                      !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8331  
8332                  // calculate the numbers (first)n+(last) including if they are negative
8333                  match[2] = (test[1] + (test[2] || 1)) - 0;
8334                  match[3] = test[3] - 0;
8335              }
8336              else if ( match[2] ) {
8337                  Sizzle.error( match[0] );
8338              }
8339  
8340              // TODO: Move to normal caching system
8341              match[0] = done++;
8342  
8343              return match;
8344          },
8345  
8346          ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8347              var name = match[1] = match[1].replace( rBackslash, "" );
8348  
8349              if ( !isXML && Expr.attrMap[name] ) {
8350                  match[1] = Expr.attrMap[name];
8351              }
8352  
8353              // Handle if an un-quoted value was used
8354              match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8355  
8356              if ( match[2] === "~=" ) {
8357                  match[4] = " " + match[4] + " ";
8358              }
8359  
8360              return match;
8361          },
8362  
8363          PSEUDO: function( match, curLoop, inplace, result, not ) {
8364              if ( match[1] === "not" ) {
8365                  // If we're dealing with a complex expression, or a simple one
8366                  if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8367                      match[3] = Sizzle(match[3], null, null, curLoop);
8368  
8369                  } else {
8370                      var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8371  
8372                      if ( !inplace ) {
8373                          result.push.apply( result, ret );
8374                      }
8375  
8376                      return false;
8377                  }
8378  
8379              } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8380                  return true;
8381              }
8382  
8383              return match;
8384          },
8385  
8386          POS: function( match ) {
8387              match.unshift( true );
8388  
8389              return match;
8390          }
8391      },
8392  
8393      filters: {
8394          enabled: function( elem ) {
8395              return elem.disabled === false && elem.type !== "hidden";
8396          },
8397  
8398          disabled: function( elem ) {
8399              return elem.disabled === true;
8400          },
8401  
8402          checked: function( elem ) {
8403              return elem.checked === true;
8404          },
8405  
8406          selected: function( elem ) {
8407              // Accessing this property makes selected-by-default
8408              // options in Safari work properly
8409              if ( elem.parentNode ) {
8410                  elem.parentNode.selectedIndex;
8411              }
8412  
8413              return elem.selected === true;
8414          },
8415  
8416          parent: function( elem ) {
8417              return !!elem.firstChild;
8418          },
8419  
8420          empty: function( elem ) {
8421              return !elem.firstChild;
8422          },
8423  
8424          has: function( elem, i, match ) {
8425              return !!Sizzle( match[3], elem ).length;
8426          },
8427  
8428          header: function( elem ) {
8429              return (/h\d/i).test( elem.nodeName );
8430          },
8431  
8432          text: function( elem ) {
8433              var attr = elem.getAttribute( "type" ), type = elem.type;
8434              // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8435              // use getAttribute instead to test this case
8436              return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8437          },
8438  
8439          radio: function( elem ) {
8440              return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8441          },
8442  
8443          checkbox: function( elem ) {
8444              return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8445          },
8446  
8447          file: function( elem ) {
8448              return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8449          },
8450  
8451          password: function( elem ) {
8452              return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8453          },
8454  
8455          submit: function( elem ) {
8456              var name = elem.nodeName.toLowerCase();
8457              return (name === "input" || name === "button") && "submit" === elem.type;
8458          },
8459  
8460          image: function( elem ) {
8461              return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8462          },
8463  
8464          reset: function( elem ) {
8465              var name = elem.nodeName.toLowerCase();
8466              return (name === "input" || name === "button") && "reset" === elem.type;
8467          },
8468  
8469          button: function( elem ) {
8470              var name = elem.nodeName.toLowerCase();
8471              return name === "input" && "button" === elem.type || name === "button";
8472          },
8473  
8474          input: function( elem ) {
8475              return (/input|select|textarea|button/i).test( elem.nodeName );
8476          },
8477  
8478          focus: function( elem ) {
8479              return elem === elem.ownerDocument.activeElement;
8480          }
8481      },
8482      setFilters: {
8483          first: function( elem, i ) {
8484              return i === 0;
8485          },
8486  
8487          last: function( elem, i, match, array ) {
8488              return i === array.length - 1;
8489          },
8490  
8491          even: function( elem, i ) {
8492              return i % 2 === 0;
8493          },
8494  
8495          odd: function( elem, i ) {
8496              return i % 2 === 1;
8497          },
8498  
8499          lt: function( elem, i, match ) {
8500              return i < match[3] - 0;
8501          },
8502  
8503          gt: function( elem, i, match ) {
8504              return i > match[3] - 0;
8505          },
8506  
8507          nth: function( elem, i, match ) {
8508              return match[3] - 0 === i;
8509          },
8510  
8511          eq: function( elem, i, match ) {
8512              return match[3] - 0 === i;
8513          }
8514      },
8515      filter: {
8516          PSEUDO: function( elem, match, i, array ) {
8517              var name = match[1],
8518                  filter = Expr.filters[ name ];
8519  
8520              if ( filter ) {
8521                  return filter( elem, i, match, array );
8522  
8523              } else if ( name === "contains" ) {
8524                  return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8525  
8526              } else if ( name === "not" ) {
8527                  var not = match[3];
8528  
8529                  for ( var j = 0, l = not.length; j < l; j++ ) {
8530                      if ( not[j] === elem ) {
8531                          return false;
8532                      }
8533                  }
8534  
8535                  return true;
8536  
8537              } else {
8538                  Sizzle.error( name );
8539              }
8540          },
8541  
8542          CHILD: function( elem, match ) {
8543              var first, last,
8544                  doneName, parent, cache,
8545                  count, diff,
8546                  type = match[1],
8547                  node = elem;
8548  
8549              switch ( type ) {
8550                  case "only":
8551                  case "first":
8552                      while ( (node = node.previousSibling) ) {
8553                          if ( node.nodeType === 1 ) {
8554                              return false;
8555                          }
8556                      }
8557  
8558                      if ( type === "first" ) {
8559                          return true;
8560                      }
8561  
8562                      node = elem;
8563  
8564                      /* falls through */
8565                  case "last":
8566                      while ( (node = node.nextSibling) ) {
8567                          if ( node.nodeType === 1 ) {
8568                              return false;
8569                          }
8570                      }
8571  
8572                      return true;
8573  
8574                  case "nth":
8575                      first = match[2];
8576                      last = match[3];
8577  
8578                      if ( first === 1 && last === 0 ) {
8579                          return true;
8580                      }
8581  
8582                      doneName = match[0];
8583                      parent = elem.parentNode;
8584  
8585                      if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8586                          count = 0;
8587  
8588                          for ( node = parent.firstChild; node; node = node.nextSibling ) {
8589                              if ( node.nodeType === 1 ) {
8590                                  node.nodeIndex = ++count;
8591                              }
8592                          }
8593  
8594                          parent[ expando ] = doneName;
8595                      }
8596  
8597                      diff = elem.nodeIndex - last;
8598  
8599                      if ( first === 0 ) {
8600                          return diff === 0;
8601  
8602                      } else {
8603                          return ( diff % first === 0 && diff / first >= 0 );
8604                      }
8605              }
8606          },
8607  
8608          ID: function( elem, match ) {
8609              return elem.nodeType === 1 && elem.getAttribute("id") === match;
8610          },
8611  
8612          TAG: function( elem, match ) {
8613              return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8614          },
8615  
8616          CLASS: function( elem, match ) {
8617              return (" " + (elem.className || elem.getAttribute("class")) + " ")
8618                  .indexOf( match ) > -1;
8619          },
8620  
8621          ATTR: function( elem, match ) {
8622              var name = match[1],
8623                  result = Sizzle.attr ?
8624                      Sizzle.attr( elem, name ) :
8625                      Expr.attrHandle[ name ] ?
8626                      Expr.attrHandle[ name ]( elem ) :
8627                      elem[ name ] != null ?
8628                          elem[ name ] :
8629                          elem.getAttribute( name ),
8630                  value = result + "",
8631                  type = match[2],
8632                  check = match[4];
8633  
8634              return result == null ?
8635                  type === "!=" :
8636                  !type && Sizzle.attr ?
8637                  result != null :
8638                  type === "=" ?
8639                  value === check :
8640                  type === "*=" ?
8641                  value.indexOf(check) >= 0 :
8642                  type === "~=" ?
8643                  (" " + value + " ").indexOf(check) >= 0 :
8644                  !check ?
8645                  value && result !== false :
8646                  type === "!=" ?
8647                  value !== check :
8648                  type === "^=" ?
8649                  value.indexOf(check) === 0 :
8650                  type === "$=" ?
8651                  value.substr(value.length - check.length) === check :
8652                  type === "|=" ?
8653                  value === check || value.substr(0, check.length + 1) === check + "-" :
8654                  false;
8655          },
8656  
8657          POS: function( elem, match, i, array ) {
8658              var name = match[2],
8659                  filter = Expr.setFilters[ name ];
8660  
8661              if ( filter ) {
8662                  return filter( elem, i, match, array );
8663              }
8664          }
8665      }
8666  };
8667  
8668  var origPOS = Expr.match.POS,
8669      fescape = function(all, num){
8670          return "\\" + (num - 0 + 1);
8671      };
8672  
8673  for ( var type in Expr.match ) {
8674      Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8675      Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8676  }
8677  // Expose origPOS
8678  // "global" as in regardless of relation to brackets/parens
8679  Expr.match.globalPOS = origPOS;
8680  
8681  var makeArray = function( array, results ) {
8682      array = Array.prototype.slice.call( array, 0 );
8683  
8684      if ( results ) {
8685          results.push.apply( results, array );
8686          return results;
8687      }
8688  
8689      return array;
8690  };
8691  
8692  // Perform a simple check to determine if the browser is capable of
8693  // converting a NodeList to an array using builtin methods.
8694  // Also verifies that the returned array holds DOM nodes
8695  // (which is not the case in the Blackberry browser)
8696  try {
8697      Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8698  
8699  // Provide a fallback method if it does not work
8700  } catch( e ) {
8701      makeArray = function( array, results ) {
8702          var i = 0,
8703              ret = results || [];
8704  
8705          if ( toString.call(array) === "[object Array]" ) {
8706              Array.prototype.push.apply( ret, array );
8707  
8708          } else {
8709              if ( typeof array.length === "number" ) {
8710                  for ( var l = array.length; i < l; i++ ) {
8711                      ret.push( array[i] );
8712                  }
8713  
8714              } else {
8715                  for ( ; array[i]; i++ ) {
8716                      ret.push( array[i] );
8717                  }
8718              }
8719          }
8720  
8721          return ret;
8722      };
8723  }
8724  
8725  var sortOrder, siblingCheck;
8726  
8727  if ( document.documentElement.compareDocumentPosition ) {
8728      sortOrder = function( a, b ) {
8729          if ( a === b ) {
8730              hasDuplicate = true;
8731              return 0;
8732          }
8733  
8734          if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8735              return a.compareDocumentPosition ? -1 : 1;
8736          }
8737  
8738          return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8739      };
8740  
8741  } else {
8742      sortOrder = function( a, b ) {
8743          // The nodes are identical, we can exit early
8744          if ( a === b ) {
8745              hasDuplicate = true;
8746              return 0;
8747  
8748          // Fallback to using sourceIndex (in IE) if it's available on both nodes
8749          } else if ( a.sourceIndex && b.sourceIndex ) {
8750              return a.sourceIndex - b.sourceIndex;
8751          }
8752  
8753          var al, bl,
8754              ap = [],
8755              bp = [],
8756              aup = a.parentNode,
8757              bup = b.parentNode,
8758              cur = aup;
8759  
8760          // If the nodes are siblings (or identical) we can do a quick check
8761          if ( aup === bup ) {
8762              return siblingCheck( a, b );
8763  
8764          // If no parents were found then the nodes are disconnected
8765          } else if ( !aup ) {
8766              return -1;
8767  
8768          } else if ( !bup ) {
8769              return 1;
8770          }
8771  
8772          // Otherwise they're somewhere else in the tree so we need
8773          // to build up a full list of the parentNodes for comparison
8774          while ( cur ) {
8775              ap.unshift( cur );
8776              cur = cur.parentNode;
8777          }
8778  
8779          cur = bup;
8780  
8781          while ( cur ) {
8782              bp.unshift( cur );
8783              cur = cur.parentNode;
8784          }
8785  
8786          al = ap.length;
8787          bl = bp.length;
8788  
8789          // Start walking down the tree looking for a discrepancy
8790          for ( var i = 0; i < al && i < bl; i++ ) {
8791              if ( ap[i] !== bp[i] ) {
8792                  return siblingCheck( ap[i], bp[i] );
8793              }
8794          }
8795  
8796          // We ended someplace up the tree so do a sibling check
8797          return i === al ?
8798              siblingCheck( a, bp[i], -1 ) :
8799              siblingCheck( ap[i], b, 1 );
8800      };
8801  
8802      siblingCheck = function( a, b, ret ) {
8803          if ( a === b ) {
8804              return ret;
8805          }
8806  
8807          var cur = a.nextSibling;
8808  
8809          while ( cur ) {
8810              if ( cur === b ) {
8811                  return -1;
8812              }
8813  
8814              cur = cur.nextSibling;
8815          }
8816  
8817          return 1;
8818      };
8819  }
8820  
8821  // Check to see if the browser returns elements by name when
8822  // querying by getElementById (and provide a workaround)
8823  (function(){
8824      // We're going to inject a fake input element with a specified name
8825      var form = document.createElement("div"),
8826          id = "script" + (new Date()).getTime(),
8827          root = document.documentElement;
8828  
8829      form.innerHTML = "<a name='" + id + "'/>";
8830  
8831      // Inject it into the root element, check its status, and remove it quickly
8832      root.insertBefore( form, root.firstChild );
8833  
8834      // The workaround has to do additional checks after a getElementById
8835      // Which slows things down for other browsers (hence the branching)
8836      if ( document.getElementById( id ) ) {
8837          Expr.find.ID = function( match, context, isXML ) {
8838              if ( typeof context.getElementById !== "undefined" && !isXML ) {
8839                  var m = context.getElementById(match[1]);
8840  
8841                  return m ?
8842                      m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8843                          [m] :
8844                          undefined :
8845                      [];
8846              }
8847          };
8848  
8849          Expr.filter.ID = function( elem, match ) {
8850              var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8851  
8852              return elem.nodeType === 1 && node && node.nodeValue === match;
8853          };
8854      }
8855  
8856      root.removeChild( form );
8857  
8858      // release memory in IE
8859      root = form = null;
8860  })();
8861  
8862  (function(){
8863      // Check to see if the browser returns only elements
8864      // when doing getElementsByTagName("*")
8865  
8866      // Create a fake element
8867      var div = document.createElement("div");
8868      div.appendChild( document.createComment("") );
8869  
8870      // Make sure no comments are found
8871      if ( div.getElementsByTagName("*").length > 0 ) {
8872          Expr.find.TAG = function( match, context ) {
8873              var results = context.getElementsByTagName( match[1] );
8874  
8875              // Filter out possible comments
8876              if ( match[1] === "*" ) {
8877                  var tmp = [];
8878  
8879                  for ( var i = 0; results[i]; i++ ) {
8880                      if ( results[i].nodeType === 1 ) {
8881                          tmp.push( results[i] );
8882                      }
8883                  }
8884  
8885                  results = tmp;
8886              }
8887  
8888              return results;
8889          };
8890      }
8891  
8892      // Check to see if an attribute returns normalized href attributes
8893      div.innerHTML = "<a href='#'></a>";
8894  
8895      if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8896              div.firstChild.getAttribute("href") !== "#" ) {
8897  
8898          Expr.attrHandle.href = function( elem ) {
8899              return elem.getAttribute( "href", 2 );
8900          };
8901      }
8902  
8903      // release memory in IE
8904      div = null;
8905  })();
8906  
8907  if ( document.querySelectorAll ) {
8908      (function(){
8909          var oldSizzle = Sizzle,
8910              div = document.createElement("div"),
8911              id = "__sizzle__";
8912  
8913          div.innerHTML = "<p class='TEST'></p>";
8914  
8915          // Safari can't handle uppercase or unicode characters when
8916          // in quirks mode.
8917          if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8918              return;
8919          }
8920  
8921          Sizzle = function( query, context, extra, seed ) {
8922              context = context || document;
8923  
8924              // Only use querySelectorAll on non-XML documents
8925              // (ID selectors don't work in non-HTML documents)
8926              if ( !seed && !Sizzle.isXML(context) ) {
8927                  // See if we find a selector to speed up
8928                  var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8929  
8930                  if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8931                      // Speed-up: Sizzle("TAG")
8932                      if ( match[1] ) {
8933                          return makeArray( context.getElementsByTagName( query ), extra );
8934  
8935                      // Speed-up: Sizzle(".CLASS")
8936                      } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8937                          return makeArray( context.getElementsByClassName( match[2] ), extra );
8938                      }
8939                  }
8940  
8941                  if ( context.nodeType === 9 ) {
8942                      // Speed-up: Sizzle("body")
8943                      // The body element only exists once, optimize finding it
8944                      if ( query === "body" && context.body ) {
8945                          return makeArray( [ context.body ], extra );
8946  
8947                      // Speed-up: Sizzle("#ID")
8948                      } else if ( match && match[3] ) {
8949                          var elem = context.getElementById( match[3] );
8950  
8951                          // Check parentNode to catch when Blackberry 4.6 returns
8952                          // nodes that are no longer in the document #6963
8953                          if ( elem && elem.parentNode ) {
8954                              // Handle the case where IE and Opera return items
8955                              // by name instead of ID
8956                              if ( elem.id === match[3] ) {
8957                                  return makeArray( [ elem ], extra );
8958                              }
8959  
8960                          } else {
8961                              return makeArray( [], extra );
8962                          }
8963                      }
8964  
8965                      try {
8966                          return makeArray( context.querySelectorAll(query), extra );
8967                      } catch(qsaError) {}
8968  
8969                  // qSA works strangely on Element-rooted queries
8970                  // We can work around this by specifying an extra ID on the root
8971                  // and working up from there (Thanks to Andrew Dupont for the technique)
8972                  // IE 8 doesn't work on object elements
8973                  } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8974                      var oldContext = context,
8975                          old = context.getAttribute( "id" ),
8976                          nid = old || id,
8977                          hasParent = context.parentNode,
8978                          relativeHierarchySelector = /^\s*[+~]/.test( query );
8979  
8980                      if ( !old ) {
8981                          context.setAttribute( "id", nid );
8982                      } else {
8983                          nid = nid.replace( /'/g, "\\$&" );
8984                      }
8985                      if ( relativeHierarchySelector && hasParent ) {
8986                          context = context.parentNode;
8987                      }
8988  
8989                      try {
8990                          if ( !relativeHierarchySelector || hasParent ) {
8991                              return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
8992                          }
8993  
8994                      } catch(pseudoError) {
8995                      } finally {
8996                          if ( !old ) {
8997                              oldContext.removeAttribute( "id" );
8998                          }
8999                      }
9000                  }
9001              }
9002  
9003              return oldSizzle(query, context, extra, seed);
9004          };
9005  
9006          for ( var prop in oldSizzle ) {
9007              Sizzle[ prop ] = oldSizzle[ prop ];
9008          }
9009  
9010          // release memory in IE
9011          div = null;
9012      })();
9013  }
9014  
9015  (function(){
9016      var html = document.documentElement,
9017          matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
9018  
9019      if ( matches ) {
9020          // Check to see if it's possible to do matchesSelector
9021          // on a disconnected node (IE 9 fails this)
9022          var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
9023              pseudoWorks = false;
9024  
9025          try {
9026              // This should fail with an exception
9027              // Gecko does not error, returns false instead
9028              matches.call( document.documentElement, "[test!='']:sizzle" );
9029  
9030          } catch( pseudoError ) {
9031              pseudoWorks = true;
9032          }
9033  
9034          Sizzle.matchesSelector = function( node, expr ) {
9035              // Make sure that attribute selectors are quoted
9036              expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
9037  
9038              if ( !Sizzle.isXML( node ) ) {
9039                  try {
9040                      if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
9041                          var ret = matches.call( node, expr );
9042  
9043                          // IE 9's matchesSelector returns false on disconnected nodes
9044                          if ( ret || !disconnectedMatch ||
9045                                  // As well, disconnected nodes are said to be in a document
9046                                  // fragment in IE 9, so check for that
9047                                  node.document && node.document.nodeType !== 11 ) {
9048                              return ret;
9049                          }
9050                      }
9051                  } catch(e) {}
9052              }
9053  
9054              return Sizzle(expr, null, null, [node]).length > 0;
9055          };
9056      }
9057  })();
9058  
9059  (function(){
9060      var div = document.createElement("div");
9061  
9062      div.innerHTML = "<div class='test e'></div><div class='test'></div>";
9063  
9064      // Opera can't find a second classname (in 9.6)
9065      // Also, make sure that getElementsByClassName actually exists
9066      if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
9067          return;
9068      }
9069  
9070      // Safari caches class attributes, doesn't catch changes (in 3.2)
9071      div.lastChild.className = "e";
9072  
9073      if ( div.getElementsByClassName("e").length === 1 ) {
9074          return;
9075      }
9076  
9077      Expr.order.splice(1, 0, "CLASS");
9078      Expr.find.CLASS = function( match, context, isXML ) {
9079          if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
9080              return context.getElementsByClassName(match[1]);
9081          }
9082      };
9083  
9084      // release memory in IE
9085      div = null;
9086  })();
9087  
9088  function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9089      for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9090          var elem = checkSet[i];
9091  
9092          if ( elem ) {
9093              var match = false;
9094  
9095              elem = elem[dir];
9096  
9097              while ( elem ) {
9098                  if ( elem[ expando ] === doneName ) {
9099                      match = checkSet[elem.sizset];
9100                      break;
9101                  }
9102  
9103                  if ( elem.nodeType === 1 && !isXML ){
9104                      elem[ expando ] = doneName;
9105                      elem.sizset = i;
9106                  }
9107  
9108                  if ( elem.nodeName.toLowerCase() === cur ) {
9109                      match = elem;
9110                      break;
9111                  }
9112  
9113                  elem = elem[dir];
9114              }
9115  
9116              checkSet[i] = match;
9117          }
9118      }
9119  }
9120  
9121  function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9122      for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9123          var elem = checkSet[i];
9124  
9125          if ( elem ) {
9126              var match = false;
9127  
9128              elem = elem[dir];
9129  
9130              while ( elem ) {
9131                  if ( elem[ expando ] === doneName ) {
9132                      match = checkSet[elem.sizset];
9133                      break;
9134                  }
9135  
9136                  if ( elem.nodeType === 1 ) {
9137                      if ( !isXML ) {
9138                          elem[ expando ] = doneName;
9139                          elem.sizset = i;
9140                      }
9141  
9142                      if ( typeof cur !== "string" ) {
9143                          if ( elem === cur ) {
9144                              match = true;
9145                              break;
9146                          }
9147  
9148                      } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
9149                          match = elem;
9150                          break;
9151                      }
9152                  }
9153  
9154                  elem = elem[dir];
9155              }
9156  
9157              checkSet[i] = match;
9158          }
9159      }
9160  }
9161  
9162  if ( document.documentElement.contains ) {
9163      Sizzle.contains = function( a, b ) {
9164          return a !== b && (a.contains ? a.contains(b) : true);
9165      };
9166  
9167  } else if ( document.documentElement.compareDocumentPosition ) {
9168      Sizzle.contains = function( a, b ) {
9169          return !!(a.compareDocumentPosition(b) & 16);
9170      };
9171  
9172  } else {
9173      Sizzle.contains = function() {
9174          return false;
9175      };
9176  }
9177  
9178  Sizzle.isXML = function( elem ) {
9179      // documentElement is verified for cases where it doesn't yet exist
9180      // (such as loading iframes in IE - #4833)
9181      var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
9182  
9183      return documentElement ? documentElement.nodeName !== "HTML" : false;
9184  };
9185  
9186  var posProcess = function( selector, context, seed ) {
9187      var match,
9188          tmpSet = [],
9189          later = "",
9190          root = context.nodeType ? [context] : context;
9191  
9192      // Position selectors must be done after the filter
9193      // And so must :not(positional) so we move all PSEUDOs to the end
9194      while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
9195          later += match[0];
9196          selector = selector.replace( Expr.match.PSEUDO, "" );
9197      }
9198  
9199      selector = Expr.relative[selector] ? selector + "*" : selector;
9200  
9201      for ( var i = 0, l = root.length; i < l; i++ ) {
9202          Sizzle( selector, root[i], tmpSet, seed );
9203      }
9204  
9205      return Sizzle.filter( later, tmpSet );
9206  };
9207  
9208  // EXPOSE
9209  
9210  window.tinymce.dom.Sizzle = Sizzle;
9211  
9212  })();
9213  
9214  (function(tinymce) {
9215      tinymce.dom.Element = function(id, settings) {
9216          var t = this, dom, el;
9217  
9218          t.settings = settings = settings || {};
9219          t.id = id;
9220          t.dom = dom = settings.dom || tinymce.DOM;
9221  
9222          // Only IE leaks DOM references, this is a lot faster
9223          if (!tinymce.isIE)
9224              el = dom.get(t.id);
9225  
9226          tinymce.each(
9227                  ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
9228                  'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
9229                  'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
9230                  'isHidden,setHTML,get').split(/,/), function(k) {
9231                      t[k] = function() {
9232                          var a = [id], i;
9233  
9234                          for (i = 0; i < arguments.length; i++)
9235                              a.push(arguments[i]);
9236  
9237                          a = dom[k].apply(dom, a);
9238                          t.update(k);
9239  
9240                          return a;
9241                      };
9242              }
9243          );
9244  
9245          tinymce.extend(t, {
9246              on : function(n, f, s) {
9247                  return tinymce.dom.Event.add(t.id, n, f, s);
9248              },
9249  
9250              getXY : function() {
9251                  return {
9252                      x : parseInt(t.getStyle('left')),
9253                      y : parseInt(t.getStyle('top'))
9254                  };
9255              },
9256  
9257              getSize : function() {
9258                  var n = dom.get(t.id);
9259  
9260                  return {
9261                      w : parseInt(t.getStyle('width') || n.clientWidth),
9262                      h : parseInt(t.getStyle('height') || n.clientHeight)
9263                  };
9264              },
9265  
9266              moveTo : function(x, y) {
9267                  t.setStyles({left : x, top : y});
9268              },
9269  
9270              moveBy : function(x, y) {
9271                  var p = t.getXY();
9272  
9273                  t.moveTo(p.x + x, p.y + y);
9274              },
9275  
9276              resizeTo : function(w, h) {
9277                  t.setStyles({width : w, height : h});
9278              },
9279  
9280              resizeBy : function(w, h) {
9281                  var s = t.getSize();
9282  
9283                  t.resizeTo(s.w + w, s.h + h);
9284              },
9285  
9286              update : function(k) {
9287                  var b;
9288  
9289                  if (tinymce.isIE6 && settings.blocker) {
9290                      k = k || '';
9291  
9292                      // Ignore getters
9293                      if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9294                          return;
9295  
9296                      // Remove blocker on remove
9297                      if (k == 'remove') {
9298                          dom.remove(t.blocker);
9299                          return;
9300                      }
9301  
9302                      if (!t.blocker) {
9303                          t.blocker = dom.uniqueId();
9304                          b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9305                          dom.setStyle(b, 'opacity', 0);
9306                      } else
9307                          b = dom.get(t.blocker);
9308  
9309                      dom.setStyles(b, {
9310                          left : t.getStyle('left', 1),
9311                          top : t.getStyle('top', 1),
9312                          width : t.getStyle('width', 1),
9313                          height : t.getStyle('height', 1),
9314                          display : t.getStyle('display', 1),
9315                          zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9316                      });
9317                  }
9318              }
9319          });
9320      };
9321  })(tinymce);
9322  (function(tinymce) {
9323  	function trimNl(s) {
9324          return s.replace(/[\n\r]+/g, '');
9325      };
9326  
9327      // Shorten names
9328      var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9329  
9330      tinymce.create('tinymce.dom.Selection', {
9331          Selection : function(dom, win, serializer, editor) {
9332              var t = this;
9333  
9334              t.dom = dom;
9335              t.win = win;
9336              t.serializer = serializer;
9337              t.editor = editor;
9338  
9339              // Add events
9340              each([
9341                  'onBeforeSetContent',
9342  
9343                  'onBeforeGetContent',
9344  
9345                  'onSetContent',
9346  
9347                  'onGetContent'
9348              ], function(e) {
9349                  t[e] = new tinymce.util.Dispatcher(t);
9350              });
9351  
9352              // No W3C Range support
9353              if (!t.win.getSelection)
9354                  t.tridentSel = new tinymce.dom.TridentSelection(t);
9355  
9356              if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel)
9357                  this._fixIESelection();
9358  
9359              // Prevent leaks
9360              tinymce.addUnload(t.destroy, t);
9361          },
9362  
9363          setCursorLocation: function(node, offset) {
9364              var t = this; var r = t.dom.createRng();
9365              r.setStart(node, offset);
9366              r.setEnd(node, offset);
9367              t.setRng(r);
9368              t.collapse(false);
9369          },
9370          getContent : function(s) {
9371              var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9372  
9373              s = s || {};
9374              wb = wa = '';
9375              s.get = true;
9376              s.format = s.format || 'html';
9377              s.forced_root_block = '';
9378              t.onBeforeGetContent.dispatch(t, s);
9379  
9380              if (s.format == 'text')
9381                  return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9382  
9383              if (r.cloneContents) {
9384                  n = r.cloneContents();
9385  
9386                  if (n)
9387                      e.appendChild(n);
9388              } else if (is(r.item) || is(r.htmlText)) {
9389                  // IE will produce invalid markup if elements are present that
9390                  // it doesn't understand like custom elements or HTML5 elements.
9391                  // Adding a BR in front of the contents and then remoiving it seems to fix it though.
9392                  e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9393                  e.removeChild(e.firstChild);
9394              } else
9395                  e.innerHTML = r.toString();
9396  
9397              // Keep whitespace before and after
9398              if (/^\s/.test(e.innerHTML))
9399                  wb = ' ';
9400  
9401              if (/\s+$/.test(e.innerHTML))
9402                  wa = ' ';
9403  
9404              s.getInner = true;
9405  
9406              s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9407              t.onGetContent.dispatch(t, s);
9408  
9409              return s.content;
9410          },
9411  
9412          setContent : function(content, args) {
9413              var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9414  
9415              args = args || {format : 'html'};
9416              args.set = true;
9417              content = args.content = content;
9418  
9419              // Dispatch before set content event
9420              if (!args.no_events)
9421                  self.onBeforeSetContent.dispatch(self, args);
9422  
9423              content = args.content;
9424  
9425              if (rng.insertNode) {
9426                  // Make caret marker since insertNode places the caret in the beginning of text after insert
9427                  content += '<span id="__caret">_</span>';
9428  
9429                  // Delete and insert new node
9430                  if (rng.startContainer == doc && rng.endContainer == doc) {
9431                      // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9432                      doc.body.innerHTML = content;
9433                  } else {
9434                      rng.deleteContents();
9435  
9436                      if (doc.body.childNodes.length === 0) {
9437                          doc.body.innerHTML = content;
9438                      } else {
9439                          // createContextualFragment doesn't exists in IE 9 DOMRanges
9440                          if (rng.createContextualFragment) {
9441                              rng.insertNode(rng.createContextualFragment(content));
9442                          } else {
9443                              // Fake createContextualFragment call in IE 9
9444                              frag = doc.createDocumentFragment();
9445                              temp = doc.createElement('div');
9446  
9447                              frag.appendChild(temp);
9448                              temp.outerHTML = content;
9449  
9450                              rng.insertNode(frag);
9451                          }
9452                      }
9453                  }
9454  
9455                  // Move to caret marker
9456                  caretNode = self.dom.get('__caret');
9457  
9458                  // Make sure we wrap it compleatly, Opera fails with a simple select call
9459                  rng = doc.createRange();
9460                  rng.setStartBefore(caretNode);
9461                  rng.setEndBefore(caretNode);
9462                  self.setRng(rng);
9463  
9464                  // Remove the caret position
9465                  self.dom.remove('__caret');
9466  
9467                  try {
9468                      self.setRng(rng);
9469                  } catch (ex) {
9470                      // Might fail on Opera for some odd reason
9471                  }
9472              } else {
9473                  if (rng.item) {
9474                      // Delete content and get caret text selection
9475                      doc.execCommand('Delete', false, null);
9476                      rng = self.getRng();
9477                  }
9478  
9479                  // Explorer removes spaces from the beginning of pasted contents
9480                  if (/^\s+/.test(content)) {
9481                      rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9482                      self.dom.remove('__mce_tmp');
9483                  } else
9484                      rng.pasteHTML(content);
9485              }
9486  
9487              // Dispatch set content event
9488              if (!args.no_events)
9489                  self.onSetContent.dispatch(self, args);
9490          },
9491  
9492          getStart : function() {
9493              var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9494  
9495              if (rng.duplicate || rng.item) {
9496                  // Control selection, return first item
9497                  if (rng.item)
9498                      return rng.item(0);
9499  
9500                  // Get start element
9501                  checkRng = rng.duplicate();
9502                  checkRng.collapse(1);
9503                  startElement = checkRng.parentElement();
9504                  if (startElement.ownerDocument !== self.dom.doc) {
9505                      startElement = self.dom.getRoot();
9506                  }
9507  
9508                  // Check if range parent is inside the start element, then return the inner parent element
9509                  // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9510                  parentElement = node = rng.parentElement();
9511                  while (node = node.parentNode) {
9512                      if (node == startElement) {
9513                          startElement = parentElement;
9514                          break;
9515                      }
9516                  }
9517  
9518                  return startElement;
9519              } else {
9520                  startElement = rng.startContainer;
9521  
9522                  if (startElement.nodeType == 1 && startElement.hasChildNodes())
9523                      startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9524  
9525                  if (startElement && startElement.nodeType == 3)
9526                      return startElement.parentNode;
9527  
9528                  return startElement;
9529              }
9530          },
9531  
9532          getEnd : function() {
9533              var self = this, rng = self.getRng(), endElement, endOffset;
9534  
9535              if (rng.duplicate || rng.item) {
9536                  if (rng.item)
9537                      return rng.item(0);
9538  
9539                  rng = rng.duplicate();
9540                  rng.collapse(0);
9541                  endElement = rng.parentElement();
9542                  if (endElement.ownerDocument !== self.dom.doc) {
9543                      endElement = self.dom.getRoot();
9544                  }
9545  
9546                  if (endElement && endElement.nodeName == 'BODY')
9547                      return endElement.lastChild || endElement;
9548  
9549                  return endElement;
9550              } else {
9551                  endElement = rng.endContainer;
9552                  endOffset = rng.endOffset;
9553  
9554                  if (endElement.nodeType == 1 && endElement.hasChildNodes())
9555                      endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9556  
9557                  if (endElement && endElement.nodeType == 3)
9558                      return endElement.parentNode;
9559  
9560                  return endElement;
9561              }
9562          },
9563  
9564          getBookmark : function(type, normalized) {
9565              var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9566  
9567  			function findIndex(name, element) {
9568                  var index = 0;
9569  
9570                  each(dom.select(name), function(node, i) {
9571                      if (node == element)
9572                          index = i;
9573                  });
9574  
9575                  return index;
9576              };
9577  
9578  			function normalizeTableCellSelection(rng) {
9579  				function moveEndPoint(start) {
9580                      var container, offset, childNodes, prefix = start ? 'start' : 'end';
9581  
9582                      container = rng[prefix + 'Container'];
9583                      offset = rng[prefix + 'Offset'];
9584  
9585                      if (container.nodeType == 1 && container.nodeName == "TR") {
9586                          childNodes = container.childNodes;
9587                          container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9588                          if (container) {
9589                              offset = start ? 0 : container.childNodes.length;
9590                              rng['set' + (start ? 'Start' : 'End')](container, offset);
9591                          }
9592                      }
9593                  };
9594  
9595                  moveEndPoint(true);
9596                  moveEndPoint();
9597  
9598                  return rng;
9599              };
9600  
9601  			function getLocation() {
9602                  var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9603  
9604  				function getPoint(rng, start) {
9605                      var container = rng[start ? 'startContainer' : 'endContainer'],
9606                          offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9607  
9608                      if (container.nodeType == 3) {
9609                          if (normalized) {
9610                              for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9611                                  offset += node.nodeValue.length;
9612                          }
9613  
9614                          point.push(offset);
9615                      } else {
9616                          childNodes = container.childNodes;
9617  
9618                          if (offset >= childNodes.length && childNodes.length) {
9619                              after = 1;
9620                              offset = Math.max(0, childNodes.length - 1);
9621                          }
9622  
9623                          point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9624                      }
9625  
9626                      for (; container && container != root; container = container.parentNode)
9627                          point.push(t.dom.nodeIndex(container, normalized));
9628  
9629                      return point;
9630                  };
9631  
9632                  bookmark.start = getPoint(rng, true);
9633  
9634                  if (!t.isCollapsed())
9635                      bookmark.end = getPoint(rng);
9636  
9637                  return bookmark;
9638              };
9639  
9640              if (type == 2) {
9641                  if (t.tridentSel)
9642                      return t.tridentSel.getBookmark(type);
9643  
9644                  return getLocation();
9645              }
9646  
9647              // Handle simple range
9648              if (type) {
9649                  rng = t.getRng();
9650  
9651                  if (rng.setStart) {
9652                      rng = {
9653                          startContainer: rng.startContainer,
9654                          startOffset: rng.startOffset,
9655                          endContainer: rng.endContainer,
9656                          endOffset: rng.endOffset
9657                      };
9658                  }
9659  
9660                  return {rng : rng};
9661              }
9662  
9663              rng = t.getRng();
9664              id = dom.uniqueId();
9665              collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9666              styles = 'overflow:hidden;line-height:0px';
9667  
9668              // Explorer method
9669              if (rng.duplicate || rng.item) {
9670                  // Text selection
9671                  if (!rng.item) {
9672                      rng2 = rng.duplicate();
9673  
9674                      try {
9675                          // Insert start marker
9676                          rng.collapse();
9677                          rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9678  
9679                          // Insert end marker
9680                          if (!collapsed) {
9681                              rng2.collapse(false);
9682  
9683                              // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9684                              rng.moveToElementText(rng2.parentElement());
9685                              if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9686                                  rng2.move('character', -1);
9687  
9688                              rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9689                          }
9690                      } catch (ex) {
9691                          // IE might throw unspecified error so lets ignore it
9692                          return null;
9693                      }
9694                  } else {
9695                      // Control selection
9696                      element = rng.item(0);
9697                      name = element.nodeName;
9698  
9699                      return {name : name, index : findIndex(name, element)};
9700                  }
9701              } else {
9702                  element = t.getNode();
9703                  name = element.nodeName;
9704                  if (name == 'IMG')
9705                      return {name : name, index : findIndex(name, element)};
9706  
9707                  // W3C method
9708                  rng2 = normalizeTableCellSelection(rng.cloneRange());
9709  
9710                  // Insert end marker
9711                  if (!collapsed) {
9712                      rng2.collapse(false);
9713                      rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9714                  }
9715  
9716                  rng = normalizeTableCellSelection(rng);
9717                  rng.collapse(true);
9718                  rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9719              }
9720  
9721              t.moveToBookmark({id : id, keep : 1});
9722  
9723              return {id : id};
9724          },
9725  
9726          moveToBookmark : function(bookmark) {
9727              var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset;
9728  
9729  			function setEndPoint(start) {
9730                  var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9731  
9732                  if (point) {
9733                      offset = point[0];
9734  
9735                      // Find container node
9736                      for (node = root, i = point.length - 1; i >= 1; i--) {
9737                          children = node.childNodes;
9738  
9739                          if (point[i] > children.length - 1)
9740                              return;
9741  
9742                          node = children[point[i]];
9743                      }
9744  
9745                      // Move text offset to best suitable location
9746                      if (node.nodeType === 3)
9747                          offset = Math.min(point[0], node.nodeValue.length);
9748  
9749                      // Move element offset to best suitable location
9750                      if (node.nodeType === 1)
9751                          offset = Math.min(point[0], node.childNodes.length);
9752  
9753                      // Set offset within container node
9754                      if (start)
9755                          rng.setStart(node, offset);
9756                      else
9757                          rng.setEnd(node, offset);
9758                  }
9759  
9760                  return true;
9761              };
9762  
9763  			function restoreEndPoint(suffix) {
9764                  var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9765  
9766                  if (marker) {
9767                      node = marker.parentNode;
9768  
9769                      if (suffix == 'start') {
9770                          if (!keep) {
9771                              idx = dom.nodeIndex(marker);
9772                          } else {
9773                              node = marker.firstChild;
9774                              idx = 1;
9775                          }
9776  
9777                          startContainer = endContainer = node;
9778                          startOffset = endOffset = idx;
9779                      } else {
9780                          if (!keep) {
9781                              idx = dom.nodeIndex(marker);
9782                          } else {
9783                              node = marker.firstChild;
9784                              idx = 1;
9785                          }
9786  
9787                          endContainer = node;
9788                          endOffset = idx;
9789                      }
9790  
9791                      if (!keep) {
9792                          prev = marker.previousSibling;
9793                          next = marker.nextSibling;
9794  
9795                          // Remove all marker text nodes
9796                          each(tinymce.grep(marker.childNodes), function(node) {
9797                              if (node.nodeType == 3)
9798                                  node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9799                          });
9800  
9801                          // Remove marker but keep children if for example contents where inserted into the marker
9802                          // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9803                          while (marker = dom.get(bookmark.id + '_' + suffix))
9804                              dom.remove(marker, 1);
9805  
9806                          // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9807                          // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9808                          if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9809                              idx = prev.nodeValue.length;
9810                              prev.appendData(next.nodeValue);
9811                              dom.remove(next);
9812  
9813                              if (suffix == 'start') {
9814                                  startContainer = endContainer = prev;
9815                                  startOffset = endOffset = idx;
9816                              } else {
9817                                  endContainer = prev;
9818                                  endOffset = idx;
9819                              }
9820                          }
9821                      }
9822                  }
9823              };
9824  
9825  			function addBogus(node) {
9826                  // Adds a bogus BR element for empty block elements
9827                  if (dom.isBlock(node) && !node.innerHTML && !isIE)
9828                      node.innerHTML = '<br data-mce-bogus="1" />';
9829  
9830                  return node;
9831              };
9832  
9833              if (bookmark) {
9834                  if (bookmark.start) {
9835                      rng = dom.createRng();
9836                      root = dom.getRoot();
9837  
9838                      if (t.tridentSel)
9839                          return t.tridentSel.moveToBookmark(bookmark);
9840  
9841                      if (setEndPoint(true) && setEndPoint()) {
9842                          t.setRng(rng);
9843                      }
9844                  } else if (bookmark.id) {
9845                      // Restore start/end points
9846                      restoreEndPoint('start');
9847                      restoreEndPoint('end');
9848  
9849                      if (startContainer) {
9850                          rng = dom.createRng();
9851                          rng.setStart(addBogus(startContainer), startOffset);
9852                          rng.setEnd(addBogus(endContainer), endOffset);
9853                          t.setRng(rng);
9854                      }
9855                  } else if (bookmark.name) {
9856                      t.select(dom.select(bookmark.name)[bookmark.index]);
9857                  } else if (bookmark.rng) {
9858                      rng = bookmark.rng;
9859  
9860                      if (rng.startContainer) {
9861                          rng2 = t.dom.createRng();
9862  
9863                          try {
9864                              rng2.setStart(rng.startContainer, rng.startOffset);
9865                              rng2.setEnd(rng.endContainer, rng.endOffset);
9866                          } catch (e) {
9867                              // Might fail with index error
9868                          }
9869  
9870                          rng = rng2;
9871                      }
9872  
9873                      t.setRng(rng);
9874                  }
9875              }
9876          },
9877  
9878          select : function(node, content) {
9879              var t = this, dom = t.dom, rng = dom.createRng(), idx;
9880  
9881  			function setPoint(node, start) {
9882                  var walker = new TreeWalker(node, node);
9883  
9884                  do {
9885                      // Text node
9886                      if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9887                          if (start)
9888                              rng.setStart(node, 0);
9889                          else
9890                              rng.setEnd(node, node.nodeValue.length);
9891  
9892                          return;
9893                      }
9894  
9895                      // BR element
9896                      if (node.nodeName == 'BR') {
9897                          if (start)
9898                              rng.setStartBefore(node);
9899                          else
9900                              rng.setEndBefore(node);
9901  
9902                          return;
9903                      }
9904                  } while (node = (start ? walker.next() : walker.prev()));
9905              };
9906  
9907              if (node) {
9908                  idx = dom.nodeIndex(node);
9909                  rng.setStart(node.parentNode, idx);
9910                  rng.setEnd(node.parentNode, idx + 1);
9911  
9912                  // Find first/last text node or BR element
9913                  if (content) {
9914                      setPoint(node, 1);
9915                      setPoint(node);
9916                  }
9917  
9918                  t.setRng(rng);
9919              }
9920  
9921              return node;
9922          },
9923  
9924          isCollapsed : function() {
9925              var t = this, r = t.getRng(), s = t.getSel();
9926  
9927              if (!r || r.item)
9928                  return false;
9929  
9930              if (r.compareEndPoints)
9931                  return r.compareEndPoints('StartToEnd', r) === 0;
9932  
9933              return !s || r.collapsed;
9934          },
9935  
9936          collapse : function(to_start) {
9937              var self = this, rng = self.getRng(), node;
9938  
9939              // Control range on IE
9940              if (rng.item) {
9941                  node = rng.item(0);
9942                  rng = self.win.document.body.createTextRange();
9943                  rng.moveToElementText(node);
9944              }
9945  
9946              rng.collapse(!!to_start);
9947              self.setRng(rng);
9948          },
9949  
9950          getSel : function() {
9951              var t = this, w = this.win;
9952  
9953              return w.getSelection ? w.getSelection() : w.document.selection;
9954          },
9955  
9956          getRng : function(w3c) {
9957              var self = this, selection, rng, elm, doc = self.win.document;
9958  
9959              // Found tridentSel object then we need to use that one
9960              if (w3c && self.tridentSel) {
9961                  return self.tridentSel.getRangeAt(0);
9962              }
9963  
9964              try {
9965                  if (selection = self.getSel()) {
9966                      rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9967                  }
9968              } catch (ex) {
9969                  // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9970              }
9971  
9972              // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
9973              if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) {
9974                  elm = doc.selection.createRange().item(0);
9975                  rng = doc.createRange();
9976                  rng.setStartBefore(elm);
9977                  rng.setEndAfter(elm);
9978              }
9979  
9980              // No range found then create an empty one
9981              // This can occur when the editor is placed in a hidden container element on Gecko
9982              // Or on IE when there was an exception
9983              if (!rng) {
9984                  rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
9985              }
9986  
9987              // If range is at start of document then move it to start of body
9988              if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
9989                  elm = self.dom.getRoot();
9990                  rng.setStart(elm, 0);
9991                  rng.setEnd(elm, 0);
9992              }
9993  
9994              if (self.selectedRange && self.explicitRange) {
9995                  if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
9996                      // Safari, Opera and Chrome only ever select text which causes the range to change.
9997                      // This lets us use the originally set range if the selection hasn't been changed by the user.
9998                      rng = self.explicitRange;
9999                  } else {
10000                      self.selectedRange = null;
10001                      self.explicitRange = null;
10002                  }
10003              }
10004  
10005              return rng;
10006          },
10007  
10008          setRng : function(r, forward) {
10009              var s, t = this;
10010  
10011              if (!t.tridentSel) {
10012                  s = t.getSel();
10013  
10014                  if (s) {
10015                      t.explicitRange = r;
10016  
10017                      try {
10018                          s.removeAllRanges();
10019                      } catch (ex) {
10020                          // IE9 might throw errors here don't know why
10021                      }
10022  
10023                      s.addRange(r);
10024  
10025                      // Forward is set to false and we have an extend function
10026                      if (forward === false && s.extend) {
10027                          s.collapse(r.endContainer, r.endOffset);
10028                          s.extend(r.startContainer, r.startOffset);
10029                      }
10030  
10031                      // adding range isn't always successful so we need to check range count otherwise an exception can occur
10032                      t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
10033                  }
10034              } else {
10035                  // Is W3C Range
10036                  if (r.cloneRange) {
10037                      try {
10038                          t.tridentSel.addRange(r);
10039                          return;
10040                      } catch (ex) {
10041                          //IE9 throws an error here if called before selection is placed in the editor
10042                      }
10043                  }
10044  
10045                  // Is IE specific range
10046                  try {
10047                      r.select();
10048                  } catch (ex) {
10049                      // Needed for some odd IE bug #1843306
10050                  }
10051              }
10052          },
10053  
10054          setNode : function(n) {
10055              var t = this;
10056  
10057              t.setContent(t.dom.getOuterHTML(n));
10058  
10059              return n;
10060          },
10061  
10062          getNode : function() {
10063              var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
10064  
10065  			function skipEmptyTextNodes(n, forwards) {
10066                  var orig = n;
10067                  while (n && n.nodeType === 3 && n.length === 0) {
10068                      n = forwards ? n.nextSibling : n.previousSibling;
10069                  }
10070                  return n || orig;
10071              };
10072  
10073              // Range maybe lost after the editor is made visible again
10074              if (!rng)
10075                  return t.dom.getRoot();
10076  
10077              if (rng.setStart) {
10078                  elm = rng.commonAncestorContainer;
10079  
10080                  // Handle selection a image or other control like element such as anchors
10081                  if (!rng.collapsed) {
10082                      if (rng.startContainer == rng.endContainer) {
10083                          if (rng.endOffset - rng.startOffset < 2) {
10084                              if (rng.startContainer.hasChildNodes())
10085                                  elm = rng.startContainer.childNodes[rng.startOffset];
10086                          }
10087                      }
10088  
10089                      // If the anchor node is a element instead of a text node then return this element
10090                      //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
10091                      //    return sel.anchorNode.childNodes[sel.anchorOffset];
10092  
10093                      // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
10094                      // This happens when you double click an underlined word in FireFox.
10095                      if (start.nodeType === 3 && end.nodeType === 3) {
10096                          if (start.length === rng.startOffset) {
10097                              start = skipEmptyTextNodes(start.nextSibling, true);
10098                          } else {
10099                              start = start.parentNode;
10100                          }
10101                          if (rng.endOffset === 0) {
10102                              end = skipEmptyTextNodes(end.previousSibling, false);
10103                          } else {
10104                              end = end.parentNode;
10105                          }
10106  
10107                          if (start && start === end)
10108                              return start;
10109                      }
10110                  }
10111  
10112                  if (elm && elm.nodeType == 3)
10113                      return elm.parentNode;
10114  
10115                  return elm;
10116              }
10117  
10118              return rng.item ? rng.item(0) : rng.parentElement();
10119          },
10120  
10121          getSelectedBlocks : function(st, en) {
10122              var t = this, dom = t.dom, sb, eb, n, bl = [];
10123  
10124              sb = dom.getParent(st || t.getStart(), dom.isBlock);
10125              eb = dom.getParent(en || t.getEnd(), dom.isBlock);
10126  
10127              if (sb)
10128                  bl.push(sb);
10129  
10130              if (sb && eb && sb != eb) {
10131                  n = sb;
10132  
10133                  var walker = new TreeWalker(sb, dom.getRoot());
10134                  while ((n = walker.next()) && n != eb) {
10135                      if (dom.isBlock(n))
10136                          bl.push(n);
10137                  }
10138              }
10139  
10140              if (eb && sb != eb)
10141                  bl.push(eb);
10142  
10143              return bl;
10144          },
10145  
10146          isForward: function(){
10147              var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
10148  
10149              // No support for selection direction then always return true
10150              if (!sel || sel.anchorNode == null || sel.focusNode == null) {
10151                  return true;
10152              }
10153  
10154              anchorRange = dom.createRng();
10155              anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
10156              anchorRange.collapse(true);
10157  
10158              focusRange = dom.createRng();
10159              focusRange.setStart(sel.focusNode, sel.focusOffset);
10160              focusRange.collapse(true);
10161  
10162              return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
10163          },
10164  
10165          normalize : function() {
10166              var self = this, rng, normalized, collapsed, node, sibling;
10167  
10168  			function normalizeEndPoint(start) {
10169                  var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
10170  
10171  				function hasBrBeforeAfter(node, left) {
10172                      var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
10173  
10174                      while (node = walker[left ? 'prev' : 'next']()) {
10175                          if (node.nodeName === "BR") {
10176                              return true;
10177                          }
10178                      }
10179                  };
10180  
10181                  // Walks the dom left/right to find a suitable text node to move the endpoint into
10182                  // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
10183  				function findTextNodeRelative(left, startNode) {
10184                      var walker, lastInlineElement;
10185  
10186                      startNode = startNode || container;
10187                      walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
10188  
10189                      // Walk left until we hit a text node we can move to or a block/br/img
10190                      while (node = walker[left ? 'prev' : 'next']()) {
10191                          // Found text node that has a length
10192                          if (node.nodeType === 3 && node.nodeValue.length > 0) {
10193                              container = node;
10194                              offset = left ? node.nodeValue.length : 0;
10195                              normalized = true;
10196                              return;
10197                          }
10198  
10199                          // Break if we find a block or a BR/IMG/INPUT etc
10200                          if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10201                              return;
10202                          }
10203  
10204                          lastInlineElement = node;
10205                      }
10206  
10207                      // Only fetch the last inline element when in caret mode for now
10208                      if (collapsed && lastInlineElement) {
10209                          container = lastInlineElement;
10210                          normalized = true;
10211                          offset = 0;
10212                      }
10213                  };
10214  
10215                  container = rng[(start ? 'start' : 'end') + 'Container'];
10216                  offset = rng[(start ? 'start' : 'end') + 'Offset'];
10217                  nonEmptyElementsMap = dom.schema.getNonEmptyElements();
10218  
10219                  // If the container is a document move it to the body element
10220                  if (container.nodeType === 9) {
10221                      container = dom.getRoot();
10222                      offset = 0;
10223                  }
10224  
10225                  // If the container is body try move it into the closest text node or position
10226                  if (container === body) {
10227                      // If start is before/after a image, table etc
10228                      if (start) {
10229                          node = container.childNodes[offset > 0 ? offset - 1 : 0];
10230                          if (node) {
10231                              nodeName = node.nodeName.toLowerCase();
10232                              if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
10233                                  return;
10234                              }
10235                          }
10236                      }
10237  
10238                      // Resolve the index
10239                      if (container.hasChildNodes()) {
10240                          container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
10241                          offset = 0;
10242  
10243                          // Don't walk into elements that doesn't have any child nodes like a IMG
10244                          if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
10245                              // Walk the DOM to find a text node to place the caret at or a BR
10246                              node = container;
10247                              walker = new TreeWalker(container, body);
10248  
10249                              do {
10250                                  // Found a text node use that position
10251                                  if (node.nodeType === 3 && node.nodeValue.length > 0) {
10252                                      offset = start ? 0 : node.nodeValue.length;
10253                                      container = node;
10254                                      normalized = true;
10255                                      break;
10256                                  }
10257  
10258                                  // Found a BR/IMG element that we can place the caret before
10259                                  if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10260                                      offset = dom.nodeIndex(node);
10261                                      container = node.parentNode;
10262  
10263                                      // Put caret after image when moving the end point
10264                                      if (node.nodeName ==  "IMG" && !start) {
10265                                          offset++;
10266                                      }
10267  
10268                                      normalized = true;
10269                                      break;
10270                                  }
10271                              } while (node = (start ? walker.next() : walker.prev()));
10272                          }
10273                      }
10274                  }
10275  
10276                  // Lean the caret to the left if possible
10277                  if (collapsed) {
10278                      // So this: <b>x</b><i>|x</i>
10279                      // Becomes: <b>x|</b><i>x</i>
10280                      // Seems that only gecko has issues with this
10281                      if (container.nodeType === 3 && offset === 0) {
10282                          findTextNodeRelative(true);
10283                      }
10284  
10285                      // Lean left into empty inline elements when the caret is before a BR
10286                      // So this: <i><b></b><i>|<br></i>
10287                      // Becomes: <i><b>|</b><i><br></i>
10288                      // Seems that only gecko has issues with this
10289                      if (container.nodeType === 1) {
10290                          node = container.childNodes[offset];
10291                          if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10292                              findTextNodeRelative(true, container.childNodes[offset]);
10293                          }
10294                      }
10295                  }
10296  
10297                  // Lean the start of the selection right if possible
10298                  // So this: x[<b>x]</b>
10299                  // Becomes: x<b>[x]</b>
10300                  if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10301                      findTextNodeRelative(false);
10302                  }
10303  
10304                  // Set endpoint if it was normalized
10305                  if (normalized)
10306                      rng['set' + (start ? 'Start' : 'End')](container, offset);
10307              };
10308  
10309              // Normalize only on non IE browsers for now
10310              if (tinymce.isIE)
10311                  return;
10312              
10313              rng = self.getRng();
10314              collapsed = rng.collapsed;
10315  
10316              // Normalize the end points
10317              normalizeEndPoint(true);
10318  
10319              if (!collapsed)
10320                  normalizeEndPoint();
10321  
10322              // Set the selection if it was normalized
10323              if (normalized) {
10324                  // If it was collapsed then make sure it still is
10325                  if (collapsed) {
10326                      rng.collapse(true);
10327                  }
10328  
10329                  //console.log(self.dom.dumpRng(rng));
10330                  self.setRng(rng, self.isForward());
10331              }
10332          },
10333  
10334          selectorChanged: function(selector, callback) {
10335              var self = this, currentSelectors;
10336  
10337              if (!self.selectorChangedData) {
10338                  self.selectorChangedData = {};
10339                  currentSelectors = {};
10340  
10341                  self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10342                      var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10343  
10344                      // Check for new matching selectors
10345                      each(self.selectorChangedData, function(callbacks, selector) {
10346                          each(parents, function(node) {
10347                              if (dom.is(node, selector)) {
10348                                  if (!currentSelectors[selector]) {
10349                                      // Execute callbacks
10350                                      each(callbacks, function(callback) {
10351                                          callback(true, {node: node, selector: selector, parents: parents});
10352                                      });
10353  
10354                                      currentSelectors[selector] = callbacks;
10355                                  }
10356  
10357                                  matchedSelectors[selector] = callbacks;
10358                                  return false;
10359                              }
10360                          });
10361                      });
10362  
10363                      // Check if current selectors still match
10364                      each(currentSelectors, function(callbacks, selector) {
10365                          if (!matchedSelectors[selector]) {
10366                              delete currentSelectors[selector];
10367  
10368                              each(callbacks, function(callback) {
10369                                  callback(false, {node: node, selector: selector, parents: parents});
10370                              });
10371                          }
10372                      });
10373                  });
10374              }
10375  
10376              // Add selector listeners
10377              if (!self.selectorChangedData[selector]) {
10378                  self.selectorChangedData[selector] = [];
10379              }
10380  
10381              self.selectorChangedData[selector].push(callback);
10382  
10383              return self;
10384          },
10385  
10386          scrollIntoView: function(elm) {
10387              var y, viewPort, self = this, dom = self.dom;
10388  
10389              viewPort = dom.getViewPort(self.editor.getWin());
10390              y = dom.getPos(elm).y;
10391              if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
10392                  self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);
10393              }
10394          },
10395  
10396          destroy : function(manual) {
10397              var self = this;
10398  
10399              self.win = null;
10400  
10401              // Manual destroy then remove unload handler
10402              if (!manual)
10403                  tinymce.removeUnload(self.destroy);
10404          },
10405  
10406          // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10407          _fixIESelection : function() {
10408              var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10409  
10410              // Return range from point or null if it failed
10411  			function rngFromPoint(x, y) {
10412                  var rng = body.createTextRange();
10413  
10414                  try {
10415                      rng.moveToPoint(x, y);
10416                  } catch (ex) {
10417                      // IE sometimes throws and exception, so lets just ignore it
10418                      rng = null;
10419                  }
10420  
10421                  return rng;
10422              };
10423  
10424              // Fires while the selection is changing
10425  			function selectionChange(e) {
10426                  var pointRng;
10427  
10428                  // Check if the button is down or not
10429                  if (e.button) {
10430                      // Create range from mouse position
10431                      pointRng = rngFromPoint(e.x, e.y);
10432  
10433                      if (pointRng) {
10434                          // Check if pointRange is before/after selection then change the endPoint
10435                          if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10436                              pointRng.setEndPoint('StartToStart', startRng);
10437                          else
10438                              pointRng.setEndPoint('EndToEnd', startRng);
10439  
10440                          pointRng.select();
10441                      }
10442                  } else
10443                      endSelection();
10444              }
10445  
10446              // Removes listeners
10447  			function endSelection() {
10448                  var rng = doc.selection.createRange();
10449  
10450                  // If the range is collapsed then use the last start range
10451                  if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10452                      startRng.select();
10453  
10454                  dom.unbind(doc, 'mouseup', endSelection);
10455                  dom.unbind(doc, 'mousemove', selectionChange);
10456                  startRng = started = 0;
10457              };
10458  
10459              // Make HTML element unselectable since we are going to handle selection by hand
10460              doc.documentElement.unselectable = true;
10461              
10462              // Detect when user selects outside BODY
10463              dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10464                  if (e.target.nodeName === 'HTML') {
10465                      if (started)
10466                          endSelection();
10467  
10468                      // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10469                      htmlElm = doc.documentElement;
10470                      if (htmlElm.scrollHeight > htmlElm.clientHeight)
10471                          return;
10472  
10473                      started = 1;
10474                      // Setup start position
10475                      startRng = rngFromPoint(e.x, e.y);
10476                      if (startRng) {
10477                          // Listen for selection change events
10478                          dom.bind(doc, 'mouseup', endSelection);
10479                          dom.bind(doc, 'mousemove', selectionChange);
10480  
10481                          dom.win.focus();
10482                          startRng.select();
10483                      }
10484                  }
10485              });
10486          }
10487      });
10488  })(tinymce);
10489  (function(tinymce) {
10490      tinymce.dom.Serializer = function(settings, dom, schema) {
10491          var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10492  
10493          // Support the old apply_source_formatting option
10494          if (!settings.apply_source_formatting)
10495              settings.indent = false;
10496  
10497          // Default DOM and Schema if they are undefined
10498          dom = dom || tinymce.DOM;
10499          schema = schema || new tinymce.html.Schema(settings);
10500          settings.entity_encoding = settings.entity_encoding || 'named';
10501          settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10502  
10503          onPreProcess = new tinymce.util.Dispatcher(self);
10504  
10505          onPostProcess = new tinymce.util.Dispatcher(self);
10506  
10507          htmlParser = new tinymce.html.DomParser(settings, schema);
10508  
10509          // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10510          htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10511              var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10512  
10513              while (i--) {
10514                  node = nodes[i];
10515  
10516                  value = node.attributes.map[internalName];
10517                  if (value !== undef) {
10518                      // Set external name to internal value and remove internal
10519                      node.attr(name, value.length > 0 ? value : null);
10520                      node.attr(internalName, null);
10521                  } else {
10522                      // No internal attribute found then convert the value we have in the DOM
10523                      value = node.attributes.map[name];
10524  
10525                      if (name === "style")
10526                          value = dom.serializeStyle(dom.parseStyle(value), node.name);
10527                      else if (urlConverter)
10528                          value = urlConverter.call(urlConverterScope, value, name, node.name);
10529  
10530                      node.attr(name, value.length > 0 ? value : null);
10531                  }
10532              }
10533          });
10534  
10535          // Remove internal classes mceItem<..> or mceSelected
10536          htmlParser.addAttributeFilter('class', function(nodes, name) {
10537              var i = nodes.length, node, value;
10538  
10539              while (i--) {
10540                  node = nodes[i];
10541                  value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10542                  node.attr('class', value.length > 0 ? value : null);
10543              }
10544          });
10545  
10546          // Remove bookmark elements
10547          htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10548              var i = nodes.length, node;
10549  
10550              while (i--) {
10551                  node = nodes[i];
10552  
10553                  if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10554                      node.remove();
10555              }
10556          });
10557  
10558          // Remove expando attributes
10559          htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10560              var i = nodes.length;
10561  
10562              while (i--) {
10563                  nodes[i].attr(name, null);
10564              }
10565          });
10566  
10567          htmlParser.addNodeFilter('noscript', function(nodes) {
10568              var i = nodes.length, node;
10569  
10570              while (i--) {
10571                  node = nodes[i].firstChild;
10572  
10573                  if (node) {
10574                      node.value = tinymce.html.Entities.decode(node.value);
10575                  }
10576              }
10577          });
10578  
10579          // Force script into CDATA sections and remove the mce- prefix also add comments around styles
10580          htmlParser.addNodeFilter('script,style', function(nodes, name) {
10581              var i = nodes.length, node, value;
10582  
10583  			function trim(value) {
10584                  return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10585                          .replace(/^[\r\n]*|[\r\n]*$/g, '')
10586                          .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10587                          .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10588              };
10589  
10590              while (i--) {
10591                  node = nodes[i];
10592                  value = node.firstChild ? node.firstChild.value : '';
10593  
10594                  if (name === "script") {
10595                      // Remove mce- prefix from script elements
10596                      node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10597  
10598                      if (value.length > 0)
10599                          node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10600                  } else {
10601                      if (value.length > 0)
10602                          node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10603                  }
10604              }
10605          });
10606  
10607          // Convert comments to cdata and handle protected comments
10608          htmlParser.addNodeFilter('#comment', function(nodes, name) {
10609              var i = nodes.length, node;
10610  
10611              while (i--) {
10612                  node = nodes[i];
10613  
10614                  if (node.value.indexOf('[CDATA[') === 0) {
10615                      node.name = '#cdata';
10616                      node.type = 4;
10617                      node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10618                  } else if (node.value.indexOf('mce:protected ') === 0) {
10619                      node.name = "#text";
10620                      node.type = 3;
10621                      node.raw = true;
10622                      node.value = unescape(node.value).substr(14);
10623                  }
10624              }
10625          });
10626  
10627          htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10628              var i = nodes.length, node;
10629  
10630              while (i--) {
10631                  node = nodes[i];
10632                  if (node.type === 7)
10633                      node.remove();
10634                  else if (node.type === 1) {
10635                      if (name === "input" && !("type" in node.attributes.map))
10636                          node.attr('type', 'text');
10637                  }
10638              }
10639          });
10640  
10641          // Fix list elements, TODO: Replace this later
10642          if (settings.fix_list_elements) {
10643              htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10644                  var i = nodes.length, node, parentNode;
10645  
10646                  while (i--) {
10647                      node = nodes[i];
10648                      parentNode = node.parent;
10649  
10650                      if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10651                          if (node.prev && node.prev.name === 'li') {
10652                              node.prev.append(node);
10653                          }
10654                      }
10655                  }
10656              });
10657          }
10658  
10659          // Remove internal data attributes
10660          htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10661              var i = nodes.length;
10662  
10663              while (i--) {
10664                  nodes[i].attr(name, null);
10665              }
10666          });
10667  
10668          // Return public methods
10669          return {
10670              schema : schema,
10671  
10672              addNodeFilter : htmlParser.addNodeFilter,
10673  
10674              addAttributeFilter : htmlParser.addAttributeFilter,
10675  
10676              onPreProcess : onPreProcess,
10677  
10678              onPostProcess : onPostProcess,
10679  
10680              serialize : function(node, args) {
10681                  var impl, doc, oldDoc, htmlSerializer, content;
10682  
10683                  // Explorer won't clone contents of script and style and the
10684                  // selected index of select elements are cleared on a clone operation.
10685                  if (isIE && dom.select('script,style,select,map').length > 0) {
10686                      content = node.innerHTML;
10687                      node = node.cloneNode(false);
10688                      dom.setHTML(node, content);
10689                  } else
10690                      node = node.cloneNode(true);
10691  
10692                  // Nodes needs to be attached to something in WebKit/Opera
10693                  // Older builds of Opera crashes if you attach the node to an document created dynamically
10694                  // and since we can't feature detect a crash we need to sniff the acutal build number
10695                  // This fix will make DOM ranges and make Sizzle happy!
10696                  impl = node.ownerDocument.implementation;
10697                  if (impl.createHTMLDocument) {
10698                      // Create an empty HTML document
10699                      doc = impl.createHTMLDocument("");
10700  
10701                      // Add the element or it's children if it's a body element to the new document
10702                      each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10703                          doc.body.appendChild(doc.importNode(node, true));
10704                      });
10705  
10706                      // Grab first child or body element for serialization
10707                      if (node.nodeName != 'BODY')
10708                          node = doc.body.firstChild;
10709                      else
10710                          node = doc.body;
10711  
10712                      // set the new document in DOMUtils so createElement etc works
10713                      oldDoc = dom.doc;
10714                      dom.doc = doc;
10715                  }
10716  
10717                  args = args || {};
10718                  args.format = args.format || 'html';
10719  
10720                  // Pre process
10721                  if (!args.no_events) {
10722                      args.node = node;
10723                      onPreProcess.dispatch(self, args);
10724                  }
10725  
10726                  // Setup serializer
10727                  htmlSerializer = new tinymce.html.Serializer(settings, schema);
10728  
10729                  // Parse and serialize HTML
10730                  args.content = htmlSerializer.serialize(
10731                      htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10732                  );
10733  
10734                  // Replace all BOM characters for now until we can find a better solution
10735                  if (!args.cleanup)
10736                      args.content = args.content.replace(/\uFEFF/g, '');
10737  
10738                  // Post process
10739                  if (!args.no_events)
10740                      onPostProcess.dispatch(self, args);
10741  
10742                  // Restore the old document if it was changed
10743                  if (oldDoc)
10744                      dom.doc = oldDoc;
10745  
10746                  args.node = null;
10747  
10748                  return args.content;
10749              },
10750  
10751              addRules : function(rules) {
10752                  schema.addValidElements(rules);
10753              },
10754  
10755              setRules : function(rules) {
10756                  schema.setValidElements(rules);
10757              }
10758          };
10759      };
10760  })(tinymce);
10761  (function(tinymce) {
10762      tinymce.dom.ScriptLoader = function(settings) {
10763          var QUEUED = 0,
10764              LOADING = 1,
10765              LOADED = 2,
10766              states = {},
10767              queue = [],
10768              scriptLoadedCallbacks = {},
10769              queueLoadedCallbacks = [],
10770              loading = 0,
10771              undef;
10772  
10773  		function loadScript(url, callback) {
10774              var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10775  
10776              // Execute callback when script is loaded
10777  			function done() {
10778                  dom.remove(id);
10779  
10780                  if (elm)
10781                      elm.onreadystatechange = elm.onload = elm = null;
10782  
10783                  callback();
10784              };
10785              
10786  			function error() {
10787                  // Report the error so it's easier for people to spot loading errors
10788                  if (typeof(console) !== "undefined" && console.log)
10789                      console.log("Failed to load: " + url);
10790  
10791                  // We can't mark it as done if there is a load error since
10792                  // A) We don't want to produce 404 errors on the server and
10793                  // B) the onerror event won't fire on all browsers.
10794                  // done();
10795              };
10796  
10797              id = dom.uniqueId();
10798  
10799              if (tinymce.isIE6) {
10800                  uri = new tinymce.util.URI(url);
10801                  loc = location;
10802  
10803                  // If script is from same domain and we
10804                  // use IE 6 then use XHR since it's more reliable
10805                  if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10806                      tinymce.util.XHR.send({
10807                          url : tinymce._addVer(uri.getURI()),
10808                          success : function(content) {
10809                              // Create new temp script element
10810                              var script = dom.create('script', {
10811                                  type : 'text/javascript'
10812                              });
10813  
10814                              // Evaluate script in global scope
10815                              script.text = content;
10816                              document.getElementsByTagName('head')[0].appendChild(script);
10817                              dom.remove(script);
10818  
10819                              done();
10820                          },
10821                          
10822                          error : error
10823                      });
10824  
10825                      return;
10826                  }
10827              }
10828  
10829              // Create new script element
10830              elm = document.createElement('script');
10831              elm.id = id;
10832              elm.type = 'text/javascript';
10833              elm.src = tinymce._addVer(url);
10834  
10835              // Add onload listener for non IE browsers since IE9
10836              // fires onload event before the script is parsed and executed
10837              if (!tinymce.isIE || tinymce.isIE11)
10838                  elm.onload = done;
10839  
10840              // Add onerror event will get fired on some browsers but not all of them
10841              elm.onerror = error;
10842  
10843              // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10844              if (!tinymce.isOpera) {
10845                  elm.onreadystatechange = function() {
10846                      var state = elm.readyState;
10847  
10848                      // Loaded state is passed on IE 6 however there
10849                      // are known issues with this method but we can't use
10850                      // XHR in a cross domain loading
10851                      if (state == 'complete' || state == 'loaded')
10852                          done();
10853                  };
10854              }
10855  
10856              // Most browsers support this feature so we report errors
10857              // for those at least to help users track their missing plugins etc
10858              // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10859              /*elm.onerror = function() {
10860                  alert('Failed to load: ' + url);
10861              };*/
10862  
10863              // Add script to document
10864              (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10865          };
10866  
10867          this.isDone = function(url) {
10868              return states[url] == LOADED;
10869          };
10870  
10871          this.markDone = function(url) {
10872              states[url] = LOADED;
10873          };
10874  
10875          this.add = this.load = function(url, callback, scope) {
10876              var item, state = states[url];
10877  
10878              // Add url to load queue
10879              if (state == undef) {
10880                  queue.push(url);
10881                  states[url] = QUEUED;
10882              }
10883  
10884              if (callback) {
10885                  // Store away callback for later execution
10886                  if (!scriptLoadedCallbacks[url])
10887                      scriptLoadedCallbacks[url] = [];
10888  
10889                  scriptLoadedCallbacks[url].push({
10890                      func : callback,
10891                      scope : scope || this
10892                  });
10893              }
10894          };
10895  
10896          this.loadQueue = function(callback, scope) {
10897              this.loadScripts(queue, callback, scope);
10898          };
10899  
10900          this.loadScripts = function(scripts, callback, scope) {
10901              var loadScripts;
10902  
10903  			function execScriptLoadedCallbacks(url) {
10904                  // Execute URL callback functions
10905                  tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10906                      callback.func.call(callback.scope);
10907                  });
10908  
10909                  scriptLoadedCallbacks[url] = undef;
10910              };
10911  
10912              queueLoadedCallbacks.push({
10913                  func : callback,
10914                  scope : scope || this
10915              });
10916  
10917              loadScripts = function() {
10918                  var loadingScripts = tinymce.grep(scripts);
10919  
10920                  // Current scripts has been handled
10921                  scripts.length = 0;
10922  
10923                  // Load scripts that needs to be loaded
10924                  tinymce.each(loadingScripts, function(url) {
10925                      // Script is already loaded then execute script callbacks directly
10926                      if (states[url] == LOADED) {
10927                          execScriptLoadedCallbacks(url);
10928                          return;
10929                      }
10930  
10931                      // Is script not loading then start loading it
10932                      if (states[url] != LOADING) {
10933                          states[url] = LOADING;
10934                          loading++;
10935  
10936                          loadScript(url, function() {
10937                              states[url] = LOADED;
10938                              loading--;
10939  
10940                              execScriptLoadedCallbacks(url);
10941  
10942                              // Load more scripts if they where added by the recently loaded script
10943                              loadScripts();
10944                          });
10945                      }
10946                  });
10947  
10948                  // No scripts are currently loading then execute all pending queue loaded callbacks
10949                  if (!loading) {
10950                      tinymce.each(queueLoadedCallbacks, function(callback) {
10951                          callback.func.call(callback.scope);
10952                      });
10953  
10954                      queueLoadedCallbacks.length = 0;
10955                  }
10956              };
10957  
10958              loadScripts();
10959          };
10960      };
10961  
10962      // Global script loader
10963      tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10964  })(tinymce);
10965  (function(tinymce) {
10966      tinymce.dom.RangeUtils = function(dom) {
10967          var INVISIBLE_CHAR = '\uFEFF';
10968  
10969          this.walk = function(rng, callback) {
10970              var startContainer = rng.startContainer,
10971                  startOffset = rng.startOffset,
10972                  endContainer = rng.endContainer,
10973                  endOffset = rng.endOffset,
10974                  ancestor, startPoint,
10975                  endPoint, node, parent, siblings, nodes;
10976  
10977              // Handle table cell selection the table plugin enables
10978              // you to fake select table cells and perform formatting actions on them
10979              nodes = dom.select('td.mceSelected,th.mceSelected');
10980              if (nodes.length > 0) {
10981                  tinymce.each(nodes, function(node) {
10982                      callback([node]);
10983                  });
10984  
10985                  return;
10986              }
10987  
10988  			function exclude(nodes) {
10989                  var node;
10990  
10991                  // First node is excluded
10992                  node = nodes[0];
10993                  if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
10994                      nodes.splice(0, 1);
10995                  }
10996  
10997                  // Last node is excluded
10998                  node = nodes[nodes.length - 1];
10999                  if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
11000                      nodes.splice(nodes.length - 1, 1);
11001                  }
11002  
11003                  return nodes;
11004              };
11005  
11006  			function collectSiblings(node, name, end_node) {
11007                  var siblings = [];
11008  
11009                  for (; node && node != end_node; node = node[name])
11010                      siblings.push(node);
11011  
11012                  return siblings;
11013              };
11014  
11015  			function findEndPoint(node, root) {
11016                  do {
11017                      if (node.parentNode == root)
11018                          return node;
11019  
11020                      node = node.parentNode;
11021                  } while(node);
11022              };
11023  
11024  			function walkBoundary(start_node, end_node, next) {
11025                  var siblingName = next ? 'nextSibling' : 'previousSibling';
11026  
11027                  for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
11028                      parent = node.parentNode;
11029                      siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
11030  
11031                      if (siblings.length) {
11032                          if (!next)
11033                              siblings.reverse();
11034  
11035                          callback(exclude(siblings));
11036                      }
11037                  }
11038              };
11039  
11040              // If index based start position then resolve it
11041              if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
11042                  startContainer = startContainer.childNodes[startOffset];
11043  
11044              // If index based end position then resolve it
11045              if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
11046                  endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
11047  
11048              // Same container
11049              if (startContainer == endContainer)
11050                  return callback(exclude([startContainer]));
11051  
11052              // Find common ancestor and end points
11053              ancestor = dom.findCommonAncestor(startContainer, endContainer);
11054                  
11055              // Process left side
11056              for (node = startContainer; node; node = node.parentNode) {
11057                  if (node === endContainer)
11058                      return walkBoundary(startContainer, ancestor, true);
11059  
11060                  if (node === ancestor)
11061                      break;
11062              }
11063  
11064              // Process right side
11065              for (node = endContainer; node; node = node.parentNode) {
11066                  if (node === startContainer)
11067                      return walkBoundary(endContainer, ancestor);
11068  
11069                  if (node === ancestor)
11070                      break;
11071              }
11072  
11073              // Find start/end point
11074              startPoint = findEndPoint(startContainer, ancestor) || startContainer;
11075              endPoint = findEndPoint(endContainer, ancestor) || endContainer;
11076  
11077              // Walk left leaf
11078              walkBoundary(startContainer, startPoint, true);
11079  
11080              // Walk the middle from start to end point
11081              siblings = collectSiblings(
11082                  startPoint == startContainer ? startPoint : startPoint.nextSibling,
11083                  'nextSibling',
11084                  endPoint == endContainer ? endPoint.nextSibling : endPoint
11085              );
11086  
11087              if (siblings.length)
11088                  callback(exclude(siblings));
11089  
11090              // Walk right leaf
11091              walkBoundary(endContainer, endPoint);
11092          };
11093  
11094          this.split = function(rng) {
11095              var startContainer = rng.startContainer,
11096                  startOffset = rng.startOffset,
11097                  endContainer = rng.endContainer,
11098                  endOffset = rng.endOffset;
11099  
11100  			function splitText(node, offset) {
11101                  return node.splitText(offset);
11102              };
11103  
11104              // Handle single text node
11105              if (startContainer == endContainer && startContainer.nodeType == 3) {
11106                  if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11107                      endContainer = splitText(startContainer, startOffset);
11108                      startContainer = endContainer.previousSibling;
11109  
11110                      if (endOffset > startOffset) {
11111                          endOffset = endOffset - startOffset;
11112                          startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
11113                          endOffset = endContainer.nodeValue.length;
11114                          startOffset = 0;
11115                      } else {
11116                          endOffset = 0;
11117                      }
11118                  }
11119              } else {
11120                  // Split startContainer text node if needed
11121                  if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11122                      startContainer = splitText(startContainer, startOffset);
11123                      startOffset = 0;
11124                  }
11125  
11126                  // Split endContainer text node if needed
11127                  if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
11128                      endContainer = splitText(endContainer, endOffset).previousSibling;
11129                      endOffset = endContainer.nodeValue.length;
11130                  }
11131              }
11132  
11133              return {
11134                  startContainer : startContainer,
11135                  startOffset : startOffset,
11136                  endContainer : endContainer,
11137                  endOffset : endOffset
11138              };
11139          };
11140  
11141      };
11142  
11143      tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
11144          if (rng1 && rng2) {
11145              // Compare native IE ranges
11146              if (rng1.item || rng1.duplicate) {
11147                  // Both are control ranges and the selected element matches
11148                  if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
11149                      return true;
11150  
11151                  // Both are text ranges and the range matches
11152                  if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
11153                      return true;
11154              } else {
11155                  // Compare w3c ranges
11156                  return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
11157              }
11158          }
11159  
11160          return false;
11161      };
11162  })(tinymce);
11163  (function(tinymce) {
11164      var Event = tinymce.dom.Event, each = tinymce.each;
11165  
11166      tinymce.create('tinymce.ui.KeyboardNavigation', {
11167          KeyboardNavigation: function(settings, dom) {
11168              var t = this, root = settings.root, items = settings.items,
11169                      enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
11170                      excludeFromTabOrder = settings.excludeFromTabOrder,
11171                      itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
11172  
11173              dom = dom || tinymce.DOM;
11174  
11175              itemFocussed = function(evt) {
11176                  focussedId = evt.target.id;
11177              };
11178              
11179              itemBlurred = function(evt) {
11180                  dom.setAttrib(evt.target.id, 'tabindex', '-1');
11181              };
11182              
11183              rootFocussed = function(evt) {
11184                  var item = dom.get(focussedId);
11185                  dom.setAttrib(item, 'tabindex', '0');
11186                  item.focus();
11187              };
11188              
11189              t.focus = function() {
11190                  dom.get(focussedId).focus();
11191              };
11192  
11193              t.destroy = function() {
11194                  each(items, function(item) {
11195                      var elm = dom.get(item.id);
11196  
11197                      dom.unbind(elm, 'focus', itemFocussed);
11198                      dom.unbind(elm, 'blur', itemBlurred);
11199                  });
11200  
11201                  var rootElm = dom.get(root);
11202                  dom.unbind(rootElm, 'focus', rootFocussed);
11203                  dom.unbind(rootElm, 'keydown', rootKeydown);
11204  
11205                  items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
11206                  t.destroy = function() {};
11207              };
11208              
11209              t.moveFocus = function(dir, evt) {
11210                  var idx = -1, controls = t.controls, newFocus;
11211  
11212                  if (!focussedId)
11213                      return;
11214  
11215                  each(items, function(item, index) {
11216                      if (item.id === focussedId) {
11217                          idx = index;
11218                          return false;
11219                      }
11220                  });
11221  
11222                  idx += dir;
11223                  if (idx < 0) {
11224                      idx = items.length - 1;
11225                  } else if (idx >= items.length) {
11226                      idx = 0;
11227                  }
11228                  
11229                  newFocus = items[idx];
11230                  dom.setAttrib(focussedId, 'tabindex', '-1');
11231                  dom.setAttrib(newFocus.id, 'tabindex', '0');
11232                  dom.get(newFocus.id).focus();
11233  
11234                  if (settings.actOnFocus) {
11235                      settings.onAction(newFocus.id);
11236                  }
11237  
11238                  if (evt)
11239                      Event.cancel(evt);
11240              };
11241              
11242              rootKeydown = function(evt) {
11243                  var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
11244                  
11245                  switch (evt.keyCode) {
11246                      case DOM_VK_LEFT:
11247                          if (enableLeftRight) t.moveFocus(-1);
11248                          Event.cancel(evt);
11249                          break;
11250      
11251                      case DOM_VK_RIGHT:
11252                          if (enableLeftRight) t.moveFocus(1);
11253                          Event.cancel(evt);
11254                          break;
11255      
11256                      case DOM_VK_UP:
11257                          if (enableUpDown) t.moveFocus(-1);
11258                          Event.cancel(evt);
11259                          break;
11260  
11261                      case DOM_VK_DOWN:
11262                          if (enableUpDown) t.moveFocus(1);
11263                          Event.cancel(evt);
11264                          break;
11265  
11266                      case DOM_VK_ESCAPE:
11267                          if (settings.onCancel) {
11268                              settings.onCancel();
11269                              Event.cancel(evt);
11270                          }
11271                          break;
11272  
11273                      case DOM_VK_ENTER:
11274                      case DOM_VK_RETURN:
11275                      case DOM_VK_SPACE:
11276                          if (settings.onAction) {
11277                              settings.onAction(focussedId);
11278                              Event.cancel(evt);
11279                          }
11280                          break;
11281                  }
11282              };
11283  
11284              // Set up state and listeners for each item.
11285              each(items, function(item, idx) {
11286                  var tabindex, elm;
11287  
11288                  if (!item.id) {
11289                      item.id = dom.uniqueId('_mce_item_');
11290                  }
11291  
11292                  elm = dom.get(item.id);
11293  
11294                  if (excludeFromTabOrder) {
11295                      dom.bind(elm, 'blur', itemBlurred);
11296                      tabindex = '-1';
11297                  } else {
11298                      tabindex = (idx === 0 ? '0' : '-1');
11299                  }
11300  
11301                  elm.setAttribute('tabindex', tabindex);
11302                  dom.bind(elm, 'focus', itemFocussed);
11303              });
11304              
11305              // Setup initial state for root element.
11306              if (items[0]){
11307                  focussedId = items[0].id;
11308              }
11309  
11310              dom.setAttrib(root, 'tabindex', '-1');
11311  
11312              // Setup listeners for root element.
11313              var rootElm = dom.get(root);
11314              dom.bind(rootElm, 'focus', rootFocussed);
11315              dom.bind(rootElm, 'keydown', rootKeydown);
11316          }
11317      });
11318  })(tinymce);
11319  (function(tinymce) {
11320      // Shorten class names
11321      var DOM = tinymce.DOM, is = tinymce.is;
11322  
11323      tinymce.create('tinymce.ui.Control', {
11324          Control : function(id, s, editor) {
11325              this.id = id;
11326              this.settings = s = s || {};
11327              this.rendered = false;
11328              this.onRender = new tinymce.util.Dispatcher(this);
11329              this.classPrefix = '';
11330              this.scope = s.scope || this;
11331              this.disabled = 0;
11332              this.active = 0;
11333              this.editor = editor;
11334          },
11335          
11336          setAriaProperty : function(property, value) {
11337              var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11338              if (element) {
11339                  DOM.setAttrib(element, 'aria-' + property, !!value);
11340              }
11341          },
11342          
11343          focus : function() {
11344              DOM.get(this.id).focus();
11345          },
11346  
11347          setDisabled : function(s) {
11348              if (s != this.disabled) {
11349                  this.setAriaProperty('disabled', s);
11350  
11351                  this.setState('Disabled', s);
11352                  this.setState('Enabled', !s);
11353                  this.disabled = s;
11354              }
11355          },
11356  
11357          isDisabled : function() {
11358              return this.disabled;
11359          },
11360  
11361          setActive : function(s) {
11362              if (s != this.active) {
11363                  this.setState('Active', s);
11364                  this.active = s;
11365                  this.setAriaProperty('pressed', s);
11366              }
11367          },
11368  
11369          isActive : function() {
11370              return this.active;
11371          },
11372  
11373          setState : function(c, s) {
11374              var n = DOM.get(this.id);
11375  
11376              c = this.classPrefix + c;
11377  
11378              if (s)
11379                  DOM.addClass(n, c);
11380              else
11381                  DOM.removeClass(n, c);
11382          },
11383  
11384          isRendered : function() {
11385              return this.rendered;
11386          },
11387  
11388          renderHTML : function() {
11389          },
11390  
11391          renderTo : function(n) {
11392              DOM.setHTML(n, this.renderHTML());
11393          },
11394  
11395          postRender : function() {
11396              var t = this, b;
11397  
11398              // Set pending states
11399              if (is(t.disabled)) {
11400                  b = t.disabled;
11401                  t.disabled = -1;
11402                  t.setDisabled(b);
11403              }
11404  
11405              if (is(t.active)) {
11406                  b = t.active;
11407                  t.active = -1;
11408                  t.setActive(b);
11409              }
11410          },
11411  
11412          remove : function() {
11413              DOM.remove(this.id);
11414              this.destroy();
11415          },
11416  
11417          destroy : function() {
11418              tinymce.dom.Event.clear(this.id);
11419          }
11420      });
11421  })(tinymce);
11422  tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11423      Container : function(id, s, editor) {
11424          this.parent(id, s, editor);
11425  
11426          this.controls = [];
11427  
11428          this.lookup = {};
11429      },
11430  
11431      add : function(c) {
11432          this.lookup[c.id] = c;
11433          this.controls.push(c);
11434  
11435          return c;
11436      },
11437  
11438      get : function(n) {
11439          return this.lookup[n];
11440      }
11441  });
11442  
11443  tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11444      Separator : function(id, s) {
11445          this.parent(id, s);
11446          this.classPrefix = 'mceSeparator';
11447          this.setDisabled(true);
11448      },
11449  
11450      renderHTML : function() {
11451          return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11452      }
11453  });
11454  (function(tinymce) {
11455      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11456  
11457      tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11458          MenuItem : function(id, s) {
11459              this.parent(id, s);
11460              this.classPrefix = 'mceMenuItem';
11461          },
11462  
11463          setSelected : function(s) {
11464              this.setState('Selected', s);
11465              this.setAriaProperty('checked', !!s);
11466              this.selected = s;
11467          },
11468  
11469          isSelected : function() {
11470              return this.selected;
11471          },
11472  
11473          postRender : function() {
11474              var t = this;
11475              
11476              t.parent();
11477  
11478              // Set pending state
11479              if (is(t.selected))
11480                  t.setSelected(t.selected);
11481          }
11482      });
11483  })(tinymce);
11484  (function(tinymce) {
11485      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11486  
11487      tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11488          Menu : function(id, s) {
11489              var t = this;
11490  
11491              t.parent(id, s);
11492              t.items = {};
11493              t.collapsed = false;
11494              t.menuCount = 0;
11495              t.onAddItem = new tinymce.util.Dispatcher(this);
11496          },
11497  
11498          expand : function(d) {
11499              var t = this;
11500  
11501              if (d) {
11502                  walk(t, function(o) {
11503                      if (o.expand)
11504                          o.expand();
11505                  }, 'items', t);
11506              }
11507  
11508              t.collapsed = false;
11509          },
11510  
11511          collapse : function(d) {
11512              var t = this;
11513  
11514              if (d) {
11515                  walk(t, function(o) {
11516                      if (o.collapse)
11517                          o.collapse();
11518                  }, 'items', t);
11519              }
11520  
11521              t.collapsed = true;
11522          },
11523  
11524          isCollapsed : function() {
11525              return this.collapsed;
11526          },
11527  
11528          add : function(o) {
11529              if (!o.settings)
11530                  o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11531  
11532              this.onAddItem.dispatch(this, o);
11533  
11534              return this.items[o.id] = o;
11535          },
11536  
11537          addSeparator : function() {
11538              return this.add({separator : true});
11539          },
11540  
11541          addMenu : function(o) {
11542              if (!o.collapse)
11543                  o = this.createMenu(o);
11544  
11545              this.menuCount++;
11546  
11547              return this.add(o);
11548          },
11549  
11550          hasMenus : function() {
11551              return this.menuCount !== 0;
11552          },
11553  
11554          remove : function(o) {
11555              delete this.items[o.id];
11556          },
11557  
11558          removeAll : function() {
11559              var t = this;
11560  
11561              walk(t, function(o) {
11562                  if (o.removeAll)
11563                      o.removeAll();
11564                  else
11565                      o.remove();
11566  
11567                  o.destroy();
11568              }, 'items', t);
11569  
11570              t.items = {};
11571          },
11572  
11573          createMenu : function(o) {
11574              var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11575  
11576              m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11577  
11578              return m;
11579          }
11580      });
11581  })(tinymce);
11582  (function(tinymce) {
11583      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11584  
11585      tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11586          DropMenu : function(id, s) {
11587              s = s || {};
11588              s.container = s.container || DOM.doc.body;
11589              s.offset_x = s.offset_x || 0;
11590              s.offset_y = s.offset_y || 0;
11591              s.vp_offset_x = s.vp_offset_x || 0;
11592              s.vp_offset_y = s.vp_offset_y || 0;
11593  
11594              if (is(s.icons) && !s.icons)
11595                  s['class'] += ' mceNoIcons';
11596  
11597              this.parent(id, s);
11598              this.onShowMenu = new tinymce.util.Dispatcher(this);
11599              this.onHideMenu = new tinymce.util.Dispatcher(this);
11600              this.classPrefix = 'mceMenu';
11601          },
11602  
11603          createMenu : function(s) {
11604              var t = this, cs = t.settings, m;
11605  
11606              s.container = s.container || cs.container;
11607              s.parent = t;
11608              s.constrain = s.constrain || cs.constrain;
11609              s['class'] = s['class'] || cs['class'];
11610              s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11611              s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11612              s.keyboard_focus = cs.keyboard_focus;
11613              m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11614  
11615              m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11616  
11617              return m;
11618          },
11619          
11620          focus : function() {
11621              var t = this;
11622              if (t.keyboardNav) {
11623                  t.keyboardNav.focus();
11624              }
11625          },
11626  
11627          update : function() {
11628              var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11629  
11630              tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11631              th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11632  
11633              if (!DOM.boxModel)
11634                  t.element.setStyles({width : tw + 2, height : th + 2});
11635              else
11636                  t.element.setStyles({width : tw, height : th});
11637  
11638              if (s.max_width)
11639                  DOM.setStyle(co, 'width', tw);
11640  
11641              if (s.max_height) {
11642                  DOM.setStyle(co, 'height', th);
11643  
11644                  if (tb.clientHeight < s.max_height)
11645                      DOM.setStyle(co, 'overflow', 'hidden');
11646              }
11647          },
11648  
11649          showMenu : function(x, y, px) {
11650              var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11651  
11652              t.collapse(1);
11653  
11654              if (t.isMenuVisible)
11655                  return;
11656  
11657              if (!t.rendered) {
11658                  co = DOM.add(t.settings.container, t.renderNode());
11659  
11660                  each(t.items, function(o) {
11661                      o.postRender();
11662                  });
11663  
11664                  t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11665              } else
11666                  co = DOM.get('menu_' + t.id);
11667  
11668              // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11669              if (!tinymce.isOpera)
11670                  DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11671  
11672              DOM.show(co);
11673              t.update();
11674  
11675              x += s.offset_x || 0;
11676              y += s.offset_y || 0;
11677              vp.w -= 4;
11678              vp.h -= 4;
11679  
11680              // Move inside viewport if not submenu
11681              if (s.constrain) {
11682                  w = co.clientWidth - ot;
11683                  h = co.clientHeight - ot;
11684                  mx = vp.x + vp.w;
11685                  my = vp.y + vp.h;
11686  
11687                  if ((x + s.vp_offset_x + w) > mx)
11688                      x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11689  
11690                  if ((y + s.vp_offset_y + h) > my)
11691                      y = Math.max(0, (my - s.vp_offset_y) - h);
11692              }
11693  
11694              DOM.setStyles(co, {left : x , top : y});
11695              t.element.update();
11696  
11697              t.isMenuVisible = 1;
11698              t.mouseClickFunc = Event.add(co, 'click', function(e) {
11699                  var m;
11700  
11701                  e = e.target;
11702  
11703                  if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11704                      m = t.items[e.id];
11705  
11706                      if (m.isDisabled())
11707                          return;
11708  
11709                      dm = t;
11710  
11711                      while (dm) {
11712                          if (dm.hideMenu)
11713                              dm.hideMenu();
11714  
11715                          dm = dm.settings.parent;
11716                      }
11717  
11718                      if (m.settings.onclick)
11719                          m.settings.onclick(e);
11720  
11721                      return false; // Cancel to fix onbeforeunload problem
11722                  }
11723              });
11724  
11725              if (t.hasMenus()) {
11726                  t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11727                      var m, r, mi;
11728  
11729                      e = e.target;
11730                      if (e && (e = DOM.getParent(e, 'tr'))) {
11731                          m = t.items[e.id];
11732  
11733                          if (t.lastMenu)
11734                              t.lastMenu.collapse(1);
11735  
11736                          if (m.isDisabled())
11737                              return;
11738  
11739                          if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11740                              //p = DOM.getPos(s.container);
11741                              r = DOM.getRect(e);
11742                              m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11743                              t.lastMenu = m;
11744                              DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11745                          }
11746                      }
11747                  });
11748              }
11749              
11750              Event.add(co, 'keydown', t._keyHandler, t);
11751  
11752              t.onShowMenu.dispatch(t);
11753  
11754              if (s.keyboard_focus) { 
11755                  t._setupKeyboardNav(); 
11756              }
11757          },
11758  
11759          hideMenu : function(c) {
11760              var t = this, co = DOM.get('menu_' + t.id), e;
11761  
11762              if (!t.isMenuVisible)
11763                  return;
11764  
11765              if (t.keyboardNav) t.keyboardNav.destroy();
11766              Event.remove(co, 'mouseover', t.mouseOverFunc);
11767              Event.remove(co, 'click', t.mouseClickFunc);
11768              Event.remove(co, 'keydown', t._keyHandler);
11769              DOM.hide(co);
11770              t.isMenuVisible = 0;
11771  
11772              if (!c)
11773                  t.collapse(1);
11774  
11775              if (t.element)
11776                  t.element.hide();
11777  
11778              if (e = DOM.get(t.id))
11779                  DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11780  
11781              t.onHideMenu.dispatch(t);
11782          },
11783  
11784          add : function(o) {
11785              var t = this, co;
11786  
11787              o = t.parent(o);
11788  
11789              if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11790                  t._add(DOM.select('tbody', co)[0], o);
11791  
11792              return o;
11793          },
11794  
11795          collapse : function(d) {
11796              this.parent(d);
11797              this.hideMenu(1);
11798          },
11799  
11800          remove : function(o) {
11801              DOM.remove(o.id);
11802              this.destroy();
11803  
11804              return this.parent(o);
11805          },
11806  
11807          destroy : function() {
11808              var t = this, co = DOM.get('menu_' + t.id);
11809  
11810              if (t.keyboardNav) t.keyboardNav.destroy();
11811              Event.remove(co, 'mouseover', t.mouseOverFunc);
11812              Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11813              Event.remove(co, 'click', t.mouseClickFunc);
11814              Event.remove(co, 'keydown', t._keyHandler);
11815  
11816              if (t.element)
11817                  t.element.remove();
11818  
11819              DOM.remove(co);
11820          },
11821  
11822          renderNode : function() {
11823              var t = this, s = t.settings, n, tb, co, w;
11824  
11825              w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11826              if (t.settings.parent) {
11827                  DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11828              }
11829              co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11830              t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11831  
11832              if (s.menu_line)
11833                  DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11834  
11835  //            n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11836              n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11837              tb = DOM.add(n, 'tbody');
11838  
11839              each(t.items, function(o) {
11840                  t._add(tb, o);
11841              });
11842  
11843              t.rendered = true;
11844  
11845              return w;
11846          },
11847  
11848          // Internal functions
11849          _setupKeyboardNav : function(){
11850              var contextMenu, menuItems, t=this; 
11851              contextMenu = DOM.get('menu_' + t.id);
11852              menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11853              menuItems.splice(0,0,contextMenu);
11854              t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11855                  root: 'menu_' + t.id,
11856                  items: menuItems,
11857                  onCancel: function() {
11858                      t.hideMenu();
11859                  },
11860                  enableUpDown: true
11861              });
11862              contextMenu.focus();
11863          },
11864  
11865          _keyHandler : function(evt) {
11866              var t = this, e;
11867              switch (evt.keyCode) {
11868                  case 37: // Left
11869                      if (t.settings.parent) {
11870                          t.hideMenu();
11871                          t.settings.parent.focus();
11872                          Event.cancel(evt);
11873                      }
11874                      break;
11875                  case 39: // Right
11876                      if (t.mouseOverFunc)
11877                          t.mouseOverFunc(evt);
11878                      break;
11879              }
11880          },
11881  
11882          _add : function(tb, o) {
11883              var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11884  
11885              if (s.separator) {
11886                  ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11887                  DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11888  
11889                  if (n = ro.previousSibling)
11890                      DOM.addClass(n, 'mceLast');
11891  
11892                  return;
11893              }
11894  
11895              n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11896              n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11897              n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11898  
11899              if (s.parent) {
11900                  DOM.setAttrib(a, 'aria-haspopup', 'true');
11901                  DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11902              }
11903  
11904              DOM.addClass(it, s['class']);
11905  //            n = DOM.add(n, 'span', {'class' : 'item'});
11906  
11907              ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11908  
11909              if (s.icon_src)
11910                  DOM.add(ic, 'img', {src : s.icon_src});
11911  
11912              n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11913  
11914              if (o.settings.style) {
11915                  if (typeof o.settings.style == "function")
11916                      o.settings.style = o.settings.style();
11917  
11918                  DOM.setAttrib(n, 'style', o.settings.style);
11919              }
11920  
11921              if (tb.childNodes.length == 1)
11922                  DOM.addClass(ro, 'mceFirst');
11923  
11924              if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11925                  DOM.addClass(ro, 'mceFirst');
11926  
11927              if (o.collapse)
11928                  DOM.addClass(ro, cp + 'ItemSub');
11929  
11930              if (n = ro.previousSibling)
11931                  DOM.removeClass(n, 'mceLast');
11932  
11933              DOM.addClass(ro, 'mceLast');
11934          }
11935      });
11936  })(tinymce);
11937  (function(tinymce) {
11938      var DOM = tinymce.DOM;
11939  
11940      tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11941          Button : function(id, s, ed) {
11942              this.parent(id, s, ed);
11943              this.classPrefix = 'mceButton';
11944          },
11945  
11946          renderHTML : function() {
11947              var cp = this.classPrefix, s = this.settings, h, l;
11948  
11949              l = DOM.encode(s.label || '');
11950              h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11951              if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11952                  h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11953              else
11954                  h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11955  
11956              h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
11957              h += '</a>';
11958              return h;
11959          },
11960  
11961          postRender : function() {
11962              var t = this, s = t.settings, imgBookmark;
11963  
11964              // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11965              // need to keep the selection in case the selection is lost
11966              if (tinymce.isIE && t.editor) {
11967                  tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11968                      var nodeName = t.editor.selection.getNode().nodeName;
11969                      imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11970                  });
11971              }
11972              tinymce.dom.Event.add(t.id, 'click', function(e) {
11973                  if (!t.isDisabled()) {
11974                      // restore the selection in case the selection is lost in IE
11975                      if (tinymce.isIE && t.editor && imgBookmark !== null) {
11976                          t.editor.selection.moveToBookmark(imgBookmark);
11977                      }
11978                      return s.onclick.call(s.scope, e);
11979                  }
11980              });
11981              tinymce.dom.Event.add(t.id, 'keydown', function(e) {
11982                  if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) {
11983                      tinymce.dom.Event.cancel(e);
11984                      return s.onclick.call(s.scope, e);
11985                  }
11986              });
11987          }
11988      });
11989  })(tinymce);
11990  (function(tinymce) {
11991      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
11992  
11993      tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
11994          ListBox : function(id, s, ed) {
11995              var t = this;
11996  
11997              t.parent(id, s, ed);
11998  
11999              t.items = [];
12000  
12001              t.onChange = new Dispatcher(t);
12002  
12003              t.onPostRender = new Dispatcher(t);
12004  
12005              t.onAdd = new Dispatcher(t);
12006  
12007              t.onRenderMenu = new tinymce.util.Dispatcher(this);
12008  
12009              t.classPrefix = 'mceListBox';
12010              t.marked = {};
12011          },
12012  
12013          select : function(va) {
12014              var t = this, fv, f;
12015  
12016              t.marked = {};
12017  
12018              if (va == undef)
12019                  return t.selectByIndex(-1);
12020  
12021              // Is string or number make function selector
12022              if (va && typeof(va)=="function")
12023                  f = va;
12024              else {
12025                  f = function(v) {
12026                      return v == va;
12027                  };
12028              }
12029  
12030              // Do we need to do something?
12031              if (va != t.selectedValue) {
12032                  // Find item
12033                  each(t.items, function(o, i) {
12034                      if (f(o.value)) {
12035                          fv = 1;
12036                          t.selectByIndex(i);
12037                          return false;
12038                      }
12039                  });
12040  
12041                  if (!fv)
12042                      t.selectByIndex(-1);
12043              }
12044          },
12045  
12046          selectByIndex : function(idx) {
12047              var t = this, e, o, label;
12048  
12049              t.marked = {};
12050  
12051              if (idx != t.selectedIndex) {
12052                  e = DOM.get(t.id + '_text');
12053                  label = DOM.get(t.id + '_voiceDesc');
12054                  o = t.items[idx];
12055  
12056                  if (o) {
12057                      t.selectedValue = o.value;
12058                      t.selectedIndex = idx;
12059                      DOM.setHTML(e, DOM.encode(o.title));
12060                      DOM.setHTML(label, t.settings.title + " - " + o.title);
12061                      DOM.removeClass(e, 'mceTitle');
12062                      DOM.setAttrib(t.id, 'aria-valuenow', o.title);
12063                  } else {
12064                      DOM.setHTML(e, DOM.encode(t.settings.title));
12065                      DOM.setHTML(label, DOM.encode(t.settings.title));
12066                      DOM.addClass(e, 'mceTitle');
12067                      t.selectedValue = t.selectedIndex = null;
12068                      DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
12069                  }
12070                  e = 0;
12071              }
12072          },
12073  
12074          mark : function(value) {
12075              this.marked[value] = true;
12076          },
12077  
12078          add : function(n, v, o) {
12079              var t = this;
12080  
12081              o = o || {};
12082              o = tinymce.extend(o, {
12083                  title : n,
12084                  value : v
12085              });
12086  
12087              t.items.push(o);
12088              t.onAdd.dispatch(t, o);
12089          },
12090  
12091          getLength : function() {
12092              return this.items.length;
12093          },
12094  
12095          renderHTML : function() {
12096              var h = '', t = this, s = t.settings, cp = t.classPrefix;
12097  
12098              h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
12099              h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
12100              h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
12101              h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
12102              h += '</tr></tbody></table></span>';
12103  
12104              return h;
12105          },
12106  
12107          showMenu : function() {
12108              var t = this, p2, e = DOM.get(this.id), m;
12109  
12110              if (t.isDisabled() || t.items.length === 0)
12111                  return;
12112  
12113              if (t.menu && t.menu.isMenuVisible)
12114                  return t.hideMenu();
12115  
12116              if (!t.isMenuRendered) {
12117                  t.renderMenu();
12118                  t.isMenuRendered = true;
12119              }
12120  
12121              p2 = DOM.getPos(e);
12122  
12123              m = t.menu;
12124              m.settings.offset_x = p2.x;
12125              m.settings.offset_y = p2.y;
12126              m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
12127  
12128              // Select in menu
12129              each(t.items, function(o) {
12130                  if (m.items[o.id]) {
12131                      m.items[o.id].setSelected(0);
12132                  }
12133              });
12134  
12135              each(t.items, function(o) {
12136                  if (m.items[o.id] && t.marked[o.value]) {
12137                      m.items[o.id].setSelected(1);
12138                  }
12139  
12140                  if (o.value === t.selectedValue) {
12141                      m.items[o.id].setSelected(1);
12142                  }
12143              });
12144  
12145              m.showMenu(0, e.clientHeight);
12146  
12147              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12148              DOM.addClass(t.id, t.classPrefix + 'Selected');
12149  
12150              //DOM.get(t.id + '_text').focus();
12151          },
12152  
12153          hideMenu : function(e) {
12154              var t = this;
12155  
12156              if (t.menu && t.menu.isMenuVisible) {
12157                  DOM.removeClass(t.id, t.classPrefix + 'Selected');
12158  
12159                  // Prevent double toogles by canceling the mouse click event to the button
12160                  if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
12161                      return;
12162  
12163                  if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12164                      DOM.removeClass(t.id, t.classPrefix + 'Selected');
12165                      Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12166                      t.menu.hideMenu();
12167                  }
12168              }
12169          },
12170  
12171          renderMenu : function() {
12172              var t = this, m;
12173  
12174              m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12175                  menu_line : 1,
12176                  'class' : t.classPrefix + 'Menu mceNoIcons',
12177                  max_width : 250,
12178                  max_height : 150
12179              });
12180  
12181              m.onHideMenu.add(function() {
12182                  t.hideMenu();
12183                  t.focus();
12184              });
12185  
12186              m.add({
12187                  title : t.settings.title,
12188                  'class' : 'mceMenuItemTitle',
12189                  onclick : function() {
12190                      if (t.settings.onselect('') !== false)
12191                          t.select(''); // Must be runned after
12192                  }
12193              });
12194  
12195              each(t.items, function(o) {
12196                  // No value then treat it as a title
12197                  if (o.value === undef) {
12198                      m.add({
12199                          title : o.title,
12200                          role : "option",
12201                          'class' : 'mceMenuItemTitle',
12202                          onclick : function() {
12203                              if (t.settings.onselect('') !== false)
12204                                  t.select(''); // Must be runned after
12205                          }
12206                      });
12207                  } else {
12208                      o.id = DOM.uniqueId();
12209                      o.role= "option";
12210                      o.onclick = function() {
12211                          if (t.settings.onselect(o.value) !== false)
12212                              t.select(o.value); // Must be runned after
12213                      };
12214  
12215                      m.add(o);
12216                  }
12217              });
12218  
12219              t.onRenderMenu.dispatch(t, m);
12220              t.menu = m;
12221          },
12222  
12223          postRender : function() {
12224              var t = this, cp = t.classPrefix;
12225  
12226              Event.add(t.id, 'click', t.showMenu, t);
12227              Event.add(t.id, 'keydown', function(evt) {
12228                  if (evt.keyCode == 32) { // Space
12229                      t.showMenu(evt);
12230                      Event.cancel(evt);
12231                  }
12232              });
12233              Event.add(t.id, 'focus', function() {
12234                  if (!t._focused) {
12235                      t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
12236                          if (e.keyCode == 40) {
12237                              t.showMenu();
12238                              Event.cancel(e);
12239                          }
12240                      });
12241                      t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
12242                          var v;
12243                          if (e.keyCode == 13) {
12244                              // Fake select on enter
12245                              v = t.selectedValue;
12246                              t.selectedValue = null; // Needs to be null to fake change
12247                              Event.cancel(e);
12248                              t.settings.onselect(v);
12249                          }
12250                      });
12251                  }
12252  
12253                  t._focused = 1;
12254              });
12255              Event.add(t.id, 'blur', function() {
12256                  Event.remove(t.id, 'keydown', t.keyDownHandler);
12257                  Event.remove(t.id, 'keypress', t.keyPressHandler);
12258                  t._focused = 0;
12259              });
12260  
12261              // Old IE doesn't have hover on all elements
12262              if (tinymce.isIE6 || !DOM.boxModel) {
12263                  Event.add(t.id, 'mouseover', function() {
12264                      if (!DOM.hasClass(t.id, cp + 'Disabled'))
12265                          DOM.addClass(t.id, cp + 'Hover');
12266                  });
12267  
12268                  Event.add(t.id, 'mouseout', function() {
12269                      if (!DOM.hasClass(t.id, cp + 'Disabled'))
12270                          DOM.removeClass(t.id, cp + 'Hover');
12271                  });
12272              }
12273  
12274              t.onPostRender.dispatch(t, DOM.get(t.id));
12275          },
12276  
12277          destroy : function() {
12278              this.parent();
12279  
12280              Event.clear(this.id + '_text');
12281              Event.clear(this.id + '_open');
12282          }
12283      });
12284  })(tinymce);
12285  (function(tinymce) {
12286      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
12287  
12288      tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
12289          NativeListBox : function(id, s) {
12290              this.parent(id, s);
12291              this.classPrefix = 'mceNativeListBox';
12292          },
12293  
12294          setDisabled : function(s) {
12295              DOM.get(this.id).disabled = s;
12296              this.setAriaProperty('disabled', s);
12297          },
12298  
12299          isDisabled : function() {
12300              return DOM.get(this.id).disabled;
12301          },
12302  
12303          select : function(va) {
12304              var t = this, fv, f;
12305  
12306              if (va == undef)
12307                  return t.selectByIndex(-1);
12308  
12309              // Is string or number make function selector
12310              if (va && typeof(va)=="function")
12311                  f = va;
12312              else {
12313                  f = function(v) {
12314                      return v == va;
12315                  };
12316              }
12317  
12318              // Do we need to do something?
12319              if (va != t.selectedValue) {
12320                  // Find item
12321                  each(t.items, function(o, i) {
12322                      if (f(o.value)) {
12323                          fv = 1;
12324                          t.selectByIndex(i);
12325                          return false;
12326                      }
12327                  });
12328  
12329                  if (!fv)
12330                      t.selectByIndex(-1);
12331              }
12332          },
12333  
12334          selectByIndex : function(idx) {
12335              DOM.get(this.id).selectedIndex = idx + 1;
12336              this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12337          },
12338  
12339          add : function(n, v, a) {
12340              var o, t = this;
12341  
12342              a = a || {};
12343              a.value = v;
12344  
12345              if (t.isRendered())
12346                  DOM.add(DOM.get(this.id), 'option', a, n);
12347  
12348              o = {
12349                  title : n,
12350                  value : v,
12351                  attribs : a
12352              };
12353  
12354              t.items.push(o);
12355              t.onAdd.dispatch(t, o);
12356          },
12357  
12358          getLength : function() {
12359              return this.items.length;
12360          },
12361  
12362          renderHTML : function() {
12363              var h, t = this;
12364  
12365              h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12366  
12367              each(t.items, function(it) {
12368                  h += DOM.createHTML('option', {value : it.value}, it.title);
12369              });
12370  
12371              h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12372              h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12373              return h;
12374          },
12375  
12376          postRender : function() {
12377              var t = this, ch, changeListenerAdded = true;
12378  
12379              t.rendered = true;
12380  
12381  			function onChange(e) {
12382                  var v = t.items[e.target.selectedIndex - 1];
12383  
12384                  if (v && (v = v.value)) {
12385                      t.onChange.dispatch(t, v);
12386  
12387                      if (t.settings.onselect)
12388                          t.settings.onselect(v);
12389                  }
12390              };
12391  
12392              Event.add(t.id, 'change', onChange);
12393  
12394              // Accessibility keyhandler
12395              Event.add(t.id, 'keydown', function(e) {
12396                  var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
12397  
12398                  Event.remove(t.id, 'change', ch);
12399                  changeListenerAdded = false;
12400  
12401                  bf = Event.add(t.id, 'blur', function() {
12402                      if (changeListenerAdded) return;
12403                      changeListenerAdded = true;
12404                      Event.add(t.id, 'change', onChange);
12405                      Event.remove(t.id, 'blur', bf);
12406                  });
12407  
12408                  if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) {
12409                      onChange(e);
12410                      return Event.cancel(e);
12411                  } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) {
12412                      // allow native implementation (navigate select element options)
12413                      e.stopImmediatePropagation();
12414                  }
12415              });
12416  
12417              t.onPostRender.dispatch(t, DOM.get(t.id));
12418          }
12419      });
12420  })(tinymce);
12421  (function(tinymce) {
12422      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12423  
12424      tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12425          MenuButton : function(id, s, ed) {
12426              this.parent(id, s, ed);
12427  
12428              this.onRenderMenu = new tinymce.util.Dispatcher(this);
12429  
12430              s.menu_container = s.menu_container || DOM.doc.body;
12431          },
12432  
12433          showMenu : function() {
12434              var t = this, p1, p2, e = DOM.get(t.id), m;
12435  
12436              if (t.isDisabled())
12437                  return;
12438  
12439              if (!t.isMenuRendered) {
12440                  t.renderMenu();
12441                  t.isMenuRendered = true;
12442              }
12443  
12444              if (t.isMenuVisible)
12445                  return t.hideMenu();
12446  
12447              p1 = DOM.getPos(t.settings.menu_container);
12448              p2 = DOM.getPos(e);
12449  
12450              m = t.menu;
12451              m.settings.offset_x = p2.x;
12452              m.settings.offset_y = p2.y;
12453              m.settings.vp_offset_x = p2.x;
12454              m.settings.vp_offset_y = p2.y;
12455              m.settings.keyboard_focus = t._focused;
12456              m.showMenu(0, e.firstChild.clientHeight);
12457  
12458              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12459              t.setState('Selected', 1);
12460  
12461              t.isMenuVisible = 1;
12462          },
12463  
12464          renderMenu : function() {
12465              var t = this, m;
12466  
12467              m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12468                  menu_line : 1,
12469                  'class' : this.classPrefix + 'Menu',
12470                  icons : t.settings.icons
12471              });
12472  
12473              m.onHideMenu.add(function() {
12474                  t.hideMenu();
12475                  t.focus();
12476              });
12477  
12478              t.onRenderMenu.dispatch(t, m);
12479              t.menu = m;
12480          },
12481  
12482          hideMenu : function(e) {
12483              var t = this;
12484  
12485              // Prevent double toogles by canceling the mouse click event to the button
12486              if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12487                  return;
12488  
12489              if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12490                  t.setState('Selected', 0);
12491                  Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12492                  if (t.menu)
12493                      t.menu.hideMenu();
12494              }
12495  
12496              t.isMenuVisible = 0;
12497          },
12498  
12499          postRender : function() {
12500              var t = this, s = t.settings;
12501  
12502              Event.add(t.id, 'click', function() {
12503                  if (!t.isDisabled()) {
12504                      if (s.onclick)
12505                          s.onclick(t.value);
12506  
12507                      t.showMenu();
12508                  }
12509              });
12510          }
12511      });
12512  })(tinymce);
12513  (function(tinymce) {
12514      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12515  
12516      tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12517          SplitButton : function(id, s, ed) {
12518              this.parent(id, s, ed);
12519              this.classPrefix = 'mceSplitButton';
12520          },
12521  
12522          renderHTML : function() {
12523              var h, t = this, s = t.settings, h1;
12524  
12525              h = '<tbody><tr>';
12526  
12527              if (s.image)
12528                  h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12529              else
12530                  h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12531  
12532              h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12533              h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12534      
12535              h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12536              h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12537  
12538              h += '</tr></tbody>';
12539              h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12540              return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12541          },
12542  
12543          postRender : function() {
12544              var t = this, s = t.settings, activate;
12545  
12546              if (s.onclick) {
12547                  activate = function(evt) {
12548                      if (!t.isDisabled()) {
12549                          s.onclick(t.value);
12550                          Event.cancel(evt);
12551                      }
12552                  };
12553                  Event.add(t.id + '_action', 'click', activate);
12554                  Event.add(t.id, ['click', 'keydown'], function(evt) {
12555                      var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12556                      if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12557                          activate();
12558                          Event.cancel(evt);
12559                      } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12560                          t.showMenu();
12561                          Event.cancel(evt);
12562                      }
12563                  });
12564              }
12565  
12566              Event.add(t.id + '_open', 'click', function (evt) {
12567                  t.showMenu();
12568                  Event.cancel(evt);
12569              });
12570              Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12571              Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12572  
12573              // Old IE doesn't have hover on all elements
12574              if (tinymce.isIE6 || !DOM.boxModel) {
12575                  Event.add(t.id, 'mouseover', function() {
12576                      if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12577                          DOM.addClass(t.id, 'mceSplitButtonHover');
12578                  });
12579  
12580                  Event.add(t.id, 'mouseout', function() {
12581                      if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12582                          DOM.removeClass(t.id, 'mceSplitButtonHover');
12583                  });
12584              }
12585          },
12586  
12587          destroy : function() {
12588              this.parent();
12589  
12590              Event.clear(this.id + '_action');
12591              Event.clear(this.id + '_open');
12592              Event.clear(this.id);
12593          }
12594      });
12595  })(tinymce);
12596  (function(tinymce) {
12597      var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12598  
12599      tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12600          ColorSplitButton : function(id, s, ed) {
12601              var t = this;
12602  
12603              t.parent(id, s, ed);
12604  
12605              t.settings = s = tinymce.extend({
12606                  colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12607                  grid_width : 8,
12608                  default_color : '#888888'
12609              }, t.settings);
12610  
12611              t.onShowMenu = new tinymce.util.Dispatcher(t);
12612  
12613              t.onHideMenu = new tinymce.util.Dispatcher(t);
12614  
12615              t.value = s.default_color;
12616          },
12617  
12618          showMenu : function() {
12619              var t = this, r, p, e, p2;
12620  
12621              if (t.isDisabled())
12622                  return;
12623  
12624              if (!t.isMenuRendered) {
12625                  t.renderMenu();
12626                  t.isMenuRendered = true;
12627              }
12628  
12629              if (t.isMenuVisible)
12630                  return t.hideMenu();
12631  
12632              e = DOM.get(t.id);
12633              DOM.show(t.id + '_menu');
12634              DOM.addClass(e, 'mceSplitButtonSelected');
12635              p2 = DOM.getPos(e);
12636              DOM.setStyles(t.id + '_menu', {
12637                  left : p2.x,
12638                  top : p2.y + e.firstChild.clientHeight,
12639                  zIndex : 200000
12640              });
12641              e = 0;
12642  
12643              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12644              t.onShowMenu.dispatch(t);
12645  
12646              if (t._focused) {
12647                  t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12648                      if (e.keyCode == 27)
12649                          t.hideMenu();
12650                  });
12651  
12652                  DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12653              }
12654  
12655              t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12656                  root: t.id + '_menu',
12657                  items: DOM.select('a', t.id + '_menu'),
12658                  onCancel: function() {
12659                      t.hideMenu();
12660                      t.focus();
12661                  }
12662              });
12663  
12664              t.keyboardNav.focus();
12665              t.isMenuVisible = 1;
12666          },
12667  
12668          hideMenu : function(e) {
12669              var t = this;
12670  
12671              if (t.isMenuVisible) {
12672                  // Prevent double toogles by canceling the mouse click event to the button
12673                  if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12674                      return;
12675  
12676                  if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12677                      DOM.removeClass(t.id, 'mceSplitButtonSelected');
12678                      Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12679                      Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12680                      DOM.hide(t.id + '_menu');
12681                  }
12682  
12683                  t.isMenuVisible = 0;
12684                  t.onHideMenu.dispatch();
12685                  t.keyboardNav.destroy();
12686              }
12687          },
12688  
12689          renderMenu : function() {
12690              var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12691  
12692              w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12693              m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12694              DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12695  
12696              n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12697              tb = DOM.add(n, 'tbody');
12698  
12699              // Generate color grid
12700              i = 0;
12701              each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12702                  c = c.replace(/^#/, '');
12703  
12704                  if (!i--) {
12705                      tr = DOM.add(tb, 'tr');
12706                      i = s.grid_width - 1;
12707                  }
12708  
12709                  n = DOM.add(tr, 'td');
12710                  var settings = {
12711                      href : 'javascript:;',
12712                      style : {
12713                          backgroundColor : '#' + c
12714                      },
12715                      'title': t.editor.getLang('colors.' + c, c),
12716                      'data-mce-color' : '#' + c
12717                  };
12718  
12719                  // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12720                  if (!tinymce.isIE ) {
12721                      settings.role = 'option';
12722                  }
12723  
12724                  n = DOM.add(n, 'a', settings);
12725  
12726                  if (t.editor.forcedHighContrastMode) {
12727                      n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12728                      if (n.getContext && (context = n.getContext("2d"))) {
12729                          context.fillStyle = '#' + c;
12730                          context.fillRect(0, 0, 16, 16);
12731                      } else {
12732                          // No point leaving a canvas element around if it's not supported for drawing on anyway.
12733                          DOM.remove(n);
12734                      }
12735                  }
12736              });
12737  
12738              if (s.more_colors_func) {
12739                  n = DOM.add(tb, 'tr');
12740                  n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12741                  n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12742  
12743                  Event.add(n, 'click', function(e) {
12744                      s.more_colors_func.call(s.more_colors_scope || this);
12745                      return Event.cancel(e); // Cancel to fix onbeforeunload problem
12746                  });
12747              }
12748  
12749              DOM.addClass(m, 'mceColorSplitMenu');
12750  
12751              // Prevent IE from scrolling and hindering click to occur #4019
12752              Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12753  
12754              Event.add(t.id + '_menu', 'click', function(e) {
12755                  var c;
12756  
12757                  e = DOM.getParent(e.target, 'a', tb);
12758  
12759                  if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12760                      t.setColor(c);
12761  
12762                  return false; // Prevent IE auto save warning
12763              });
12764  
12765              return w;
12766          },
12767  
12768          setColor : function(c) {
12769              this.displayColor(c);
12770              this.hideMenu();
12771              this.settings.onselect(c);
12772          },
12773          
12774          displayColor : function(c) {
12775              var t = this;
12776  
12777              DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12778  
12779              t.value = c;
12780          },
12781  
12782          postRender : function() {
12783              var t = this, id = t.id;
12784  
12785              t.parent();
12786              DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12787              DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12788          },
12789  
12790          destroy : function() {
12791              var self = this;
12792  
12793              self.parent();
12794  
12795              Event.clear(self.id + '_menu');
12796              Event.clear(self.id + '_more');
12797              DOM.remove(self.id + '_menu');
12798  
12799              if (self.keyboardNav) {
12800                  self.keyboardNav.destroy();
12801              }
12802          }
12803      });
12804  })(tinymce);
12805  (function(tinymce) {
12806  // Shorten class names
12807  var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12808  tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12809      renderHTML : function() {
12810          var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12811  
12812          h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12813          //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12814          h.push("<span role='application'>");
12815          h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12816          each(controls, function(toolbar) {
12817              h.push(toolbar.renderHTML());
12818          });
12819          h.push("</span>");
12820          h.push('</div>');
12821  
12822          return h.join('');
12823      },
12824      
12825      focus : function() {
12826          var t = this;
12827          dom.get(t.id).focus();
12828      },
12829      
12830      postRender : function() {
12831          var t = this, items = [];
12832  
12833          each(t.controls, function(toolbar) {
12834              each (toolbar.controls, function(control) {
12835                  if (control.id) {
12836                      items.push(control);
12837                  }
12838              });
12839          });
12840  
12841          t.keyNav = new tinymce.ui.KeyboardNavigation({
12842              root: t.id,
12843              items: items,
12844              onCancel: function() {
12845                  //Move focus if webkit so that navigation back will read the item.
12846                  if (tinymce.isWebKit) {
12847                      dom.get(t.editor.id+"_ifr").focus();
12848                  }
12849                  t.editor.focus();
12850              },
12851              excludeFromTabOrder: !t.settings.tab_focus_toolbar
12852          });
12853      },
12854      
12855      destroy : function() {
12856          var self = this;
12857  
12858          self.parent();
12859          self.keyNav.destroy();
12860          Event.clear(self.id);
12861      }
12862  });
12863  })(tinymce);
12864  (function(tinymce) {
12865  // Shorten class names
12866  var dom = tinymce.DOM, each = tinymce.each;
12867  tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12868      renderHTML : function() {
12869          var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12870  
12871          cl = t.controls;
12872          for (i=0; i<cl.length; i++) {
12873              // Get current control, prev control, next control and if the control is a list box or not
12874              co = cl[i];
12875              pr = cl[i - 1];
12876              nx = cl[i + 1];
12877  
12878              // Add toolbar start
12879              if (i === 0) {
12880                  c = 'mceToolbarStart';
12881  
12882                  if (co.Button)
12883                      c += ' mceToolbarStartButton';
12884                  else if (co.SplitButton)
12885                      c += ' mceToolbarStartSplitButton';
12886                  else if (co.ListBox)
12887                      c += ' mceToolbarStartListBox';
12888  
12889                  h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12890              }
12891  
12892              // Add toolbar end before list box and after the previous button
12893              // This is to fix the o2k7 editor skins
12894              if (pr && co.ListBox) {
12895                  if (pr.Button || pr.SplitButton)
12896                      h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12897              }
12898  
12899              // Render control HTML
12900  
12901              // IE 8 quick fix, needed to propertly generate a hit area for anchors
12902              if (dom.stdMode)
12903                  h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12904              else
12905                  h += '<td>' + co.renderHTML() + '</td>';
12906  
12907              // Add toolbar start after list box and before the next button
12908              // This is to fix the o2k7 editor skins
12909              if (nx && co.ListBox) {
12910                  if (nx.Button || nx.SplitButton)
12911                      h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12912              }
12913          }
12914  
12915          c = 'mceToolbarEnd';
12916  
12917          if (co.Button)
12918              c += ' mceToolbarEndButton';
12919          else if (co.SplitButton)
12920              c += ' mceToolbarEndSplitButton';
12921          else if (co.ListBox)
12922              c += ' mceToolbarEndListBox';
12923  
12924          h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12925  
12926          return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12927      }
12928  });
12929  })(tinymce);
12930  (function(tinymce) {
12931      var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12932  
12933      tinymce.create('tinymce.AddOnManager', {
12934          AddOnManager : function() {
12935              var self = this;
12936  
12937              self.items = [];
12938              self.urls = {};
12939              self.lookup = {};
12940              self.onAdd = new Dispatcher(self);
12941          },
12942  
12943          get : function(n) {
12944              if (this.lookup[n]) {
12945                  return this.lookup[n].instance;
12946              } else {
12947                  return undefined;
12948              }
12949          },
12950  
12951          dependencies : function(n) {
12952              var result;
12953              if (this.lookup[n]) {
12954                  result = this.lookup[n].dependencies;
12955              }
12956              return result || [];
12957          },
12958  
12959          requireLangPack : function(n) {
12960              var s = tinymce.settings;
12961  
12962              if (s && s.language && s.language_load !== false)
12963                  tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12964          },
12965  
12966          add : function(id, o, dependencies) {
12967              this.items.push(o);
12968              this.lookup[id] = {instance:o, dependencies:dependencies};
12969              this.onAdd.dispatch(this, id, o);
12970  
12971              return o;
12972          },
12973          createUrl: function(baseUrl, dep) {
12974              if (typeof dep === "object") {
12975                  return dep
12976              } else {
12977                  return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
12978              }
12979          },
12980  
12981          addComponents: function(pluginName, scripts) {
12982              var pluginUrl = this.urls[pluginName];
12983              tinymce.each(scripts, function(script){
12984                  tinymce.ScriptLoader.add(pluginUrl+"/"+script);    
12985              });
12986          },
12987  
12988          load : function(n, u, cb, s) {
12989              var t = this, url = u;
12990  
12991  			function loadDependencies() {
12992                  var dependencies = t.dependencies(n);
12993                  tinymce.each(dependencies, function(dep) {
12994                      var newUrl = t.createUrl(u, dep);
12995                      t.load(newUrl.resource, newUrl, undefined, undefined);
12996                  });
12997                  if (cb) {
12998                      if (s) {
12999                          cb.call(s);
13000                      } else {
13001                          cb.call(tinymce.ScriptLoader);
13002                      }
13003                  }
13004              }
13005  
13006              if (t.urls[n])
13007                  return;
13008              if (typeof u === "object")
13009                  url = u.prefix + u.resource + u.suffix;
13010  
13011              if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
13012                  url = tinymce.baseURL + '/' + url;
13013  
13014              t.urls[n] = url.substring(0, url.lastIndexOf('/'));
13015  
13016              if (t.lookup[n]) {
13017                  loadDependencies();
13018              } else {
13019                  tinymce.ScriptLoader.add(url, loadDependencies, s);
13020              }
13021          }
13022      });
13023  
13024      // Create plugin and theme managers
13025      tinymce.PluginManager = new tinymce.AddOnManager();
13026      tinymce.ThemeManager = new tinymce.AddOnManager();
13027  }(tinymce));
13028  
13029  (function(tinymce) {
13030      // Shorten names
13031      var each = tinymce.each, extend = tinymce.extend,
13032          DOM = tinymce.DOM, Event = tinymce.dom.Event,
13033          ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13034          explode = tinymce.explode,
13035          Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
13036  
13037      // Setup some URLs where the editor API is located and where the document is
13038      tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
13039      if (!/[\/\\]$/.test(tinymce.documentBaseURL))
13040          tinymce.documentBaseURL += '/';
13041  
13042      tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
13043  
13044      tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
13045  
13046      // Add before unload listener
13047      // This was required since IE was leaking memory if you added and removed beforeunload listeners
13048      // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
13049      tinymce.onBeforeUnload = new Dispatcher(tinymce);
13050  
13051      // Must be on window or IE will leak if the editor is placed in frame or iframe
13052      Event.add(window, 'beforeunload', function(e) {
13053          tinymce.onBeforeUnload.dispatch(tinymce, e);
13054      });
13055  
13056      tinymce.onAddEditor = new Dispatcher(tinymce);
13057  
13058      tinymce.onRemoveEditor = new Dispatcher(tinymce);
13059  
13060      tinymce.EditorManager = extend(tinymce, {
13061          editors : [],
13062  
13063          i18n : {},
13064  
13065          activeEditor : null,
13066  
13067          init : function(s) {
13068              var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
13069  
13070  			function createId(elm) {
13071                  var id = elm.id;
13072      
13073                  // Use element id, or unique name or generate a unique id
13074                  if (!id) {
13075                      id = elm.name;
13076      
13077                      if (id && !DOM.get(id)) {
13078                          id = elm.name;
13079                      } else {
13080                          // Generate unique name
13081                          id = DOM.uniqueId();
13082                      }
13083  
13084                      elm.setAttribute('id', id);
13085                  }
13086  
13087                  return id;
13088              };
13089  
13090  			function execCallback(se, n, s) {
13091                  var f = se[n];
13092  
13093                  if (!f)
13094                      return;
13095  
13096                  if (tinymce.is(f, 'string')) {
13097                      s = f.replace(/\.\w+$/, '');
13098                      s = s ? tinymce.resolve(s) : 0;
13099                      f = tinymce.resolve(f);
13100                  }
13101  
13102                  return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
13103              };
13104  
13105  			function hasClass(n, c) {
13106                  return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
13107              };
13108  
13109              t.settings = s;
13110  
13111              // Legacy call
13112              Event.bind(window, 'ready', function() {
13113                  var l, co;
13114  
13115                  execCallback(s, 'onpageload');
13116  
13117                  switch (s.mode) {
13118                      case "exact":
13119                          l = s.elements || '';
13120  
13121                          if(l.length > 0) {
13122                              each(explode(l), function(v) {
13123                                  if (DOM.get(v)) {
13124                                      ed = new tinymce.Editor(v, s);
13125                                      el.push(ed);
13126                                      ed.render(1);
13127                                  } else {
13128                                      each(document.forms, function(f) {
13129                                          each(f.elements, function(e) {
13130                                              if (e.name === v) {
13131                                                  v = 'mce_editor_' + instanceCounter++;
13132                                                  DOM.setAttrib(e, 'id', v);
13133  
13134                                                  ed = new tinymce.Editor(v, s);
13135                                                  el.push(ed);
13136                                                  ed.render(1);
13137                                              }
13138                                          });
13139                                      });
13140                                  }
13141                              });
13142                          }
13143                          break;
13144  
13145                      case "textareas":
13146                      case "specific_textareas":
13147                          each(DOM.select('textarea'), function(elm) {
13148                              if (s.editor_deselector && hasClass(elm, s.editor_deselector))
13149                                  return;
13150  
13151                              if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
13152                                  ed = new tinymce.Editor(createId(elm), s);
13153                                  el.push(ed);
13154                                  ed.render(1);
13155                              }
13156                          });
13157                          break;
13158                      
13159                      default:
13160                          if (s.types) {
13161                              // Process type specific selector
13162                              each(s.types, function(type) {
13163                                  each(DOM.select(type.selector), function(elm) {
13164                                      var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
13165                                      el.push(editor);
13166                                      editor.render(1);
13167                                  });
13168                              });
13169                          } else if (s.selector) {
13170                              // Process global selector
13171                              each(DOM.select(s.selector), function(elm) {
13172                                  var editor = new tinymce.Editor(createId(elm), s);
13173                                  el.push(editor);
13174                                  editor.render(1);
13175                              });
13176                          }
13177                  }
13178  
13179                  // Call onInit when all editors are initialized
13180                  if (s.oninit) {
13181                      l = co = 0;
13182  
13183                      each(el, function(ed) {
13184                          co++;
13185  
13186                          if (!ed.initialized) {
13187                              // Wait for it
13188                              ed.onInit.add(function() {
13189                                  l++;
13190  
13191                                  // All done
13192                                  if (l == co)
13193                                      execCallback(s, 'oninit');
13194                              });
13195                          } else
13196                              l++;
13197  
13198                          // All done
13199                          if (l == co)
13200                              execCallback(s, 'oninit');                    
13201                      });
13202                  }
13203              });
13204          },
13205  
13206          get : function(id) {
13207              if (id === undef)
13208                  return this.editors;
13209  
13210              if (!this.editors.hasOwnProperty(id))
13211                  return undef;
13212  
13213              return this.editors[id];
13214          },
13215  
13216          getInstanceById : function(id) {
13217              return this.get(id);
13218          },
13219  
13220          add : function(editor) {
13221              var self = this, editors = self.editors;
13222  
13223              // Add named and index editor instance
13224              editors[editor.id] = editor;
13225              editors.push(editor);
13226  
13227              self._setActive(editor);
13228              self.onAddEditor.dispatch(self, editor);
13229  
13230  
13231              return editor;
13232          },
13233  
13234          remove : function(editor) {
13235              var t = this, i, editors = t.editors;
13236  
13237              // Not in the collection
13238              if (!editors[editor.id])
13239                  return null;
13240  
13241              delete editors[editor.id];
13242  
13243              for (i = 0; i < editors.length; i++) {
13244                  if (editors[i] == editor) {
13245                      editors.splice(i, 1);
13246                      break;
13247                  }
13248              }
13249  
13250              // Select another editor since the active one was removed
13251              if (t.activeEditor == editor)
13252                  t._setActive(editors[0]);
13253  
13254              editor.destroy();
13255              t.onRemoveEditor.dispatch(t, editor);
13256  
13257              return editor;
13258          },
13259  
13260          execCommand : function(c, u, v) {
13261              var t = this, ed = t.get(v), w;
13262  
13263  			function clr() {
13264                  ed.destroy();
13265                  w.detachEvent('onunload', clr);
13266                  w = w.tinyMCE = w.tinymce = null; // IE leak
13267              };
13268  
13269              // Manager commands
13270              switch (c) {
13271                  case "mceFocus":
13272                      ed.focus();
13273                      return true;
13274  
13275                  case "mceAddEditor":
13276                  case "mceAddControl":
13277                      if (!t.get(v))
13278                          new tinymce.Editor(v, t.settings).render();
13279  
13280                      return true;
13281  
13282                  case "mceAddFrameControl":
13283                      w = v.window;
13284  
13285                      // Add tinyMCE global instance and tinymce namespace to specified window
13286                      w.tinyMCE = tinyMCE;
13287                      w.tinymce = tinymce;
13288  
13289                      tinymce.DOM.doc = w.document;
13290                      tinymce.DOM.win = w;
13291  
13292                      ed = new tinymce.Editor(v.element_id, v);
13293                      ed.render();
13294  
13295                      // Fix IE memory leaks
13296                      if (tinymce.isIE && ! tinymce.isIE11) {
13297                          w.attachEvent('onunload', clr);
13298                      }
13299  
13300                      v.page_window = null;
13301  
13302                      return true;
13303  
13304                  case "mceRemoveEditor":
13305                  case "mceRemoveControl":
13306                      if (ed)
13307                          ed.remove();
13308  
13309                      return true;
13310  
13311                  case 'mceToggleEditor':
13312                      if (!ed) {
13313                          t.execCommand('mceAddControl', 0, v);
13314                          return true;
13315                      }
13316  
13317                      if (ed.isHidden())
13318                          ed.show();
13319                      else
13320                          ed.hide();
13321  
13322                      return true;
13323              }
13324  
13325              // Run command on active editor
13326              if (t.activeEditor)
13327                  return t.activeEditor.execCommand(c, u, v);
13328  
13329              return false;
13330          },
13331  
13332          execInstanceCommand : function(id, c, u, v) {
13333              var ed = this.get(id);
13334  
13335              if (ed)
13336                  return ed.execCommand(c, u, v);
13337  
13338              return false;
13339          },
13340  
13341          triggerSave : function() {
13342              each(this.editors, function(e) {
13343                  e.save();
13344              });
13345          },
13346  
13347          addI18n : function(p, o) {
13348              var lo, i18n = this.i18n;
13349  
13350              if (!tinymce.is(p, 'string')) {
13351                  each(p, function(o, lc) {
13352                      each(o, function(o, g) {
13353                          each(o, function(o, k) {
13354                              if (g === 'common')
13355                                  i18n[lc + '.' + k] = o;
13356                              else
13357                                  i18n[lc + '.' + g + '.' + k] = o;
13358                          });
13359                      });
13360                  });
13361              } else {
13362                  each(o, function(o, k) {
13363                      i18n[p + '.' + k] = o;
13364                  });
13365              }
13366          },
13367  
13368          // Private methods
13369  
13370          _setActive : function(editor) {
13371              this.selectedInstance = this.activeEditor = editor;
13372          }
13373      });
13374  })(tinymce);
13375  
13376  (function(tinymce) {
13377      // Shorten these names
13378      var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13379          each = tinymce.each, isGecko = tinymce.isGecko,
13380          isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13381          ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13382          explode = tinymce.explode;
13383  
13384      tinymce.create('tinymce.Editor', {
13385          Editor : function(id, settings) {
13386              var self = this, TRUE = true;
13387  
13388              self.settings = settings = extend({
13389                  id : id,
13390                  language : 'en',
13391                  theme : 'advanced',
13392                  skin : 'default',
13393                  delta_width : 0,
13394                  delta_height : 0,
13395                  popup_css : '',
13396                  plugins : '',
13397                  document_base_url : tinymce.documentBaseURL,
13398                  add_form_submit_trigger : TRUE,
13399                  submit_patch : TRUE,
13400                  add_unload_trigger : TRUE,
13401                  convert_urls : TRUE,
13402                  relative_urls : TRUE,
13403                  remove_script_host : TRUE,
13404                  table_inline_editing : false,
13405                  object_resizing : TRUE,
13406                  accessibility_focus : TRUE,
13407                  doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13408                  visual : TRUE,
13409                  font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13410                  font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13411                  apply_source_formatting : TRUE,
13412                  directionality : 'ltr',
13413                  forced_root_block : 'p',
13414                  hidden_input : TRUE,
13415                  padd_empty_editor : TRUE,
13416                  render_ui : TRUE,
13417                  indentation : '30px',
13418                  fix_table_elements : TRUE,
13419                  inline_styles : TRUE,
13420                  convert_fonts_to_spans : TRUE,
13421                  indent : 'simple',
13422                  indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13423                  indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13424                  validate : TRUE,
13425                  entity_encoding : 'named',
13426                  url_converter : self.convertURL,
13427                  url_converter_scope : self,
13428                  ie7_compat : TRUE
13429              }, settings);
13430  
13431              self.id = self.editorId = id;
13432  
13433              self.isNotDirty = false;
13434  
13435              self.plugins = {};
13436  
13437              self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13438                  base_uri : tinyMCE.baseURI
13439              });
13440  
13441              self.baseURI = tinymce.baseURI;
13442  
13443              self.contentCSS = [];
13444  
13445              self.contentStyles = [];
13446  
13447              // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13448              self.setupEvents();
13449  
13450              // Internal command handler objects
13451              self.execCommands = {};
13452              self.queryStateCommands = {};
13453              self.queryValueCommands = {};
13454  
13455              // Call setup
13456              self.execCallback('setup', self);
13457          },
13458  
13459          render : function(nst) {
13460              var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13461  
13462              // Page is not loaded yet, wait for it
13463              if (!Event.domLoaded) {
13464                  Event.add(window, 'ready', function() {
13465                      t.render();
13466                  });
13467                  return;
13468              }
13469  
13470              tinyMCE.settings = s;
13471  
13472              // Element not found, then skip initialization
13473              if (!t.getElement())
13474                  return;
13475  
13476              // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13477              // here since the browser says it has contentEditable support but there is no visible caret.
13478              if (tinymce.isIDevice && !tinymce.isIOS5)
13479                  return;
13480  
13481              // Add hidden input for non input elements inside form elements
13482              if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13483                  DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13484  
13485              // Hide target element early to prevent content flashing
13486              if (!s.content_editable) {
13487                  t.orgVisibility = t.getElement().style.visibility;
13488                  t.getElement().style.visibility = 'hidden';
13489              }
13490  
13491              if (tinymce.WindowManager)
13492                  t.windowManager = new tinymce.WindowManager(t);
13493  
13494              if (s.encoding == 'xml') {
13495                  t.onGetContent.add(function(ed, o) {
13496                      if (o.save)
13497                          o.content = DOM.encode(o.content);
13498                  });
13499              }
13500  
13501              if (s.add_form_submit_trigger) {
13502                  t.onSubmit.addToTop(function() {
13503                      if (t.initialized) {
13504                          t.save();
13505                          t.isNotDirty = 1;
13506                      }
13507                  });
13508              }
13509  
13510              if (s.add_unload_trigger) {
13511                  t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13512                      if (t.initialized && !t.destroyed && !t.isHidden())
13513                          t.save({format : 'raw', no_events : true});
13514                  });
13515              }
13516  
13517              tinymce.addUnload(t.destroy, t);
13518  
13519              if (s.submit_patch) {
13520                  t.onBeforeRenderUI.add(function() {
13521                      var n = t.getElement().form;
13522  
13523                      if (!n)
13524                          return;
13525  
13526                      // Already patched
13527                      if (n._mceOldSubmit)
13528                          return;
13529  
13530                      // Check page uses id="submit" or name="submit" for it's submit button
13531                      if (!n.submit.nodeType && !n.submit.length) {
13532                          t.formElement = n;
13533                          n._mceOldSubmit = n.submit;
13534                          n.submit = function() {
13535                              // Save all instances
13536                              tinymce.triggerSave();
13537                              t.isNotDirty = 1;
13538  
13539                              return t.formElement._mceOldSubmit(t.formElement);
13540                          };
13541                      }
13542  
13543                      n = null;
13544                  });
13545              }
13546  
13547              // Load scripts
13548  			function loadScripts() {
13549                  if (s.language && s.language_load !== false)
13550                      sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13551  
13552                  if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13553                      ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13554  
13555                  each(explode(s.plugins), function(p) {
13556                      if (p &&!PluginManager.urls[p]) {
13557                          if (p.charAt(0) == '-') {
13558                              p = p.substr(1, p.length);
13559                              var dependencies = PluginManager.dependencies(p);
13560                              each(dependencies, function(dep) {
13561                                  var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13562                                  dep = PluginManager.createUrl(defaultSettings, dep);
13563                                  PluginManager.load(dep.resource, dep);
13564                              });
13565                          } else {
13566                              // Skip safari plugin, since it is removed as of 3.3b1
13567                              if (p == 'safari') {
13568                                  return;
13569                              }
13570                              PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13571                          }
13572                      }
13573                  });
13574  
13575                  // Init when que is loaded
13576                  sl.loadQueue(function() {
13577                      if (!t.removed)
13578                          t.init();
13579                  });
13580              };
13581  
13582              loadScripts();
13583          },
13584  
13585          init : function() {
13586              var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13587  
13588              tinymce.add(t);
13589  
13590              s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13591  
13592              if (s.theme) {
13593                  if (typeof s.theme != "function") {
13594                      s.theme = s.theme.replace(/-/, '');
13595                      o = ThemeManager.get(s.theme);
13596                      t.theme = new o();
13597  
13598                      if (t.theme.init)
13599                          t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13600                  } else {
13601                      t.theme = s.theme;
13602                  }
13603              }
13604  
13605  			function initPlugin(p) {
13606                  var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13607                  if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13608                      each(PluginManager.dependencies(p), function(dep){
13609                          initPlugin(dep);
13610                      });
13611                      po = new c(t, u);
13612  
13613                      t.plugins[p] = po;
13614  
13615                      if (po.init) {
13616                          po.init(t, u);
13617                          initializedPlugins.push(p);
13618                      }
13619                  }
13620              }
13621              
13622              // Create all plugins
13623              each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13624  
13625              // Setup popup CSS path(s)
13626              if (s.popup_css !== false) {
13627                  if (s.popup_css)
13628                      s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13629                  else
13630                      s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13631              }
13632  
13633              if (s.popup_css_add)
13634                  s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13635  
13636              t.controlManager = new tinymce.ControlManager(t);
13637  
13638              // Enables users to override the control factory
13639              t.onBeforeRenderUI.dispatch(t, t.controlManager);
13640  
13641              // Measure box
13642              if (s.render_ui && t.theme) {
13643                  t.orgDisplay = e.style.display;
13644  
13645                  if (typeof s.theme != "function") {
13646                      w = s.width || e.style.width || e.offsetWidth;
13647                      h = s.height || e.style.height || e.offsetHeight;
13648                      mh = s.min_height || 100;
13649                      re = /^[0-9\.]+(|px)$/i;
13650  
13651                      if (re.test('' + w))
13652                          w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13653  
13654                      if (re.test('' + h))
13655                          h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13656  
13657                      // Render UI
13658                      o = t.theme.renderUI({
13659                          targetNode : e,
13660                          width : w,
13661                          height : h,
13662                          deltaWidth : s.delta_width,
13663                          deltaHeight : s.delta_height
13664                      });
13665  
13666                      // Resize editor
13667                      DOM.setStyles(o.sizeContainer || o.editorContainer, {
13668                          width : w,
13669                          height : h
13670                      });
13671  
13672                      h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13673                      if (h < mh)
13674                          h = mh;
13675                  } else {
13676                      o = s.theme(t, e);
13677  
13678                      // Convert element type to id:s
13679                      if (o.editorContainer.nodeType) {
13680                          o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13681                      }
13682  
13683                      // Convert element type to id:s
13684                      if (o.iframeContainer.nodeType) {
13685                          o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13686                      }
13687  
13688                      // Use specified iframe height or the targets offsetHeight
13689                      h = o.iframeHeight || e.offsetHeight;
13690  
13691                      // Store away the selection when it's changed to it can be restored later with a editor.focus() call
13692                      if (isIE) {
13693                          t.onInit.add(function(ed) {
13694                              ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() {
13695                                  ed.bookmark = ed.selection.getBookmark(1);
13696                              });
13697                          });
13698  
13699                          t.onNodeChange.add(function(ed) {
13700                              if (document.activeElement.id == ed.id + "_ifr") {
13701                                  ed.bookmark = ed.selection.getBookmark(1);
13702                              }
13703                          });
13704                      }
13705                  }
13706  
13707                  t.editorContainer = o.editorContainer;
13708              }
13709  
13710              // Load specified content CSS last
13711              if (s.content_css) {
13712                  each(explode(s.content_css), function(u) {
13713                      t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13714                  });
13715              }
13716  
13717              // Load specified content CSS last
13718              if (s.content_style) {
13719                  t.contentStyles.push(s.content_style);
13720              }
13721  
13722              // Content editable mode ends here
13723              if (s.content_editable) {
13724                  e = n = o = null; // Fix IE leak
13725                  return t.initContentBody();
13726              }
13727  
13728              // User specified a document.domain value
13729              if (document.domain && location.hostname != document.domain)
13730                  tinymce.relaxedDomain = document.domain;
13731  
13732              t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13733  
13734              // We only need to override paths if we have to
13735              // IE has a bug where it remove site absolute urls to relative ones if this is specified
13736              if (s.document_base_url != tinymce.documentBaseURL)
13737                  t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13738  
13739              // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13740              if (tinymce.isIE8) {
13741                  if (s.ie7_compat)
13742                      t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13743                  else
13744                      t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13745              }
13746  
13747              t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13748  
13749              // Load the CSS by injecting them into the HTML this will reduce "flicker"
13750              for (i = 0; i < t.contentCSS.length; i++) {
13751                  t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13752              }
13753  
13754              t.contentCSS = [];
13755  
13756              bi = s.body_id || 'tinymce';
13757              if (bi.indexOf('=') != -1) {
13758                  bi = t.getParam('body_id', '', 'hash');
13759                  bi = bi[t.id] || bi;
13760              }
13761  
13762              bc = s.body_class || '';
13763              if (bc.indexOf('=') != -1) {
13764                  bc = t.getParam('body_class', '', 'hash');
13765                  bc = bc[t.id] || '';
13766              }
13767  
13768              t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13769  
13770              // Domain relaxing enabled, then set document domain
13771              if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13772                  // We need to write the contents here in IE since multiple writes messes up refresh button and back button
13773                  u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13774              }
13775  
13776              // Create iframe
13777              // TODO: ACC add the appropriate description on this.
13778              n = DOM.add(o.iframeContainer, 'iframe', { 
13779                  id : t.id + "_ifr",
13780                  src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13781                  frameBorder : '0',
13782                  allowTransparency : "true",
13783                  title : s.aria_label,
13784                  style : {
13785                      width : '100%',
13786                      height : h,
13787                      display : 'block' // Important for Gecko to render the iframe correctly
13788                  }
13789              });
13790  
13791              t.contentAreaContainer = o.iframeContainer;
13792  
13793              if (o.editorContainer) {
13794                  DOM.get(o.editorContainer).style.display = t.orgDisplay;
13795              }
13796  
13797              // Restore visibility on target element
13798              e.style.visibility = t.orgVisibility;
13799  
13800              DOM.get(t.id).style.display = 'none';
13801              DOM.setAttrib(t.id, 'aria-hidden', true);
13802  
13803              if (!tinymce.relaxedDomain || !u)
13804                  t.initContentBody();
13805  
13806              e = n = o = null; // Cleanup
13807          },
13808  
13809          initContentBody : function() {
13810              var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13811  
13812              // Setup iframe body
13813              if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13814                  doc.open();
13815                  doc.write(self.iframeHTML);
13816                  doc.close();
13817  
13818                  if (tinymce.relaxedDomain)
13819                      doc.domain = tinymce.relaxedDomain;
13820              }
13821  
13822              if (settings.content_editable) {
13823                  DOM.addClass(targetElm, 'mceContentBody');
13824                  self.contentDocument = doc = settings.content_document || document;
13825                  self.contentWindow = settings.content_window || window;
13826                  self.bodyElement = targetElm;
13827  
13828                  // Prevent leak in IE
13829                  settings.content_document = settings.content_window = null;
13830              }
13831  
13832              // It will not steal focus while setting contentEditable
13833              body = self.getBody();
13834              body.disabled = true;
13835  
13836              if (!settings.readonly)
13837                  body.contentEditable = self.getParam('content_editable_state', true);
13838  
13839              body.disabled = false;
13840  
13841              self.schema = new tinymce.html.Schema(settings);
13842  
13843              self.dom = new tinymce.dom.DOMUtils(doc, {
13844                  keep_values : true,
13845                  url_converter : self.convertURL,
13846                  url_converter_scope : self,
13847                  hex_colors : settings.force_hex_style_colors,
13848                  class_filter : settings.class_filter,
13849                  update_styles : true,
13850                  root_element : settings.content_editable ? self.id : null,
13851                  schema : self.schema
13852              });
13853  
13854              self.parser = new tinymce.html.DomParser(settings, self.schema);
13855  
13856              // Convert src and href into data-mce-src, data-mce-href and data-mce-style
13857              self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13858                  var i = nodes.length, node, dom = self.dom, value, internalName;
13859  
13860                  while (i--) {
13861                      node = nodes[i];
13862                      value = node.attr(name);
13863                      internalName = 'data-mce-' + name;
13864  
13865                      // Add internal attribute if we need to we don't on a refresh of the document
13866                      if (!node.attributes.map[internalName]) {    
13867                          if (name === "style")
13868                              node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13869                          else
13870                              node.attr(internalName, self.convertURL(value, name, node.name));
13871                      }
13872                  }
13873              });
13874  
13875              // Keep scripts from executing
13876              self.parser.addNodeFilter('script', function(nodes, name) {
13877                  var i = nodes.length, node;
13878  
13879                  while (i--) {
13880                      node = nodes[i];
13881                      node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13882                  }
13883              });
13884  
13885              self.parser.addNodeFilter('#cdata', function(nodes, name) {
13886                  var i = nodes.length, node;
13887  
13888                  while (i--) {
13889                      node = nodes[i];
13890                      node.type = 8;
13891                      node.name = '#comment';
13892                      node.value = '[CDATA[' + node.value + ']]';
13893                  }
13894              });
13895  
13896              self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13897                  var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13898  
13899                  while (i--) {
13900                      node = nodes[i];
13901  
13902                      if (node.isEmpty(nonEmptyElements))
13903                          node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13904                  }
13905              });
13906  
13907              self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13908  
13909              self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13910  
13911              self.formatter = new tinymce.Formatter(self);
13912  
13913              self.undoManager = new tinymce.UndoManager(self);
13914  
13915              self.forceBlocks = new tinymce.ForceBlocks(self);
13916              self.enterKey = new tinymce.EnterKey(self);
13917              self.editorCommands = new tinymce.EditorCommands(self);
13918  
13919              self.onExecCommand.add(function(editor, command) {
13920                  // Don't refresh the select lists until caret move
13921                  if (!/^(FontName|FontSize)$/.test(command))
13922                      self.nodeChanged();
13923              });
13924  
13925              // Pass through
13926              self.serializer.onPreProcess.add(function(se, o) {
13927                  return self.onPreProcess.dispatch(self, o, se);
13928              });
13929  
13930              self.serializer.onPostProcess.add(function(se, o) {
13931                  return self.onPostProcess.dispatch(self, o, se);
13932              });
13933  
13934              self.onPreInit.dispatch(self);
13935  
13936              if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13937                  doc.body.spellcheck = false;
13938  
13939              if (!settings.readonly) {
13940                  self.bindNativeEvents();
13941              }
13942  
13943              self.controlManager.onPostRender.dispatch(self, self.controlManager);
13944              self.onPostRender.dispatch(self);
13945  
13946              self.quirks = tinymce.util.Quirks(self);
13947  
13948              if (settings.directionality)
13949                  body.dir = settings.directionality;
13950  
13951              if (settings.nowrap)
13952                  body.style.whiteSpace = "nowrap";
13953  
13954              if (settings.protect) {
13955                  self.onBeforeSetContent.add(function(ed, o) {
13956                      each(settings.protect, function(pattern) {
13957                          o.content = o.content.replace(pattern, function(str) {
13958                              return '<!--mce:protected ' + escape(str) + '-->';
13959                          });
13960                      });
13961                  });
13962              }
13963  
13964              // Add visual aids when new contents is added
13965              self.onSetContent.add(function() {
13966                  self.addVisual(self.getBody());
13967              });
13968  
13969              // Remove empty contents
13970              if (settings.padd_empty_editor) {
13971                  self.onPostProcess.add(function(ed, o) {
13972                      o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
13973                  });
13974              }
13975  
13976              self.load({initial : true, format : 'html'});
13977              self.startContent = self.getContent({format : 'raw'});
13978  
13979              self.initialized = true;
13980  
13981              self.onInit.dispatch(self);
13982              self.execCallback('setupcontent_callback', self.id, body, doc);
13983              self.execCallback('init_instance_callback', self);
13984              self.focus(true);
13985              self.nodeChanged({initial : true});
13986  
13987              // Add editor specific CSS styles
13988              if (self.contentStyles.length > 0) {
13989                  contentCssText = '';
13990  
13991                  each(self.contentStyles, function(style) {
13992                      contentCssText += style + "\r\n";
13993                  });
13994  
13995                  self.dom.addStyle(contentCssText);
13996              }
13997  
13998              // Load specified content CSS last
13999              each(self.contentCSS, function(url) {
14000                  self.dom.loadCSS(url);
14001              });
14002  
14003              // Handle auto focus
14004              if (settings.auto_focus) {
14005                  setTimeout(function () {
14006                      var ed = tinymce.get(settings.auto_focus);
14007  
14008                      ed.selection.select(ed.getBody(), 1);
14009                      ed.selection.collapse(1);
14010                      ed.getBody().focus();
14011                      ed.getWin().focus();
14012                  }, 100);
14013              }
14014  
14015              // Clean up references for IE
14016              targetElm = doc = body = null;
14017          },
14018  
14019          focus : function(skip_focus) {
14020              var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
14021  
14022              if (!skip_focus) {
14023                  if (self.bookmark) {
14024                      selection.moveToBookmark(self.bookmark);
14025                      self.bookmark = null;
14026                  }
14027  
14028                  // Get selected control element
14029                  ieRng = selection.getRng();
14030                  if (ieRng.item) {
14031                      controlElm = ieRng.item(0);
14032                  }
14033  
14034                  self._refreshContentEditable();
14035  
14036                  // Focus the window iframe
14037                  if (!contentEditable) {
14038                      self.getWin().focus();
14039                  }
14040  
14041                  // Focus the body as well since it's contentEditable
14042                  if (tinymce.isGecko || contentEditable) {
14043                      body = self.getBody();
14044  
14045                      // Check for setActive since it doesn't scroll to the element
14046                      if (body.setActive && ! tinymce.isIE11) {
14047                          body.setActive();
14048                      } else {
14049                          body.focus();
14050                      }
14051  
14052                      if (contentEditable) {
14053                          selection.normalize();
14054                      }
14055                  }
14056  
14057                  // Restore selected control element
14058                  // This is needed when for example an image is selected within a
14059                  // layer a call to focus will then remove the control selection
14060                  if (controlElm && controlElm.ownerDocument == doc) {
14061                      ieRng = doc.body.createControlRange();
14062                      ieRng.addElement(controlElm);
14063                      ieRng.select();
14064                  }
14065              }
14066  
14067              if (tinymce.activeEditor != self) {
14068                  if ((oed = tinymce.activeEditor) != null)
14069                      oed.onDeactivate.dispatch(oed, self);
14070  
14071                  self.onActivate.dispatch(self, oed);
14072              }
14073  
14074              tinymce._setActive(self);
14075          },
14076  
14077          execCallback : function(n) {
14078              var t = this, f = t.settings[n], s;
14079  
14080              if (!f)
14081                  return;
14082  
14083              // Look through lookup
14084              if (t.callbackLookup && (s = t.callbackLookup[n])) {
14085                  f = s.func;
14086                  s = s.scope;
14087              }
14088  
14089              if (is(f, 'string')) {
14090                  s = f.replace(/\.\w+$/, '');
14091                  s = s ? tinymce.resolve(s) : 0;
14092                  f = tinymce.resolve(f);
14093                  t.callbackLookup = t.callbackLookup || {};
14094                  t.callbackLookup[n] = {func : f, scope : s};
14095              }
14096  
14097              return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
14098          },
14099  
14100          translate : function(s) {
14101              var c = this.settings.language || 'en', i18n = tinymce.i18n;
14102  
14103              if (!s)
14104                  return '';
14105  
14106              return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
14107                  return i18n[c + '.' + b] || '{#' + b + '}';
14108              });
14109          },
14110  
14111          getLang : function(n, dv) {
14112              return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
14113          },
14114  
14115          getParam : function(n, dv, ty) {
14116              var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
14117  
14118              if (ty === 'hash') {
14119                  o = {};
14120  
14121                  if (is(v, 'string')) {
14122                      each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
14123                          v = v.split('=');
14124  
14125                          if (v.length > 1)
14126                              o[tr(v[0])] = tr(v[1]);
14127                          else
14128                              o[tr(v[0])] = tr(v);
14129                      });
14130                  } else
14131                      o = v;
14132  
14133                  return o;
14134              }
14135  
14136              return v;
14137          },
14138  
14139          nodeChanged : function(o) {
14140              var self = this, selection = self.selection, node;
14141  
14142              // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
14143              if (self.initialized) {
14144                  o = o || {};
14145  
14146                  // Get start node
14147                  node = selection.getStart() || self.getBody();
14148                  node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
14149  
14150                  // Get parents and add them to object
14151                  o.parents = [];
14152                  self.dom.getParent(node, function(node) {
14153                      if (node.nodeName == 'BODY')
14154                          return true;
14155  
14156                      o.parents.push(node);
14157                  });
14158  
14159                  self.onNodeChange.dispatch(
14160                      self,
14161                      o ? o.controlManager || self.controlManager : self.controlManager,
14162                      node,
14163                      selection.isCollapsed(),
14164                      o
14165                  );
14166              }
14167          },
14168  
14169          addButton : function(name, settings) {
14170              var self = this;
14171  
14172              self.buttons = self.buttons || {};
14173              self.buttons[name] = settings;
14174          },
14175  
14176          addCommand : function(name, callback, scope) {
14177              this.execCommands[name] = {func : callback, scope : scope || this};
14178          },
14179  
14180          addQueryStateHandler : function(name, callback, scope) {
14181              this.queryStateCommands[name] = {func : callback, scope : scope || this};
14182          },
14183  
14184          addQueryValueHandler : function(name, callback, scope) {
14185              this.queryValueCommands[name] = {func : callback, scope : scope || this};
14186          },
14187  
14188          addShortcut : function(pa, desc, cmd_func, sc) {
14189              var t = this, c;
14190  
14191              if (t.settings.custom_shortcuts === false)
14192                  return false;
14193  
14194              t.shortcuts = t.shortcuts || {};
14195  
14196              if (is(cmd_func, 'string')) {
14197                  c = cmd_func;
14198  
14199                  cmd_func = function() {
14200                      t.execCommand(c, false, null);
14201                  };
14202              }
14203  
14204              if (is(cmd_func, 'object')) {
14205                  c = cmd_func;
14206  
14207                  cmd_func = function() {
14208                      t.execCommand(c[0], c[1], c[2]);
14209                  };
14210              }
14211  
14212              each(explode(pa), function(pa) {
14213                  var o = {
14214                      func : cmd_func,
14215                      scope : sc || this,
14216                      desc : t.translate(desc),
14217                      alt : false,
14218                      ctrl : false,
14219                      shift : false
14220                  };
14221  
14222                  each(explode(pa, '+'), function(v) {
14223                      switch (v) {
14224                          case 'alt':
14225                          case 'ctrl':
14226                          case 'shift':
14227                              o[v] = true;
14228                              break;
14229  
14230                          default:
14231                              o.charCode = v.charCodeAt(0);
14232                              o.keyCode = v.toUpperCase().charCodeAt(0);
14233                      }
14234                  });
14235  
14236                  t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
14237              });
14238  
14239              return true;
14240          },
14241  
14242          execCommand : function(cmd, ui, val, a) {
14243              var t = this, s = 0, o, st;
14244  
14245              if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
14246                  t.focus();
14247  
14248              a = extend({}, a);
14249              t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
14250              if (a.terminate)
14251                  return false;
14252  
14253              // Command callback
14254              if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
14255                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14256                  return true;
14257              }
14258  
14259              // Registred commands
14260              if (o = t.execCommands[cmd]) {
14261                  st = o.func.call(o.scope, ui, val);
14262  
14263                  // Fall through on true
14264                  if (st !== true) {
14265                      t.onExecCommand.dispatch(t, cmd, ui, val, a);
14266                      return st;
14267                  }
14268              }
14269  
14270              // Plugin commands
14271              each(t.plugins, function(p) {
14272                  if (p.execCommand && p.execCommand(cmd, ui, val)) {
14273                      t.onExecCommand.dispatch(t, cmd, ui, val, a);
14274                      s = 1;
14275                      return false;
14276                  }
14277              });
14278  
14279              if (s)
14280                  return true;
14281  
14282              // Theme commands
14283              if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
14284                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14285                  return true;
14286              }
14287  
14288              // Editor commands
14289              if (t.editorCommands.execCommand(cmd, ui, val)) {
14290                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14291                  return true;
14292              }
14293  
14294              // Browser commands
14295              t.getDoc().execCommand(cmd, ui, val);
14296              t.onExecCommand.dispatch(t, cmd, ui, val, a);
14297          },
14298  
14299          queryCommandState : function(cmd) {
14300              var t = this, o, s;
14301  
14302              // Is hidden then return undefined
14303              if (t._isHidden())
14304                  return;
14305  
14306              // Registred commands
14307              if (o = t.queryStateCommands[cmd]) {
14308                  s = o.func.call(o.scope);
14309  
14310                  // Fall though on true
14311                  if (s !== true)
14312                      return s;
14313              }
14314  
14315              // Registred commands
14316              o = t.editorCommands.queryCommandState(cmd);
14317              if (o !== -1)
14318                  return o;
14319  
14320              // Browser commands
14321              try {
14322                  return this.getDoc().queryCommandState(cmd);
14323              } catch (ex) {
14324                  // Fails sometimes see bug: 1896577
14325              }
14326          },
14327  
14328          queryCommandValue : function(c) {
14329              var t = this, o, s;
14330  
14331              // Is hidden then return undefined
14332              if (t._isHidden())
14333                  return;
14334  
14335              // Registred commands
14336              if (o = t.queryValueCommands[c]) {
14337                  s = o.func.call(o.scope);
14338  
14339                  // Fall though on true
14340                  if (s !== true)
14341                      return s;
14342              }
14343  
14344              // Registred commands
14345              o = t.editorCommands.queryCommandValue(c);
14346              if (is(o))
14347                  return o;
14348  
14349              // Browser commands
14350              try {
14351                  return this.getDoc().queryCommandValue(c);
14352              } catch (ex) {
14353                  // Fails sometimes see bug: 1896577
14354              }
14355          },
14356  
14357          show : function() {
14358              var self = this;
14359  
14360              DOM.show(self.getContainer());
14361              DOM.hide(self.id);
14362              self.load();
14363          },
14364  
14365          hide : function() {
14366              var self = this, doc = self.getDoc();
14367  
14368              // Fixed bug where IE has a blinking cursor left from the editor
14369              if (isIE && doc)
14370                  doc.execCommand('SelectAll');
14371  
14372              // We must save before we hide so Safari doesn't crash
14373              self.save();
14374  
14375              // defer the call to hide to prevent an IE9 crash #4921
14376              DOM.hide(self.getContainer());
14377              DOM.setStyle(self.id, 'display', self.orgDisplay);
14378          },
14379  
14380          isHidden : function() {
14381              return !DOM.isHidden(this.id);
14382          },
14383  
14384          setProgressState : function(b, ti, o) {
14385              this.onSetProgressState.dispatch(this, b, ti, o);
14386  
14387              return b;
14388          },
14389  
14390          load : function(o) {
14391              var t = this, e = t.getElement(), h;
14392  
14393              if (e) {
14394                  o = o || {};
14395                  o.load = true;
14396  
14397                  // Double encode existing entities in the value
14398                  h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14399                  o.element = e;
14400  
14401                  if (!o.no_events)
14402                      t.onLoadContent.dispatch(t, o);
14403  
14404                  o.element = e = null;
14405  
14406                  return h;
14407              }
14408          },
14409  
14410          save : function(o) {
14411              var t = this, e = t.getElement(), h, f;
14412  
14413              if (!e || !t.initialized)
14414                  return;
14415  
14416              o = o || {};
14417              o.save = true;
14418  
14419              o.element = e;
14420              h = o.content = t.getContent(o);
14421  
14422              if (!o.no_events)
14423                  t.onSaveContent.dispatch(t, o);
14424  
14425              h = o.content;
14426  
14427              if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14428                  e.innerHTML = h;
14429  
14430                  // Update hidden form element
14431                  if (f = DOM.getParent(t.id, 'form')) {
14432                      each(f.elements, function(e) {
14433                          if (e.name == t.id) {
14434                              e.value = h;
14435                              return false;
14436                          }
14437                      });
14438                  }
14439              } else
14440                  e.value = h;
14441  
14442              o.element = e = null;
14443  
14444              return h;
14445          },
14446  
14447          setContent : function(content, args) {
14448              var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14449  
14450              // Setup args object
14451              args = args || {};
14452              args.format = args.format || 'html';
14453              args.set = true;
14454              args.content = content;
14455  
14456              // Do preprocessing
14457              if (!args.no_events)
14458                  self.onBeforeSetContent.dispatch(self, args);
14459  
14460              content = args.content;
14461  
14462              // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14463              // It will also be impossible to place the caret in the editor unless there is a BR element present
14464              if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14465                  forcedRootBlockName = self.settings.forced_root_block;
14466                  if (forcedRootBlockName)
14467                      content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14468                  else
14469                      content = '<br data-mce-bogus="1">';
14470  
14471                  body.innerHTML = content;
14472                  self.selection.select(body, true);
14473                  self.selection.collapse(true);
14474                  return;
14475              }
14476  
14477              // Parse and serialize the html
14478              if (args.format !== 'raw') {
14479                  content = new tinymce.html.Serializer({}, self.schema).serialize(
14480                      self.parser.parse(content)
14481                  );
14482              }
14483  
14484              // Set the new cleaned contents to the editor
14485              args.content = tinymce.trim(content);
14486              self.dom.setHTML(body, args.content);
14487  
14488              // Do post processing
14489              if (!args.no_events)
14490                  self.onSetContent.dispatch(self, args);
14491  
14492              // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14493              if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14494                  self.selection.normalize();
14495              }
14496  
14497              return args.content;
14498          },
14499  
14500          getContent : function(args) {
14501              var self = this, content, body = self.getBody();
14502  
14503              // Setup args object
14504              args = args || {};
14505              args.format = args.format || 'html';
14506              args.get = true;
14507              args.getInner = true;
14508  
14509              // Do preprocessing
14510              if (!args.no_events)
14511                  self.onBeforeGetContent.dispatch(self, args);
14512  
14513              // Get raw contents or by default the cleaned contents
14514              if (args.format == 'raw')
14515                  content = body.innerHTML;
14516              else if (args.format == 'text')
14517                  content = body.innerText || body.textContent;
14518              else
14519                  content = self.serializer.serialize(body, args);
14520  
14521              // Trim whitespace in beginning/end of HTML
14522              if (args.format != 'text') {
14523                  args.content = tinymce.trim(content);
14524              } else {
14525                  args.content = content;
14526              }
14527  
14528              // Do post processing
14529              if (!args.no_events)
14530                  self.onGetContent.dispatch(self, args);
14531  
14532              return args.content;
14533          },
14534  
14535          isDirty : function() {
14536              var self = this;
14537  
14538              return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14539          },
14540  
14541          getContainer : function() {
14542              var self = this;
14543  
14544              if (!self.container)
14545                  self.container = DOM.get(self.editorContainer || self.id + '_parent');
14546  
14547              return self.container;
14548          },
14549  
14550          getContentAreaContainer : function() {
14551              return this.contentAreaContainer;
14552          },
14553  
14554          getElement : function() {
14555              return DOM.get(this.settings.content_element || this.id);
14556          },
14557  
14558          getWin : function() {
14559              var self = this, elm;
14560  
14561              if (!self.contentWindow) {
14562                  elm = DOM.get(self.id + "_ifr");
14563  
14564                  if (elm)
14565                      self.contentWindow = elm.contentWindow;
14566              }
14567  
14568              return self.contentWindow;
14569          },
14570  
14571          getDoc : function() {
14572              var self = this, win;
14573  
14574              if (!self.contentDocument) {
14575                  win = self.getWin();
14576  
14577                  if (win)
14578                      self.contentDocument = win.document;
14579              }
14580  
14581              return self.contentDocument;
14582          },
14583  
14584          getBody : function() {
14585              return this.bodyElement || this.getDoc().body;
14586          },
14587  
14588          convertURL : function(url, name, elm) {
14589              var self = this, settings = self.settings;
14590  
14591              // Use callback instead
14592              if (settings.urlconverter_callback)
14593                  return self.execCallback('urlconverter_callback', url, elm, true, name);
14594  
14595              // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14596              if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14597                  return url;
14598  
14599              // Convert to relative
14600              if (settings.relative_urls)
14601                  return self.documentBaseURI.toRelative(url);
14602  
14603              // Convert to absolute
14604              url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14605  
14606              return url;
14607          },
14608  
14609          addVisual : function(elm) {
14610              var self = this, settings = self.settings, dom = self.dom, cls;
14611  
14612              elm = elm || self.getBody();
14613  
14614              if (!is(self.hasVisual))
14615                  self.hasVisual = settings.visual;
14616  
14617              each(dom.select('table,a', elm), function(elm) {
14618                  var value;
14619  
14620                  switch (elm.nodeName) {
14621                      case 'TABLE':
14622                          cls = settings.visual_table_class || 'mceItemTable';
14623                          value = dom.getAttrib(elm, 'border');
14624  
14625                          if (!value || value == '0') {
14626                              if (self.hasVisual)
14627                                  dom.addClass(elm, cls);
14628                              else
14629                                  dom.removeClass(elm, cls);
14630                          }
14631  
14632                          return;
14633  
14634                      case 'A':
14635                          if (!dom.getAttrib(elm, 'href', false)) {
14636                              value = dom.getAttrib(elm, 'name') || elm.id;
14637                              cls = 'mceItemAnchor';
14638  
14639                              if (value) {
14640                                  if (self.hasVisual)
14641                                      dom.addClass(elm, cls);
14642                                  else
14643                                      dom.removeClass(elm, cls);
14644                              }
14645                          }
14646  
14647                          return;
14648                  }
14649              });
14650  
14651              self.onVisualAid.dispatch(self, elm, self.hasVisual);
14652          },
14653  
14654          remove : function() {
14655              var self = this, elm = self.getContainer(), doc = self.getDoc();
14656  
14657              if (!self.removed) {
14658                  self.removed = 1; // Cancels post remove event execution
14659  
14660                  // Fixed bug where IE has a blinking cursor left from the editor
14661                  if (isIE && doc)
14662                      doc.execCommand('SelectAll');
14663  
14664                  // We must save before we hide so Safari doesn't crash
14665                  self.save();
14666  
14667                  DOM.setStyle(self.id, 'display', self.orgDisplay);
14668  
14669                  // Don't clear the window or document if content editable
14670                  // is enabled since other instances might still be present
14671                  if (!self.settings.content_editable) {
14672                      Event.unbind(self.getWin());
14673                      Event.unbind(self.getDoc());
14674                  }
14675  
14676                  Event.unbind(self.getBody());
14677                  Event.clear(elm);
14678  
14679                  self.execCallback('remove_instance_callback', self);
14680                  self.onRemove.dispatch(self);
14681  
14682                  // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14683                  self.onExecCommand.listeners = [];
14684  
14685                  tinymce.remove(self);
14686                  DOM.remove(elm);
14687              }
14688          },
14689  
14690          destroy : function(s) {
14691              var t = this;
14692  
14693              // One time is enough
14694              if (t.destroyed)
14695                  return;
14696  
14697              // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14698              if (isGecko) {
14699                  Event.unbind(t.getDoc());
14700                  Event.unbind(t.getWin());
14701                  Event.unbind(t.getBody());
14702              }
14703  
14704              if (!s) {
14705                  tinymce.removeUnload(t.destroy);
14706                  tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14707  
14708                  // Manual destroy
14709                  if (t.theme && t.theme.destroy)
14710                      t.theme.destroy();
14711  
14712                  // Destroy controls, selection and dom
14713                  t.controlManager.destroy();
14714                  t.selection.destroy();
14715                  t.dom.destroy();
14716              }
14717  
14718              if (t.formElement) {
14719                  t.formElement.submit = t.formElement._mceOldSubmit;
14720                  t.formElement._mceOldSubmit = null;
14721              }
14722  
14723              t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14724  
14725              if (t.selection)
14726                  t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14727  
14728              t.destroyed = 1;
14729          },
14730  
14731          // Internal functions
14732  
14733          _refreshContentEditable : function() {
14734              var self = this, body, parent;
14735  
14736              // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14737              if (self._isHidden()) {
14738                  body = self.getBody();
14739                  parent = body.parentNode;
14740  
14741                  parent.removeChild(body);
14742                  parent.appendChild(body);
14743  
14744                  body.focus();
14745              }
14746          },
14747  
14748          _isHidden : function() {
14749              var s;
14750  
14751              if (!isGecko)
14752                  return 0;
14753  
14754              // Weird, wheres that cursor selection?
14755              s = this.selection.getSel();
14756              return (!s || !s.rangeCount || s.rangeCount === 0);
14757          }
14758      });
14759  })(tinymce);
14760  (function(tinymce) {
14761      var each = tinymce.each;
14762  
14763      tinymce.Editor.prototype.setupEvents = function() {
14764          var self = this, settings = self.settings;
14765  
14766          // Add events to the editor
14767          each([
14768              'onPreInit',
14769  
14770              'onBeforeRenderUI',
14771  
14772              'onPostRender',
14773  
14774              'onLoad',
14775  
14776              'onInit',
14777  
14778              'onRemove',
14779  
14780              'onActivate',
14781  
14782              'onDeactivate',
14783  
14784              'onClick',
14785  
14786              'onEvent',
14787  
14788              'onMouseUp',
14789  
14790              'onMouseDown',
14791  
14792              'onDblClick',
14793  
14794              'onKeyDown',
14795  
14796              'onKeyUp',
14797  
14798              'onKeyPress',
14799  
14800              'onContextMenu',
14801  
14802              'onSubmit',
14803  
14804              'onReset',
14805  
14806              'onPaste',
14807  
14808              'onPreProcess',
14809  
14810              'onPostProcess',
14811  
14812              'onBeforeSetContent',
14813  
14814              'onBeforeGetContent',
14815  
14816              'onSetContent',
14817  
14818              'onGetContent',
14819  
14820              'onLoadContent',
14821  
14822              'onSaveContent',
14823  
14824              'onNodeChange',
14825  
14826              'onChange',
14827  
14828              'onBeforeExecCommand',
14829  
14830              'onExecCommand',
14831  
14832              'onUndo',
14833  
14834              'onRedo',
14835  
14836              'onVisualAid',
14837  
14838              'onSetProgressState',
14839  
14840              'onSetAttrib'
14841          ], function(name) {
14842              self[name] = new tinymce.util.Dispatcher(self);
14843          });
14844  
14845          // Handle legacy cleanup_callback option
14846          if (settings.cleanup_callback) {
14847              self.onBeforeSetContent.add(function(ed, o) {
14848                  o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14849              });
14850  
14851              self.onPreProcess.add(function(ed, o) {
14852                  if (o.set)
14853                      ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14854  
14855                  if (o.get)
14856                      ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14857              });
14858  
14859              self.onPostProcess.add(function(ed, o) {
14860                  if (o.set)
14861                      o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14862  
14863                  if (o.get)                        
14864                      o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14865              });
14866          }
14867  
14868          // Handle legacy save_callback option
14869          if (settings.save_callback) {
14870              self.onGetContent.add(function(ed, o) {
14871                  if (o.save)
14872                      o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14873              });
14874          }
14875  
14876          // Handle legacy handle_event_callback option
14877          if (settings.handle_event_callback) {
14878              self.onEvent.add(function(ed, e, o) {
14879                  if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14880                      e.preventDefault();
14881                      e.stopPropagation();
14882                  }
14883              });
14884          }
14885  
14886          // Handle legacy handle_node_change_callback option
14887          if (settings.handle_node_change_callback) {
14888              self.onNodeChange.add(function(ed, cm, n) {
14889                  ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14890              });
14891          }
14892  
14893          // Handle legacy save_callback option
14894          if (settings.save_callback) {
14895              self.onSaveContent.add(function(ed, o) {
14896                  var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14897  
14898                  if (h)
14899                      o.content = h;
14900              });
14901          }
14902  
14903          // Handle legacy onchange_callback option
14904          if (settings.onchange_callback) {
14905              self.onChange.add(function(ed, l) {
14906                  ed.execCallback('onchange_callback', ed, l);
14907              });
14908          }
14909      };
14910  
14911      tinymce.Editor.prototype.bindNativeEvents = function() {
14912          // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14913          var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14914  
14915          nativeToDispatcherMap = {
14916              mouseup : 'onMouseUp',
14917              mousedown : 'onMouseDown',
14918              click : 'onClick',
14919              keyup : 'onKeyUp',
14920              keydown : 'onKeyDown',
14921              keypress : 'onKeyPress',
14922              submit : 'onSubmit',
14923              reset : 'onReset',
14924              contextmenu : 'onContextMenu',
14925              dblclick : 'onDblClick',
14926              paste : 'onPaste' // Doesn't work in all browsers yet
14927          };
14928  
14929          // Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14930  		function eventHandler(evt, args) {
14931              var type = evt.type;
14932  
14933              // Don't fire events when it's removed
14934              if (self.removed)
14935                  return;
14936  
14937              // Sends the native event out to a global dispatcher then to the specific event dispatcher
14938              if (self.onEvent.dispatch(self, evt, args) !== false) {
14939                  self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14940              }
14941          };
14942  
14943          // Opera doesn't support focus event for contentEditable elements so we need to fake it
14944  		function doOperaFocus(e) {
14945              self.focus(true);
14946          };
14947  
14948  		function nodeChanged(ed, e) {
14949              // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14950              if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14951                  self.selection.normalize();
14952              }
14953  
14954              self.nodeChanged();
14955          }
14956  
14957          // Add DOM events
14958          each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14959              var root = settings.content_editable ? self.getBody() : self.getDoc();
14960  
14961              switch (nativeName) {
14962                  case 'contextmenu':
14963                      dom.bind(root, nativeName, eventHandler);
14964                      break;
14965  
14966                  case 'paste':
14967                      dom.bind(self.getBody(), nativeName, eventHandler);
14968                      break;
14969  
14970                  case 'submit':
14971                  case 'reset':
14972                      dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
14973                      break;
14974  
14975                  default:
14976                      dom.bind(root, nativeName, eventHandler);
14977              }
14978          });
14979  
14980          // Set the editor as active when focused
14981          dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
14982              self.focus(true);
14983          });
14984  
14985          if (settings.content_editable && tinymce.isOpera) {
14986              dom.bind(self.getBody(), 'click', doOperaFocus);
14987              dom.bind(self.getBody(), 'keydown', doOperaFocus);
14988          }
14989  
14990          // Add node change handler
14991          self.onMouseUp.add(nodeChanged);
14992  
14993          self.onKeyUp.add(function(ed, e) {
14994              var keyCode = e.keyCode;
14995  
14996              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
14997                  nodeChanged(ed, e);
14998          });
14999  
15000          // Add reset handler
15001          self.onReset.add(function() {
15002              self.setContent(self.startContent, {format : 'raw'});
15003          });
15004  
15005          // Add shortcuts
15006  		function handleShortcut(e, execute) {
15007              if (e.altKey || e.ctrlKey || e.metaKey) {
15008                  each(self.shortcuts, function(shortcut) {
15009                      var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
15010  
15011                      if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
15012                          return;
15013  
15014                      if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
15015                          e.preventDefault();
15016  
15017                          if (execute) {
15018                              shortcut.func.call(shortcut.scope);
15019                          }
15020  
15021                          return true;
15022                      }
15023                  });
15024              }
15025          };
15026  
15027          self.onKeyUp.add(function(ed, e) {
15028              handleShortcut(e);
15029          });
15030  
15031          self.onKeyPress.add(function(ed, e) {
15032              handleShortcut(e);
15033          });
15034  
15035          self.onKeyDown.add(function(ed, e) {
15036              handleShortcut(e, true);
15037          });
15038  
15039          if (tinymce.isOpera) {
15040              self.onClick.add(function(ed, e) {
15041                  e.preventDefault();
15042              });
15043          }
15044      };
15045  })(tinymce);
15046  (function(tinymce) {
15047      // Added for compression purposes
15048      var each = tinymce.each, undef, TRUE = true, FALSE = false;
15049  
15050      tinymce.EditorCommands = function(editor) {
15051          var dom = editor.dom,
15052              selection = editor.selection,
15053              commands = {state: {}, exec : {}, value : {}},
15054              settings = editor.settings,
15055              formatter = editor.formatter,
15056              bookmark;
15057  
15058  		function execCommand(command, ui, value) {
15059              var func;
15060  
15061              command = command.toLowerCase();
15062              if (func = commands.exec[command]) {
15063                  func(command, ui, value);
15064                  return TRUE;
15065              }
15066  
15067              return FALSE;
15068          };
15069  
15070  		function queryCommandState(command) {
15071              var func;
15072  
15073              command = command.toLowerCase();
15074              if (func = commands.state[command])
15075                  return func(command);
15076  
15077              return -1;
15078          };
15079  
15080  		function queryCommandValue(command) {
15081              var func;
15082  
15083              command = command.toLowerCase();
15084              if (func = commands.value[command])
15085                  return func(command);
15086  
15087              return FALSE;
15088          };
15089  
15090  		function addCommands(command_list, type) {
15091              type = type || 'exec';
15092  
15093              each(command_list, function(callback, command) {
15094                  each(command.toLowerCase().split(','), function(command) {
15095                      commands[type][command] = callback;
15096                  });
15097              });
15098          };
15099  
15100          // Expose public methods
15101          tinymce.extend(this, {
15102              execCommand : execCommand,
15103              queryCommandState : queryCommandState,
15104              queryCommandValue : queryCommandValue,
15105              addCommands : addCommands
15106          });
15107  
15108          // Private methods
15109  
15110  		function execNativeCommand(command, ui, value) {
15111              if (ui === undef)
15112                  ui = FALSE;
15113  
15114              if (value === undef)
15115                  value = null;
15116  
15117              return editor.getDoc().execCommand(command, ui, value);
15118          };
15119  
15120  		function isFormatMatch(name) {
15121              return formatter.match(name);
15122          };
15123  
15124  		function toggleFormat(name, value) {
15125              formatter.toggle(name, value ? {value : value} : undef);
15126          };
15127  
15128  		function storeSelection(type) {
15129              bookmark = selection.getBookmark(type);
15130          };
15131  
15132  		function restoreSelection() {
15133              selection.moveToBookmark(bookmark);
15134          };
15135  
15136          // Add execCommand overrides
15137          addCommands({
15138              // Ignore these, added for compatibility
15139              'mceResetDesignMode,mceBeginUndoLevel' : function() {},
15140  
15141              // Add undo manager logic
15142              'mceEndUndoLevel,mceAddUndoLevel' : function() {
15143                  editor.undoManager.add();
15144              },
15145  
15146              'Cut,Copy,Paste' : function(command) {
15147                  var doc = editor.getDoc(), failed;
15148  
15149                  // Try executing the native command
15150                  try {
15151                      execNativeCommand(command);
15152                  } catch (ex) {
15153                      // Command failed
15154                      failed = TRUE;
15155                  }
15156  
15157                  // Present alert message about clipboard access not being available
15158                  if (failed || !doc.queryCommandSupported(command)) {
15159                      if (tinymce.isGecko) {
15160                          editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
15161                              if (state)
15162                                  open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
15163                          });
15164                      } else
15165                          editor.windowManager.alert(editor.getLang('clipboard_no_support'));
15166                  }
15167              },
15168  
15169              // Override unlink command
15170              unlink : function(command) {
15171                  if (selection.isCollapsed())
15172                      selection.select(selection.getNode());
15173  
15174                  execNativeCommand(command);
15175                  selection.collapse(FALSE);
15176              },
15177  
15178              // Override justify commands to use the text formatter engine
15179              'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15180                  var align = command.substring(7);
15181  
15182                  // Remove all other alignments first
15183                  each('left,center,right,full'.split(','), function(name) {
15184                      if (align != name)
15185                          formatter.remove('align' + name);
15186                  });
15187  
15188                  toggleFormat('align' + align);
15189                  execCommand('mceRepaint');
15190              },
15191  
15192              // Override list commands to fix WebKit bug
15193              'InsertUnorderedList,InsertOrderedList' : function(command) {
15194                  var listElm, listParent;
15195  
15196                  execNativeCommand(command);
15197  
15198                  // WebKit produces lists within block elements so we need to split them
15199                  // we will replace the native list creation logic to custom logic later on
15200                  // TODO: Remove this when the list creation logic is removed
15201                  listElm = dom.getParent(selection.getNode(), 'ol,ul');
15202                  if (listElm) {
15203                      listParent = listElm.parentNode;
15204  
15205                      // If list is within a text block then split that block
15206                      if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
15207                          storeSelection();
15208                          dom.split(listParent, listElm);
15209                          restoreSelection();
15210                      }
15211                  }
15212              },
15213  
15214              // Override commands to use the text formatter engine
15215              'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15216                  toggleFormat(command);
15217              },
15218  
15219              // Override commands to use the text formatter engine
15220              'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
15221                  toggleFormat(command, value);
15222              },
15223  
15224              FontSize : function(command, ui, value) {
15225                  var fontClasses, fontSizes;
15226  
15227                  // Convert font size 1-7 to styles
15228                  if (value >= 1 && value <= 7) {
15229                      fontSizes = tinymce.explode(settings.font_size_style_values);
15230                      fontClasses = tinymce.explode(settings.font_size_classes);
15231  
15232                      if (fontClasses)
15233                          value = fontClasses[value - 1] || value;
15234                      else
15235                          value = fontSizes[value - 1] || value;
15236                  }
15237  
15238                  toggleFormat(command, value);
15239              },
15240  
15241              RemoveFormat : function(command) {
15242                  formatter.remove(command);
15243              },
15244  
15245              mceBlockQuote : function(command) {
15246                  toggleFormat('blockquote');
15247              },
15248  
15249              FormatBlock : function(command, ui, value) {
15250                  return toggleFormat(value || 'p');
15251              },
15252  
15253              mceCleanup : function() {
15254                  var bookmark = selection.getBookmark();
15255  
15256                  editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
15257  
15258                  selection.moveToBookmark(bookmark);
15259              },
15260  
15261              mceRemoveNode : function(command, ui, value) {
15262                  var node = value || selection.getNode();
15263  
15264                  // Make sure that the body node isn't removed
15265                  if (node != editor.getBody()) {
15266                      storeSelection();
15267                      editor.dom.remove(node, TRUE);
15268                      restoreSelection();
15269                  }
15270              },
15271  
15272              mceSelectNodeDepth : function(command, ui, value) {
15273                  var counter = 0;
15274  
15275                  dom.getParent(selection.getNode(), function(node) {
15276                      if (node.nodeType == 1 && counter++ == value) {
15277                          selection.select(node);
15278                          return FALSE;
15279                      }
15280                  }, editor.getBody());
15281              },
15282  
15283              mceSelectNode : function(command, ui, value) {
15284                  selection.select(value);
15285              },
15286  
15287              mceInsertContent : function(command, ui, value) {
15288                  var parser, serializer, parentNode, rootNode, fragment, args,
15289                      marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
15290  
15291                  //selection.normalize();
15292  
15293                  // Setup parser and serializer
15294                  parser = editor.parser;
15295                  serializer = new tinymce.html.Serializer({}, editor.schema);
15296                  bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
15297  
15298                  // Run beforeSetContent handlers on the HTML to be inserted
15299                  args = {content: value, format: 'html'};
15300                  selection.onBeforeSetContent.dispatch(selection, args);
15301                  value = args.content;
15302  
15303                  // Add caret at end of contents if it's missing
15304                  if (value.indexOf('{$caret}') == -1)
15305                      value += '{$caret}';
15306  
15307                  // Replace the caret marker with a span bookmark element
15308                  value = value.replace(/\{\$caret\}/, bookmarkHtml);
15309  
15310                  // Insert node maker where we will insert the new HTML and get it's parent
15311                  if (!selection.isCollapsed())
15312                      editor.getDoc().execCommand('Delete', false, null);
15313  
15314                  parentNode = selection.getNode();
15315  
15316                  // Parse the fragment within the context of the parent node
15317                  args = {context : parentNode.nodeName.toLowerCase()};
15318                  fragment = parser.parse(value, args);
15319  
15320                  // Move the caret to a more suitable location
15321                  node = fragment.lastChild;
15322                  if (node.attr('id') == 'mce_marker') {
15323                      marker = node;
15324  
15325                      for (node = node.prev; node; node = node.walk(true)) {
15326                          if (node.type == 3 || !dom.isBlock(node.name)) {
15327                              node.parent.insert(marker, node, node.name === 'br');
15328                              break;
15329                          }
15330                      }
15331                  }
15332  
15333                  // If parser says valid we can insert the contents into that parent
15334                  if (!args.invalid) {
15335                      value = serializer.serialize(fragment);
15336  
15337                      // Check if parent is empty or only has one BR element then set the innerHTML of that parent
15338                      node = parentNode.firstChild;
15339                      node2 = parentNode.lastChild;
15340                      if (!node || (node === node2 && node.nodeName === 'BR'))
15341                          dom.setHTML(parentNode, value);
15342                      else
15343                          selection.setContent(value);
15344                  } else {
15345                      // If the fragment was invalid within that context then we need
15346                      // to parse and process the parent it's inserted into
15347  
15348                      // Insert bookmark node and get the parent
15349                      selection.setContent(bookmarkHtml);
15350                      parentNode = selection.getNode();
15351                      rootNode = editor.getBody();
15352  
15353                      // Opera will return the document node when selection is in root
15354                      if (parentNode.nodeType == 9)
15355                          parentNode = node = rootNode;
15356                      else
15357                          node = parentNode;
15358  
15359                      // Find the ancestor just before the root element
15360                      while (node !== rootNode) {
15361                          parentNode = node;
15362                          node = node.parentNode;
15363                      }
15364  
15365                      // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15366                      value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15367                      value = serializer.serialize(
15368                          parser.parse(
15369                              // Need to replace by using a function since $ in the contents would otherwise be a problem
15370                              value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15371                                  return serializer.serialize(fragment);
15372                              })
15373                          )
15374                      );
15375  
15376                      // Set the inner/outer HTML depending on if we are in the root or not
15377                      if (parentNode == rootNode)
15378                          dom.setHTML(rootNode, value);
15379                      else
15380                          dom.setOuterHTML(parentNode, value);
15381                  }
15382  
15383                  marker = dom.get('mce_marker');
15384  
15385                  // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15386                  nodeRect = dom.getRect(marker);
15387                  viewPortRect = dom.getViewPort(editor.getWin());
15388  
15389                  // Check if node is out side the viewport if it is then scroll to it
15390                  if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15391                      (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15392                      viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15393                      viewportBodyElement.scrollLeft = nodeRect.x;
15394                      viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15395                  }
15396  
15397                  // Move selection before marker and remove it
15398                  rng = dom.createRng();
15399  
15400                  // If previous sibling is a text node set the selection to the end of that node
15401                  node = marker.previousSibling;
15402                  if (node && node.nodeType == 3) {
15403                      rng.setStart(node, node.nodeValue.length);
15404                  } else {
15405                      // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15406                      rng.setStartBefore(marker);
15407                      rng.setEndBefore(marker);
15408                  }
15409  
15410                  // Remove the marker node and set the new range
15411                  dom.remove(marker);
15412                  selection.setRng(rng);
15413  
15414                  // Dispatch after event and add any visual elements needed
15415                  selection.onSetContent.dispatch(selection, args);
15416                  editor.addVisual();
15417              },
15418  
15419              mceInsertRawHTML : function(command, ui, value) {
15420                  selection.setContent('tiny_mce_marker');
15421                  editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15422              },
15423  
15424              mceToggleFormat : function(command, ui, value) {
15425                  toggleFormat(value);
15426              },
15427  
15428              mceSetContent : function(command, ui, value) {
15429                  editor.setContent(value);
15430              },
15431  
15432              'Indent,Outdent' : function(command) {
15433                  var intentValue, indentUnit, value;
15434  
15435                  // Setup indent level
15436                  intentValue = settings.indentation;
15437                  indentUnit = /[a-z%]+$/i.exec(intentValue);
15438                  intentValue = parseInt(intentValue);
15439  
15440                  if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15441                      // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15442                      if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15443                          formatter.apply('div');
15444                      }
15445  
15446                      each(selection.getSelectedBlocks(), function(element) {
15447                          if (command == 'outdent') {
15448                              value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15449                              dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15450                          } else
15451                              dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15452                      });
15453                  } else
15454                      execNativeCommand(command);
15455              },
15456  
15457              mceRepaint : function() {
15458                  var bookmark;
15459  
15460                  if (tinymce.isGecko) {
15461                      try {
15462                          storeSelection(TRUE);
15463  
15464                          if (selection.getSel())
15465                              selection.getSel().selectAllChildren(editor.getBody());
15466  
15467                          selection.collapse(TRUE);
15468                          restoreSelection();
15469                      } catch (ex) {
15470                          // Ignore
15471                      }
15472                  }
15473              },
15474  
15475              mceToggleFormat : function(command, ui, value) {
15476                  formatter.toggle(value);
15477              },
15478  
15479              InsertHorizontalRule : function() {
15480                  editor.execCommand('mceInsertContent', false, '<hr />');
15481              },
15482  
15483              mceToggleVisualAid : function() {
15484                  editor.hasVisual = !editor.hasVisual;
15485                  editor.addVisual();
15486              },
15487  
15488              mceReplaceContent : function(command, ui, value) {
15489                  editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15490              },
15491  
15492              mceInsertLink : function(command, ui, value) {
15493                  var anchor;
15494  
15495                  if (typeof(value) == 'string')
15496                      value = {href : value};
15497  
15498                  anchor = dom.getParent(selection.getNode(), 'a');
15499  
15500                  // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15501                  value.href = value.href.replace(' ', '%20');
15502  
15503                  // Remove existing links if there could be child links or that the href isn't specified
15504                  if (!anchor || !value.href) {
15505                      formatter.remove('link');
15506                  }        
15507  
15508                  // Apply new link to selection
15509                  if (value.href) {
15510                      formatter.apply('link', value, anchor);
15511                  }
15512              },
15513  
15514              selectAll : function() {
15515                  var root = dom.getRoot(), rng = dom.createRng();
15516  
15517                  // Old IE does a better job with selectall than new versions
15518                  if (selection.getRng().setStart) {
15519                      rng.setStart(root, 0);
15520                      rng.setEnd(root, root.childNodes.length);
15521  
15522                      selection.setRng(rng);
15523                  } else {
15524                      execNativeCommand('SelectAll');
15525                  }
15526              }
15527          });
15528  
15529          // Add queryCommandState overrides
15530          addCommands({
15531              // Override justify commands
15532              'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15533                  var name = 'align' + command.substring(7);
15534                  var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15535                  var matches = tinymce.map(nodes, function(node) {
15536                      return !!formatter.matchNode(node, name);
15537                  });
15538                  return tinymce.inArray(matches, TRUE) !== -1;
15539              },
15540  
15541              'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15542                  return isFormatMatch(command);
15543              },
15544  
15545              mceBlockQuote : function() {
15546                  return isFormatMatch('blockquote');
15547              },
15548  
15549              Outdent : function() {
15550                  var node;
15551  
15552                  if (settings.inline_styles) {
15553                      if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15554                          return TRUE;
15555  
15556                      if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15557                          return TRUE;
15558                  }
15559  
15560                  return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15561              },
15562  
15563              'InsertUnorderedList,InsertOrderedList' : function(command) {
15564                  var list = dom.getParent(selection.getNode(), 'ul,ol');
15565                  return list && 
15566                       (command === 'insertunorderedlist' && list.tagName === 'UL'
15567                     || command === 'insertorderedlist' && list.tagName === 'OL');
15568              }
15569          }, 'state');
15570  
15571          // Add queryCommandValue overrides
15572          addCommands({
15573              'FontSize,FontName' : function(command) {
15574                  var value = 0, parent;
15575  
15576                  if (parent = dom.getParent(selection.getNode(), 'span')) {
15577                      if (command == 'fontsize')
15578                          value = parent.style.fontSize;
15579                      else
15580                          value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15581                  }
15582  
15583                  return value;
15584              }
15585          }, 'value');
15586  
15587          // Add undo manager logic
15588          addCommands({
15589              Undo : function() {
15590                  editor.undoManager.undo();
15591              },
15592  
15593              Redo : function() {
15594                  editor.undoManager.redo();
15595              }
15596          });
15597      };
15598  })(tinymce);
15599  (function(tinymce) {
15600      var Dispatcher = tinymce.util.Dispatcher;
15601  
15602      tinymce.UndoManager = function(editor) {
15603          var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15604  
15605  		function getContent() {
15606              // Remove whitespace before/after and remove pure bogus nodes
15607              return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15608          };
15609  
15610  		function addNonTypingUndoLevel() {
15611              self.typing = false;
15612              self.add();
15613          };
15614  
15615          // Create event instances
15616          onBeforeAdd = new Dispatcher(self);
15617          onAdd       = new Dispatcher(self);
15618          onUndo      = new Dispatcher(self);
15619          onRedo      = new Dispatcher(self);
15620  
15621          // Pass though onAdd event from UndoManager to Editor as onChange
15622          onAdd.add(function(undoman, level) {
15623              if (undoman.hasUndo())
15624                  return editor.onChange.dispatch(editor, level, undoman);
15625          });
15626  
15627          // Pass though onUndo event from UndoManager to Editor
15628          onUndo.add(function(undoman, level) {
15629              return editor.onUndo.dispatch(editor, level, undoman);
15630          });
15631  
15632          // Pass though onRedo event from UndoManager to Editor
15633          onRedo.add(function(undoman, level) {
15634              return editor.onRedo.dispatch(editor, level, undoman);
15635          });
15636  
15637          // Add initial undo level when the editor is initialized
15638          editor.onInit.add(function() {
15639              self.add();
15640          });
15641  
15642          // Get position before an execCommand is processed
15643          editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15644              if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15645                  self.beforeChange();
15646              }
15647          });
15648  
15649          // Add undo level after an execCommand call was made
15650          editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15651              if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15652                  self.add();
15653              }
15654          });
15655  
15656          // Add undo level on save contents, drag end and blur/focusout
15657          editor.onSaveContent.add(addNonTypingUndoLevel);
15658          editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15659          editor.dom.bind(editor.getBody(), 'focusout', function(e) {
15660              if (!editor.removed && self.typing) {
15661                  addNonTypingUndoLevel();
15662              }
15663          });
15664  
15665          editor.onKeyUp.add(function(editor, e) {
15666              var keyCode = e.keyCode;
15667  
15668              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15669                  addNonTypingUndoLevel();
15670              }
15671          });
15672  
15673          editor.onKeyDown.add(function(editor, e) {
15674              var keyCode = e.keyCode;
15675  
15676              // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15677              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15678                  if (self.typing) {
15679                      addNonTypingUndoLevel();
15680                  }
15681  
15682                  return;
15683              }
15684  
15685              // If key isn't shift,ctrl,alt,capslock,metakey
15686              if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15687                  self.beforeChange();
15688                  self.typing = true;
15689                  self.add();
15690              }
15691          });
15692  
15693          editor.onMouseDown.add(function(editor, e) {
15694              if (self.typing) {
15695                  addNonTypingUndoLevel();
15696              }
15697          });
15698  
15699          // Add keyboard shortcuts for undo/redo keys
15700          editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15701          editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15702  
15703          self = {
15704              // Explose for debugging reasons
15705              data : data,
15706  
15707              typing : false,
15708              
15709              onBeforeAdd: onBeforeAdd,
15710  
15711              onAdd : onAdd,
15712  
15713              onUndo : onUndo,
15714  
15715              onRedo : onRedo,
15716  
15717              beforeChange : function() {
15718                  beforeBookmark = editor.selection.getBookmark(2, true);
15719              },
15720  
15721              add : function(level) {
15722                  var i, settings = editor.settings, lastLevel;
15723  
15724                  level = level || {};
15725                  level.content = getContent();
15726                  
15727                  self.onBeforeAdd.dispatch(self, level);
15728  
15729                  // Add undo level if needed
15730                  lastLevel = data[index];
15731                  if (lastLevel && lastLevel.content == level.content)
15732                      return null;
15733  
15734                  // Set before bookmark on previous level
15735                  if (data[index])
15736                      data[index].beforeBookmark = beforeBookmark;
15737  
15738                  // Time to compress
15739                  if (settings.custom_undo_redo_levels) {
15740                      if (data.length > settings.custom_undo_redo_levels) {
15741                          for (i = 0; i < data.length - 1; i++)
15742                              data[i] = data[i + 1];
15743  
15744                          data.length--;
15745                          index = data.length;
15746                      }
15747                  }
15748  
15749                  // Get a non intrusive normalized bookmark
15750                  level.bookmark = editor.selection.getBookmark(2, true);
15751  
15752                  // Crop array if needed
15753                  if (index < data.length - 1)
15754                      data.length = index + 1;
15755  
15756                  data.push(level);
15757                  index = data.length - 1;
15758  
15759                  self.onAdd.dispatch(self, level);
15760                  editor.isNotDirty = 0;
15761  
15762                  return level;
15763              },
15764  
15765              undo : function() {
15766                  var level, i;
15767  
15768                  if (self.typing) {
15769                      self.add();
15770                      self.typing = false;
15771                  }
15772  
15773                  if (index > 0) {
15774                      level = data[--index];
15775  
15776                      editor.setContent(level.content, {format : 'raw'});
15777                      editor.selection.moveToBookmark(level.beforeBookmark);
15778  
15779                      self.onUndo.dispatch(self, level);
15780                  }
15781  
15782                  return level;
15783              },
15784  
15785              redo : function() {
15786                  var level;
15787  
15788                  if (index < data.length - 1) {
15789                      level = data[++index];
15790  
15791                      editor.setContent(level.content, {format : 'raw'});
15792                      editor.selection.moveToBookmark(level.bookmark);
15793  
15794                      self.onRedo.dispatch(self, level);
15795                  }
15796  
15797                  return level;
15798              },
15799  
15800              clear : function() {
15801                  data = [];
15802                  index = 0;
15803                  self.typing = false;
15804              },
15805  
15806              hasUndo : function() {
15807                  return index > 0 || this.typing;
15808              },
15809  
15810              hasRedo : function() {
15811                  return index < data.length - 1 && !this.typing;
15812              }
15813          };
15814  
15815          return self;
15816      };
15817  })(tinymce);
15818  tinymce.ForceBlocks = function(editor) {
15819      var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15820  
15821  	function addRootBlocks() {
15822          var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15823  
15824          if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15825              return;
15826  
15827          // Check if node is wrapped in block
15828          while (node && node != rootNode) {
15829              if (blockElements[node.nodeName])
15830                  return;
15831  
15832              node = node.parentNode;
15833          }
15834  
15835          // Get current selection
15836          rng = selection.getRng();
15837          if (rng.setStart) {
15838              startContainer = rng.startContainer;
15839              startOffset = rng.startOffset;
15840              endContainer = rng.endContainer;
15841              endOffset = rng.endOffset;
15842          } else {
15843              // Force control range into text range
15844              if (rng.item) {
15845                  node = rng.item(0);
15846                  rng = editor.getDoc().body.createTextRange();
15847                  rng.moveToElementText(node);
15848              }
15849  
15850              isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15851              tmpRng = rng.duplicate();
15852              tmpRng.collapse(true);
15853              startOffset = tmpRng.move('character', offset) * -1;
15854  
15855              if (!tmpRng.collapsed) {
15856                  tmpRng = rng.duplicate();
15857                  tmpRng.collapse(false);
15858                  endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15859              }
15860          }
15861  
15862          // Wrap non block elements and text nodes
15863          node = rootNode.firstChild;
15864          while (node) {
15865              if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15866                  // Remove empty text nodes
15867                  if (node.nodeType === 3 && node.nodeValue.length == 0) {
15868                      tempNode = node;
15869                      node = node.nextSibling;
15870                      dom.remove(tempNode);
15871                      continue;
15872                  }
15873  
15874                  if (!rootBlockNode) {
15875                      rootBlockNode = dom.create(settings.forced_root_block);
15876                      node.parentNode.insertBefore(rootBlockNode, node);
15877                      wrapped = true;
15878                  }
15879  
15880                  tempNode = node;
15881                  node = node.nextSibling;
15882                  rootBlockNode.appendChild(tempNode);
15883              } else {
15884                  rootBlockNode = null;
15885                  node = node.nextSibling;
15886              }
15887          }
15888  
15889          if (wrapped) {
15890              if (rng.setStart) {
15891                  rng.setStart(startContainer, startOffset);
15892                  rng.setEnd(endContainer, endOffset);
15893                  selection.setRng(rng);
15894              } else {
15895                  // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15896                  if (isInEditorDocument) {
15897                      try {
15898                          rng = editor.getDoc().body.createTextRange();
15899                          rng.moveToElementText(rootNode);
15900                          rng.collapse(true);
15901                          rng.moveStart('character', startOffset);
15902  
15903                          if (endOffset > 0)
15904                              rng.moveEnd('character', endOffset);
15905  
15906                          rng.select();
15907                      } catch (ex) {
15908                          // Ignore
15909                      }
15910                  }
15911              }
15912  
15913              editor.nodeChanged();
15914          }
15915      };
15916  
15917      // Force root blocks
15918      if (settings.forced_root_block) {
15919          editor.onKeyUp.add(addRootBlocks);
15920          editor.onNodeChange.add(addRootBlocks);
15921      }
15922  };
15923  (function(tinymce) {
15924      // Shorten names
15925      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15926  
15927      tinymce.create('tinymce.ControlManager', {
15928          ControlManager : function(ed, s) {
15929              var t = this, i;
15930  
15931              s = s || {};
15932              t.editor = ed;
15933              t.controls = {};
15934              t.onAdd = new tinymce.util.Dispatcher(t);
15935              t.onPostRender = new tinymce.util.Dispatcher(t);
15936              t.prefix = s.prefix || ed.id + '_';
15937              t._cls = {};
15938  
15939              t.onPostRender.add(function() {
15940                  each(t.controls, function(c) {
15941                      c.postRender();
15942                  });
15943              });
15944          },
15945  
15946          get : function(id) {
15947              return this.controls[this.prefix + id] || this.controls[id];
15948          },
15949  
15950          setActive : function(id, s) {
15951              var c = null;
15952  
15953              if (c = this.get(id))
15954                  c.setActive(s);
15955  
15956              return c;
15957          },
15958  
15959          setDisabled : function(id, s) {
15960              var c = null;
15961  
15962              if (c = this.get(id))
15963                  c.setDisabled(s);
15964  
15965              return c;
15966          },
15967  
15968          add : function(c) {
15969              var t = this;
15970  
15971              if (c) {
15972                  t.controls[c.id] = c;
15973                  t.onAdd.dispatch(c, t);
15974              }
15975  
15976              return c;
15977          },
15978  
15979          createControl : function(name) {
15980              var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
15981  
15982              // Build control factory cache
15983              if (!self.controlFactories) {
15984                  self.controlFactories = [];
15985                  each(editor.plugins, function(plugin) {
15986                      if (plugin.createControl) {
15987                          self.controlFactories.push(plugin);
15988                      }
15989                  });
15990              }
15991  
15992              // Create controls by asking cached factories
15993              factories = self.controlFactories;
15994              for (i = 0, l = factories.length; i < l; i++) {
15995                  ctrl = factories[i].createControl(name, self);
15996  
15997                  if (ctrl) {
15998                      return self.add(ctrl);
15999                  }
16000              }
16001  
16002              // Create sepearator
16003              if (name === "|" || name === "separator") {
16004                  return self.createSeparator();
16005              }
16006  
16007              // Create control from button collection
16008              if (editor.buttons && (ctrl = editor.buttons[name])) {
16009                  return self.createButton(name, ctrl);
16010              }
16011  
16012              return self.add(ctrl);
16013          },
16014  
16015          createDropMenu : function(id, s, cc) {
16016              var t = this, ed = t.editor, c, bm, v, cls;
16017  
16018              s = extend({
16019                  'class' : 'mceDropDown',
16020                  constrain : ed.settings.constrain_menus
16021              }, s);
16022  
16023              s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
16024              if (v = ed.getParam('skin_variant'))
16025                  s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
16026  
16027              s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
16028  
16029              id = t.prefix + id;
16030              cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
16031              c = t.controls[id] = new cls(id, s);
16032              c.onAddItem.add(function(c, o) {
16033                  var s = o.settings;
16034  
16035                  s.title = ed.getLang(s.title, s.title);
16036  
16037                  if (!s.onclick) {
16038                      s.onclick = function(v) {
16039                          if (s.cmd)
16040                              ed.execCommand(s.cmd, s.ui || false, s.value);
16041                      };
16042                  }
16043              });
16044  
16045              ed.onRemove.add(function() {
16046                  c.destroy();
16047              });
16048  
16049              // Fix for bug #1897785, #1898007
16050              if (tinymce.isIE) {
16051                  c.onShowMenu.add(function() {
16052                      // IE 8 needs focus in order to store away a range with the current collapsed caret location
16053                      ed.focus();
16054  
16055                      bm = ed.selection.getBookmark(1);
16056                  });
16057  
16058                  c.onHideMenu.add(function() {
16059                      if (bm) {
16060                          ed.selection.moveToBookmark(bm);
16061                          bm = 0;
16062                      }
16063                  });
16064              }
16065  
16066              return t.add(c);
16067          },
16068  
16069          createListBox : function(id, s, cc) {
16070              var t = this, ed = t.editor, cmd, c, cls;
16071  
16072              if (t.get(id))
16073                  return null;
16074  
16075              s.title = ed.translate(s.title);
16076              s.scope = s.scope || ed;
16077  
16078              if (!s.onselect) {
16079                  s.onselect = function(v) {
16080                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16081                  };
16082              }
16083  
16084              s = extend({
16085                  title : s.title,
16086                  'class' : 'mce_' + id,
16087                  scope : s.scope,
16088                  control_manager : t
16089              }, s);
16090  
16091              id = t.prefix + id;
16092  
16093  
16094  			function useNativeListForAccessibility(ed) {
16095                  return ed.settings.use_accessible_selects && !tinymce.isGecko
16096              }
16097  
16098              if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
16099                  c = new tinymce.ui.NativeListBox(id, s);
16100              else {
16101                  cls = cc || t._cls.listbox || tinymce.ui.ListBox;
16102                  c = new cls(id, s, ed);
16103              }
16104  
16105              t.controls[id] = c;
16106  
16107              // Fix focus problem in Safari
16108              if (tinymce.isWebKit) {
16109                  c.onPostRender.add(function(c, n) {
16110                      // Store bookmark on mousedown
16111                      Event.add(n, 'mousedown', function() {
16112                          ed.bookmark = ed.selection.getBookmark(1);
16113                      });
16114  
16115                      // Restore on focus, since it might be lost
16116                      Event.add(n, 'focus', function() {
16117                          ed.selection.moveToBookmark(ed.bookmark);
16118                          ed.bookmark = null;
16119                      });
16120                  });
16121              }
16122  
16123              if (c.hideMenu)
16124                  ed.onMouseDown.add(c.hideMenu, c);
16125  
16126              return t.add(c);
16127          },
16128  
16129          createButton : function(id, s, cc) {
16130              var t = this, ed = t.editor, o, c, cls;
16131  
16132              if (t.get(id))
16133                  return null;
16134  
16135              s.title = ed.translate(s.title);
16136              s.label = ed.translate(s.label);
16137              s.scope = s.scope || ed;
16138  
16139              if (!s.onclick && !s.menu_button) {
16140                  s.onclick = function() {
16141                      ed.execCommand(s.cmd, s.ui || false, s.value);
16142                  };
16143              }
16144  
16145              s = extend({
16146                  title : s.title,
16147                  'class' : 'mce_' + id,
16148                  unavailable_prefix : ed.getLang('unavailable', ''),
16149                  scope : s.scope,
16150                  control_manager : t
16151              }, s);
16152  
16153              id = t.prefix + id;
16154  
16155              if (s.menu_button) {
16156                  cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
16157                  c = new cls(id, s, ed);
16158                  ed.onMouseDown.add(c.hideMenu, c);
16159              } else {
16160                  cls = t._cls.button || tinymce.ui.Button;
16161                  c = new cls(id, s, ed);
16162              }
16163  
16164              return t.add(c);
16165          },
16166  
16167          createMenuButton : function(id, s, cc) {
16168              s = s || {};
16169              s.menu_button = 1;
16170  
16171              return this.createButton(id, s, cc);
16172          },
16173  
16174          createSplitButton : function(id, s, cc) {
16175              var t = this, ed = t.editor, cmd, c, cls;
16176  
16177              if (t.get(id))
16178                  return null;
16179  
16180              s.title = ed.translate(s.title);
16181              s.scope = s.scope || ed;
16182  
16183              if (!s.onclick) {
16184                  s.onclick = function(v) {
16185                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16186                  };
16187              }
16188  
16189              if (!s.onselect) {
16190                  s.onselect = function(v) {
16191                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16192                  };
16193              }
16194  
16195              s = extend({
16196                  title : s.title,
16197                  'class' : 'mce_' + id,
16198                  scope : s.scope,
16199                  control_manager : t
16200              }, s);
16201  
16202              id = t.prefix + id;
16203              cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
16204              c = t.add(new cls(id, s, ed));
16205              ed.onMouseDown.add(c.hideMenu, c);
16206  
16207              return c;
16208          },
16209  
16210          createColorSplitButton : function(id, s, cc) {
16211              var t = this, ed = t.editor, cmd, c, cls, bm;
16212  
16213              if (t.get(id))
16214                  return null;
16215  
16216              s.title = ed.translate(s.title);
16217              s.scope = s.scope || ed;
16218  
16219              if (!s.onclick) {
16220                  s.onclick = function(v) {
16221                      if (tinymce.isIE)
16222                          bm = ed.selection.getBookmark(1);
16223  
16224                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16225                  };
16226              }
16227  
16228              if (!s.onselect) {
16229                  s.onselect = function(v) {
16230                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16231                  };
16232              }
16233  
16234              s = extend({
16235                  title : s.title,
16236                  'class' : 'mce_' + id,
16237                  'menu_class' : ed.getParam('skin') + 'Skin',
16238                  scope : s.scope,
16239                  more_colors_title : ed.getLang('more_colors')
16240              }, s);
16241  
16242              id = t.prefix + id;
16243              cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
16244              c = new cls(id, s, ed);
16245              ed.onMouseDown.add(c.hideMenu, c);
16246  
16247              // Remove the menu element when the editor is removed
16248              ed.onRemove.add(function() {
16249                  c.destroy();
16250              });
16251  
16252              // Fix for bug #1897785, #1898007
16253              if (tinymce.isIE) {
16254                  c.onShowMenu.add(function() {
16255                      // IE 8 needs focus in order to store away a range with the current collapsed caret location
16256                      ed.focus();
16257                      bm = ed.selection.getBookmark(1);
16258                  });
16259  
16260                  c.onHideMenu.add(function() {
16261                      if (bm) {
16262                          ed.selection.moveToBookmark(bm);
16263                          bm = 0;
16264                      }
16265                  });
16266              }
16267  
16268              return t.add(c);
16269          },
16270  
16271          createToolbar : function(id, s, cc) {
16272              var c, t = this, cls;
16273  
16274              id = t.prefix + id;
16275              cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
16276              c = new cls(id, s, t.editor);
16277  
16278              if (t.get(id))
16279                  return null;
16280  
16281              return t.add(c);
16282          },
16283          
16284          createToolbarGroup : function(id, s, cc) {
16285              var c, t = this, cls;
16286              id = t.prefix + id;
16287              cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
16288              c = new cls(id, s, t.editor);
16289              
16290              if (t.get(id))
16291                  return null;
16292              
16293              return t.add(c);
16294          },
16295  
16296          createSeparator : function(cc) {
16297              var cls = cc || this._cls.separator || tinymce.ui.Separator;
16298  
16299              return new cls();
16300          },
16301  
16302          setControlType : function(n, c) {
16303              return this._cls[n.toLowerCase()] = c;
16304          },
16305      
16306          destroy : function() {
16307              each(this.controls, function(c) {
16308                  c.destroy();
16309              });
16310  
16311              this.controls = null;
16312          }
16313      });
16314  })(tinymce);
16315  (function(tinymce) {
16316      var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
16317  
16318      tinymce.create('tinymce.WindowManager', {
16319          WindowManager : function(ed) {
16320              var t = this;
16321  
16322              t.editor = ed;
16323              t.onOpen = new Dispatcher(t);
16324              t.onClose = new Dispatcher(t);
16325              t.params = {};
16326              t.features = {};
16327          },
16328  
16329          open : function(s, p) {
16330              var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
16331  
16332              // Default some options
16333              s = s || {};
16334              p = p || {};
16335              sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
16336              sh = isOpera ? vp.h : screen.height;
16337              s.name = s.name || 'mc_' + new Date().getTime();
16338              s.width = parseInt(s.width || 320);
16339              s.height = parseInt(s.height || 240);
16340              s.resizable = true;
16341              s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
16342              s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
16343              p.inline = false;
16344              p.mce_width = s.width;
16345              p.mce_height = s.height;
16346              p.mce_auto_focus = s.auto_focus;
16347  
16348              if (mo) {
16349                  if (isIE) {
16350                      s.center = true;
16351                      s.help = false;
16352                      s.dialogWidth = s.width + 'px';
16353                      s.dialogHeight = s.height + 'px';
16354                      s.scroll = s.scrollbars || false;
16355                  }
16356              }
16357  
16358              // Build features string
16359              each(s, function(v, k) {
16360                  if (tinymce.is(v, 'boolean'))
16361                      v = v ? 'yes' : 'no';
16362  
16363                  if (!/^(name|url)$/.test(k)) {
16364                      if (isIE && mo)
16365                          f += (f ? ';' : '') + k + ':' + v;
16366                      else
16367                          f += (f ? ',' : '') + k + '=' + v;
16368                  }
16369              });
16370  
16371              t.features = s;
16372              t.params = p;
16373              t.onOpen.dispatch(t, s, p);
16374  
16375              u = s.url || s.file;
16376              u = tinymce._addVer(u);
16377  
16378              try {
16379                  if (isIE && mo) {
16380                      w = 1;
16381                      window.showModalDialog(u, window, f);
16382                  } else
16383                      w = window.open(u, s.name, f);
16384              } catch (ex) {
16385                  // Ignore
16386              }
16387  
16388              if (!w)
16389                  alert(t.editor.getLang('popup_blocked'));
16390          },
16391  
16392          close : function(w) {
16393              w.close();
16394              this.onClose.dispatch(this);
16395          },
16396  
16397          createInstance : function(cl, a, b, c, d, e) {
16398              var f = tinymce.resolve(cl);
16399  
16400              return new f(a, b, c, d, e);
16401          },
16402  
16403          confirm : function(t, cb, s, w) {
16404              w = w || window;
16405  
16406              cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16407          },
16408  
16409          alert : function(tx, cb, s, w) {
16410              var t = this;
16411  
16412              w = w || window;
16413              w.alert(t._decode(t.editor.getLang(tx, tx)));
16414  
16415              if (cb)
16416                  cb.call(s || t);
16417          },
16418  
16419          resizeBy : function(dw, dh, win) {
16420              win.resizeBy(dw, dh);
16421          },
16422  
16423          // Internal functions
16424  
16425          _decode : function(s) {
16426              return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16427          }
16428      });
16429  }(tinymce));
16430  (function(tinymce) {
16431      tinymce.Formatter = function(ed) {
16432          var formats = {},
16433              each = tinymce.each,
16434              dom = ed.dom,
16435              selection = ed.selection,
16436              TreeWalker = tinymce.dom.TreeWalker,
16437              rangeUtils = new tinymce.dom.RangeUtils(dom),
16438              isValid = ed.schema.isValidChild,
16439              isArray = tinymce.isArray,
16440              isBlock = dom.isBlock,
16441              forcedRootBlock = ed.settings.forced_root_block,
16442              nodeIndex = dom.nodeIndex,
16443              INVISIBLE_CHAR = '\uFEFF',
16444              MCE_ATTR_RE = /^(src|href|style)$/,
16445              FALSE = false,
16446              TRUE = true,
16447              formatChangeData,
16448              undef,
16449              getContentEditable = dom.getContentEditable;
16450  
16451  		function isTextBlock(name) {
16452              if (name.nodeType) {
16453                  name = name.nodeName;
16454              }
16455  
16456              return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
16457          }
16458  
16459  		function getParents(node, selector) {
16460              return dom.getParents(node, selector, dom.getRoot());
16461          };
16462  
16463  		function isCaretNode(node) {
16464              return node.nodeType === 1 && node.id === '_mce_caret';
16465          };
16466  
16467  		function defaultFormats() {
16468              register({
16469                  alignleft : [
16470                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16471                      {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16472                  ],
16473  
16474                  aligncenter : [
16475                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16476                      {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16477                      {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16478                  ],
16479  
16480                  alignright : [
16481                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16482                      {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16483                  ],
16484  
16485                  alignfull : [
16486                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16487                  ],
16488  
16489                  bold : [
16490                      {inline : 'strong', remove : 'all'},
16491                      {inline : 'span', styles : {fontWeight : 'bold'}},
16492                      {inline : 'b', remove : 'all'}
16493                  ],
16494  
16495                  italic : [
16496                      {inline : 'em', remove : 'all'},
16497                      {inline : 'span', styles : {fontStyle : 'italic'}},
16498                      {inline : 'i', remove : 'all'}
16499                  ],
16500  
16501                  underline : [
16502                      {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16503                      {inline : 'u', remove : 'all'}
16504                  ],
16505  
16506                  strikethrough : [
16507                      {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16508                      {inline : 'strike', remove : 'all'}
16509                  ],
16510  
16511                  forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16512                  hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16513                  fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16514                  fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16515                  fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16516                  blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16517                  subscript : {inline : 'sub'},
16518                  superscript : {inline : 'sup'},
16519  
16520                  link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16521                      onmatch : function(node) {
16522                          return true;
16523                      },
16524  
16525                      onformat : function(elm, fmt, vars) {
16526                          each(vars, function(value, key) {
16527                              dom.setAttrib(elm, key, value);
16528                          });
16529                      }
16530                  },
16531  
16532                  removeformat : [
16533                      {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16534                      {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16535                      {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16536                  ]
16537              });
16538  
16539              // Register default block formats
16540              each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16541                  register(name, {block : name, remove : 'all'});
16542              });
16543  
16544              // Register user defined formats
16545              register(ed.settings.formats);
16546          };
16547  
16548  		function addKeyboardShortcuts() {
16549              // Add some inline shortcuts
16550              ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16551              ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16552              ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16553  
16554              // BlockFormat shortcuts keys
16555              for (var i = 1; i <= 6; i++) {
16556                  ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16557              }
16558  
16559              ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16560              ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16561              ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16562          };
16563  
16564          // Public functions
16565  
16566  		function get(name) {
16567              return name ? formats[name] : formats;
16568          };
16569  
16570  		function register(name, format) {
16571              if (name) {
16572                  if (typeof(name) !== 'string') {
16573                      each(name, function(format, name) {
16574                          register(name, format);
16575                      });
16576                  } else {
16577                      // Force format into array and add it to internal collection
16578                      format = format.length ? format : [format];
16579  
16580                      each(format, function(format) {
16581                          // Set deep to false by default on selector formats this to avoid removing
16582                          // alignment on images inside paragraphs when alignment is changed on paragraphs
16583                          if (format.deep === undef)
16584                              format.deep = !format.selector;
16585  
16586                          // Default to true
16587                          if (format.split === undef)
16588                              format.split = !format.selector || format.inline;
16589  
16590                          // Default to true
16591                          if (format.remove === undef && format.selector && !format.inline)
16592                              format.remove = 'none';
16593  
16594                          // Mark format as a mixed format inline + block level
16595                          if (format.selector && format.inline) {
16596                              format.mixed = true;
16597                              format.block_expand = true;
16598                          }
16599  
16600                          // Split classes if needed
16601                          if (typeof(format.classes) === 'string')
16602                              format.classes = format.classes.split(/\s+/);
16603                      });
16604  
16605                      formats[name] = format;
16606                  }
16607              }
16608          };
16609  
16610          var getTextDecoration = function(node) {
16611              var decoration;
16612  
16613              ed.dom.getParent(node, function(n) {
16614                  decoration = ed.dom.getStyle(n, 'text-decoration');
16615                  return decoration && decoration !== 'none';
16616              });
16617  
16618              return decoration;
16619          };
16620  
16621          var processUnderlineAndColor = function(node) {
16622              var textDecoration;
16623              if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16624                  textDecoration = getTextDecoration(node.parentNode);
16625                  if (ed.dom.getStyle(node, 'color') && textDecoration) {
16626                      ed.dom.setStyle(node, 'text-decoration', textDecoration);
16627                  } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16628                      ed.dom.setStyle(node, 'text-decoration', null);
16629                  }
16630              }
16631          };
16632  
16633  		function apply(name, vars, node) {
16634              var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16635  
16636  			function setElementFormat(elm, fmt) {
16637                  fmt = fmt || format;
16638  
16639                  if (elm) {
16640                      if (fmt.onformat) {
16641                          fmt.onformat(elm, fmt, vars, node);
16642                      }
16643  
16644                      each(fmt.styles, function(value, name) {
16645                          dom.setStyle(elm, name, replaceVars(value, vars));
16646                      });
16647  
16648                      each(fmt.attributes, function(value, name) {
16649                          dom.setAttrib(elm, name, replaceVars(value, vars));
16650                      });
16651  
16652                      each(fmt.classes, function(value) {
16653                          value = replaceVars(value, vars);
16654  
16655                          if (!dom.hasClass(elm, value))
16656                              dom.addClass(elm, value);
16657                      });
16658                  }
16659              };
16660  			function adjustSelectionToVisibleSelection() {
16661  				function findSelectionEnd(start, end) {
16662                      var walker = new TreeWalker(end);
16663                      for (node = walker.current(); node; node = walker.prev()) {
16664                          if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16665                              return node;
16666                          }
16667                      }
16668                  };
16669  
16670                  // Adjust selection so that a end container with a end offset of zero is not included in the selection
16671                  // as this isn't visible to the user.
16672                  var rng = ed.selection.getRng();
16673                  var start = rng.startContainer;
16674                  var end = rng.endContainer;
16675  
16676                  if (start != end && rng.endOffset === 0) {
16677                      var newEnd = findSelectionEnd(start, end);
16678                      var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16679  
16680                      rng.setEnd(newEnd, endOffset);
16681                  }
16682  
16683                  return rng;
16684              }
16685              
16686  			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16687                  var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16688                  
16689                  // find the index of the first child list.
16690                  each(node.childNodes, function(n, index) {
16691                      if (n.nodeName === "UL" || n.nodeName === "OL") {
16692                          listIndex = index;
16693                          list = n;
16694                          return false;
16695                      }
16696                  });
16697                  
16698                  // get the index of the bookmarks
16699                  each(node.childNodes, function(n, index) {
16700                      if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16701                          if (n.id == bookmark.id + "_start") {
16702                              startIndex = index;
16703                          } else if (n.id == bookmark.id + "_end") {
16704                              endIndex = index;
16705                          }
16706                      }
16707                  });
16708                  
16709                  // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16710                  if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16711                      each(tinymce.grep(node.childNodes), process);
16712                      return 0;
16713                  } else {
16714                      currentWrapElm = dom.clone(wrapElm, FALSE);
16715  
16716                      // create a list of the nodes on the same side of the list as the selection
16717                      each(tinymce.grep(node.childNodes), function(n, index) {
16718                          if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16719                              nodes.push(n); 
16720                              n.parentNode.removeChild(n);
16721                          }
16722                      });
16723  
16724                      // insert the wrapping element either before or after the list.
16725                      if (startIndex < listIndex) {
16726                          node.insertBefore(currentWrapElm, list);
16727                      } else if (startIndex > listIndex) {
16728                          node.insertBefore(currentWrapElm, list.nextSibling);
16729                      }
16730                      
16731                      // add the new nodes to the list.
16732                      newWrappers.push(currentWrapElm);
16733  
16734                      each(nodes, function(node) {
16735                          currentWrapElm.appendChild(node);
16736                      });
16737  
16738                      return currentWrapElm;
16739                  }
16740              };
16741  
16742  			function applyRngStyle(rng, bookmark, node_specific) {
16743                  var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16744  
16745                  // Setup wrapper element
16746                  wrapName = format.inline || format.block;
16747                  wrapElm = dom.create(wrapName);
16748                  setElementFormat(wrapElm);
16749  
16750                  rangeUtils.walk(rng, function(nodes) {
16751                      var currentWrapElm;
16752  
16753  					function process(node) {
16754                          var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16755  
16756                          lastContentEditable = contentEditable;
16757                          nodeName = node.nodeName.toLowerCase();
16758                          parentName = node.parentNode.nodeName.toLowerCase();
16759  
16760                          // Node has a contentEditable value
16761                          if (node.nodeType === 1 && getContentEditable(node)) {
16762                              lastContentEditable = contentEditable;
16763                              contentEditable = getContentEditable(node) === "true";
16764                              hasContentEditableState = true; // We don't want to wrap the container only it's children
16765                          }
16766  
16767                          // Stop wrapping on br elements
16768                          if (isEq(nodeName, 'br')) {
16769                              currentWrapElm = 0;
16770  
16771                              // Remove any br elements when we wrap things
16772                              if (format.block)
16773                                  dom.remove(node);
16774  
16775                              return;
16776                          }
16777  
16778                          // If node is wrapper type
16779                          if (format.wrapper && matchNode(node, name, vars)) {
16780                              currentWrapElm = 0;
16781                              return;
16782                          }
16783  
16784                          // Can we rename the block
16785                          if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16786                              node = dom.rename(node, wrapName);
16787                              setElementFormat(node);
16788                              newWrappers.push(node);
16789                              currentWrapElm = 0;
16790                              return;
16791                          }
16792  
16793                          // Handle selector patterns
16794                          if (format.selector) {
16795                              // Look for matching formats
16796                              each(formatList, function(format) {
16797                                  // Check collapsed state if it exists
16798                                  if ('collapsed' in format && format.collapsed !== isCollapsed) {
16799                                      return;
16800                                  }
16801  
16802                                  if (dom.is(node, format.selector) && !isCaretNode(node)) {
16803                                      setElementFormat(node, format);
16804                                      found = true;
16805                                  }
16806                              });
16807  
16808                              // Continue processing if a selector match wasn't found and a inline element is defined
16809                              if (!format.inline || found) {
16810                                  currentWrapElm = 0;
16811                                  return;
16812                              }
16813                          }
16814  
16815                          // Is it valid to wrap this item
16816                          if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16817                                  !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) {
16818                              // Start wrapping
16819                              if (!currentWrapElm) {
16820                                  // Wrap the node
16821                                  currentWrapElm = dom.clone(wrapElm, FALSE);
16822                                  node.parentNode.insertBefore(currentWrapElm, node);
16823                                  newWrappers.push(currentWrapElm);
16824                              }
16825  
16826                              currentWrapElm.appendChild(node);
16827                          } else if (nodeName == 'li' && bookmark) {
16828                              // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16829                              currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16830                          } else {
16831                              // Start a new wrapper for possible children
16832                              currentWrapElm = 0;
16833                              
16834                              each(tinymce.grep(node.childNodes), process);
16835  
16836                              if (hasContentEditableState) {
16837                                  contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16838                              }
16839  
16840                              // End the last wrapper
16841                              currentWrapElm = 0;
16842                          }
16843                      };
16844  
16845                      // Process siblings from range
16846                      each(nodes, process);
16847                  });
16848  
16849                  // Wrap links inside as well, for example color inside a link when the wrapper is around the link
16850                  if (format.wrap_links === false) {
16851                      each(newWrappers, function(node) {
16852  						function process(node) {
16853                              var i, currentWrapElm, children;
16854  
16855                              if (node.nodeName === 'A') {
16856                                  currentWrapElm = dom.clone(wrapElm, FALSE);
16857                                  newWrappers.push(currentWrapElm);
16858  
16859                                  children = tinymce.grep(node.childNodes);
16860                                  for (i = 0; i < children.length; i++)
16861                                      currentWrapElm.appendChild(children[i]);
16862  
16863                                  node.appendChild(currentWrapElm);
16864                              }
16865  
16866                              each(tinymce.grep(node.childNodes), process);
16867                          };
16868  
16869                          process(node);
16870                      });
16871                  }
16872  
16873                  // Cleanup
16874                  
16875                  each(newWrappers, function(node) {
16876                      var childCount;
16877  
16878  					function getChildCount(node) {
16879                          var count = 0;
16880  
16881                          each(node.childNodes, function(node) {
16882                              if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16883                                  count++;
16884                          });
16885  
16886                          return count;
16887                      };
16888  
16889  					function mergeStyles(node) {
16890                          var child, clone;
16891  
16892                          each(node.childNodes, function(node) {
16893                              if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16894                                  child = node;
16895                                  return FALSE; // break loop
16896                              }
16897                          });
16898  
16899                          // If child was found and of the same type as the current node
16900                          if (child && matchName(child, format)) {
16901                              clone = dom.clone(child, FALSE);
16902                              setElementFormat(clone);
16903  
16904                              dom.replace(clone, node, TRUE);
16905                              dom.remove(child, 1);
16906                          }
16907  
16908                          return clone || node;
16909                      };
16910  
16911                      childCount = getChildCount(node);
16912  
16913                      // Remove empty nodes but only if there is multiple wrappers and they are not block
16914                      // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16915                      if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16916                          dom.remove(node, 1);
16917                          return;
16918                      }
16919  
16920                      if (format.inline || format.wrapper) {
16921                          // Merges the current node with it's children of similar type to reduce the number of elements
16922                          if (!format.exact && childCount === 1)
16923                              node = mergeStyles(node);
16924  
16925                          // Remove/merge children
16926                          each(formatList, function(format) {
16927                              // Merge all children of similar type will move styles from child to parent
16928                              // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16929                              // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16930                              each(dom.select(format.inline, node), function(child) {
16931                                  var parent;
16932  
16933                                  // When wrap_links is set to false we don't want
16934                                  // to remove the format on children within links
16935                                  if (format.wrap_links === false) {
16936                                      parent = child.parentNode;
16937  
16938                                      do {
16939                                          if (parent.nodeName === 'A')
16940                                              return;
16941                                      } while (parent = parent.parentNode);
16942                                  }
16943  
16944                                  removeFormat(format, vars, child, format.exact ? child : null);
16945                              });
16946                          });
16947  
16948                          // Remove child if direct parent is of same type
16949                          if (matchNode(node.parentNode, name, vars)) {
16950                              dom.remove(node, 1);
16951                              node = 0;
16952                              return TRUE;
16953                          }
16954  
16955                          // Look for parent with similar style format
16956                          if (format.merge_with_parents) {
16957                              dom.getParent(node.parentNode, function(parent) {
16958                                  if (matchNode(parent, name, vars)) {
16959                                      dom.remove(node, 1);
16960                                      node = 0;
16961                                      return TRUE;
16962                                  }
16963                              });
16964                          }
16965  
16966                          // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16967                          if (node && format.merge_siblings !== false) {
16968                              node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16969                              node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16970                          }
16971                      }
16972                  });
16973              };
16974  
16975              if (format) {
16976                  if (node) {
16977                      if (node.nodeType) {
16978                          rng = dom.createRng();
16979                          rng.setStartBefore(node);
16980                          rng.setEndAfter(node);
16981                          applyRngStyle(expandRng(rng, formatList), null, true);
16982                      } else {
16983                          applyRngStyle(node, null, true);
16984                      }
16985                  } else {
16986                      if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
16987                          // Obtain selection node before selection is unselected by applyRngStyle()
16988                          var curSelNode = ed.selection.getNode();
16989  
16990                          // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
16991                          // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
16992                          if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
16993                              apply(formatList[0].defaultBlock);
16994                          }
16995  
16996                          // Apply formatting to selection
16997                          ed.selection.setRng(adjustSelectionToVisibleSelection());
16998                          bookmark = selection.getBookmark();
16999                          applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
17000  
17001                          // Colored nodes should be underlined so that the color of the underline matches the text color.
17002                          if (format.styles && (format.styles.color || format.styles.textDecoration)) {
17003                              tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
17004                              processUnderlineAndColor(curSelNode);
17005                          }
17006  
17007                          selection.moveToBookmark(bookmark);
17008                          moveStart(selection.getRng(TRUE));
17009                          ed.nodeChanged();
17010                      } else
17011                          performCaretAction('apply', name, vars);
17012                  }
17013              }
17014          };
17015  
17016  		function remove(name, vars, node) {
17017              var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
17018  
17019              // Merges the styles for each node
17020  			function process(node) {
17021                  var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
17022  
17023                  // Skip on text nodes as they have neither format to remove nor children
17024                  if (node.nodeType === 3) {
17025                      return;
17026                  }
17027  
17028                  // Node has a contentEditable value
17029                  if (node.nodeType === 1 && getContentEditable(node)) {
17030                      lastContentEditable = contentEditable;
17031                      contentEditable = getContentEditable(node) === "true";
17032                      hasContentEditableState = true; // We don't want to wrap the container only it's children
17033                  }
17034  
17035                  // Grab the children first since the nodelist might be changed
17036                  children = tinymce.grep(node.childNodes);
17037  
17038                  // Process current node
17039                  if (contentEditable && !hasContentEditableState) {
17040                      for (i = 0, l = formatList.length; i < l; i++) {
17041                          if (removeFormat(formatList[i], vars, node, node))
17042                              break;
17043                      }
17044                  }
17045  
17046                  // Process the children
17047                  if (format.deep) {
17048                      if (children.length) {                    
17049                          for (i = 0, l = children.length; i < l; i++)
17050                              process(children[i]);
17051  
17052                          if (hasContentEditableState) {
17053                              contentEditable = lastContentEditable; // Restore last contentEditable state from stack
17054                          }
17055                      }
17056                  }
17057              };
17058  
17059  			function findFormatRoot(container) {
17060                  var formatRoot;
17061  
17062                  // Find format root
17063                  each(getParents(container.parentNode).reverse(), function(parent) {
17064                      var format;
17065  
17066                      // Find format root element
17067                      if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
17068                          // Is the node matching the format we are looking for
17069                          format = matchNode(parent, name, vars);
17070                          if (format && format.split !== false)
17071                              formatRoot = parent;
17072                      }
17073                  });
17074  
17075                  return formatRoot;
17076              };
17077  
17078  			function wrapAndSplit(format_root, container, target, split) {
17079                  var parent, clone, lastClone, firstClone, i, formatRootParent;
17080  
17081                  // Format root found then clone formats and split it
17082                  if (format_root) {
17083                      formatRootParent = format_root.parentNode;
17084  
17085                      for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
17086                          clone = dom.clone(parent, FALSE);
17087  
17088                          for (i = 0; i < formatList.length; i++) {
17089                              if (removeFormat(formatList[i], vars, clone, clone)) {
17090                                  clone = 0;
17091                                  break;
17092                              }
17093                          }
17094  
17095                          // Build wrapper node
17096                          if (clone) {
17097                              if (lastClone)
17098                                  clone.appendChild(lastClone);
17099  
17100                              if (!firstClone)
17101                                  firstClone = clone;
17102  
17103                              lastClone = clone;
17104                          }
17105                      }
17106  
17107                      // Never split block elements if the format is mixed
17108                      if (split && (!format.mixed || !isBlock(format_root)))
17109                          container = dom.split(format_root, container);
17110  
17111                      // Wrap container in cloned formats
17112                      if (lastClone) {
17113                          target.parentNode.insertBefore(lastClone, target);
17114                          firstClone.appendChild(target);
17115                      }
17116                  }
17117  
17118                  return container;
17119              };
17120  
17121  			function splitToFormatRoot(container) {
17122                  return wrapAndSplit(findFormatRoot(container), container, container, true);
17123              };
17124  
17125  			function unwrap(start) {
17126                  var node = dom.get(start ? '_start' : '_end'),
17127                      out = node[start ? 'firstChild' : 'lastChild'];
17128  
17129                  // If the end is placed within the start the result will be removed
17130                  // So this checks if the out node is a bookmark node if it is it
17131                  // checks for another more suitable node
17132                  if (isBookmarkNode(out))
17133                      out = out[start ? 'firstChild' : 'lastChild'];
17134  
17135                  dom.remove(node, true);
17136  
17137                  return out;
17138              };
17139  
17140  			function removeRngStyle(rng) {
17141                  var startContainer, endContainer, node;
17142  
17143                  rng = expandRng(rng, formatList, TRUE);
17144  
17145                  if (format.split) {
17146                      startContainer = getContainer(rng, TRUE);
17147                      endContainer = getContainer(rng);
17148  
17149                      if (startContainer != endContainer) {
17150                          // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
17151                          // This will happen if you tripple click a table cell and use remove formatting
17152                          if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
17153                              startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
17154                          }
17155  
17156                          // Wrap start/end nodes in span element since these might be cloned/moved
17157                          startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
17158                          endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
17159  
17160                          // Split start/end
17161                          splitToFormatRoot(startContainer);
17162                          splitToFormatRoot(endContainer);
17163  
17164                          // Unwrap start/end to get real elements again
17165                          startContainer = unwrap(TRUE);
17166                          endContainer = unwrap();
17167                      } else
17168                          startContainer = endContainer = splitToFormatRoot(startContainer);
17169  
17170                      // Update range positions since they might have changed after the split operations
17171                      rng.startContainer = startContainer.parentNode;
17172                      rng.startOffset = nodeIndex(startContainer);
17173                      rng.endContainer = endContainer.parentNode;
17174                      rng.endOffset = nodeIndex(endContainer) + 1;
17175                  }
17176  
17177                  // Remove items between start/end
17178                  rangeUtils.walk(rng, function(nodes) {
17179                      each(nodes, function(node) {
17180                          process(node);
17181  
17182                          // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
17183                          if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
17184                              removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
17185                          }
17186                      });
17187                  });
17188              };
17189  
17190              // Handle node
17191              if (node) {
17192                  if (node.nodeType) {
17193                      rng = dom.createRng();
17194                      rng.setStartBefore(node);
17195                      rng.setEndAfter(node);
17196                      removeRngStyle(rng);
17197                  } else {
17198                      removeRngStyle(node);
17199                  }
17200  
17201                  return;
17202              }
17203  
17204              if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
17205                  bookmark = selection.getBookmark();
17206                  removeRngStyle(selection.getRng(TRUE));
17207                  selection.moveToBookmark(bookmark);
17208  
17209                  // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
17210                  if (format.inline && match(name, vars, selection.getStart())) {
17211                      moveStart(selection.getRng(true));
17212                  }
17213  
17214                  ed.nodeChanged();
17215              } else
17216                  performCaretAction('remove', name, vars);
17217          };
17218  
17219  		function toggle(name, vars, node) {
17220              var fmt = get(name);
17221  
17222              if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
17223                  remove(name, vars, node);
17224              else
17225                  apply(name, vars, node);
17226          };
17227  
17228  		function matchNode(node, name, vars, similar) {
17229              var formatList = get(name), format, i, classes;
17230  
17231  			function matchItems(node, format, item_name) {
17232                  var key, value, items = format[item_name], i;
17233  
17234                  // Custom match
17235                  if (format.onmatch) {
17236                      return format.onmatch(node, format, item_name);
17237                  }
17238  
17239                  // Check all items
17240                  if (items) {
17241                      // Non indexed object
17242                      if (items.length === undef) {
17243                          for (key in items) {
17244                              if (items.hasOwnProperty(key)) {
17245                                  if (item_name === 'attributes')
17246                                      value = dom.getAttrib(node, key);
17247                                  else
17248                                      value = getStyle(node, key);
17249  
17250                                  if (similar && !value && !format.exact)
17251                                      return;
17252  
17253                                  if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
17254                                      return;
17255                              }
17256                          }
17257                      } else {
17258                          // Only one match needed for indexed arrays
17259                          for (i = 0; i < items.length; i++) {
17260                              if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
17261                                  return format;
17262                          }
17263                      }
17264                  }
17265  
17266                  return format;
17267              };
17268  
17269              if (formatList && node) {
17270                  // Check each format in list
17271                  for (i = 0; i < formatList.length; i++) {
17272                      format = formatList[i];
17273  
17274                      // Name name, attributes, styles and classes
17275                      if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
17276                          // Match classes
17277                          if (classes = format.classes) {
17278                              for (i = 0; i < classes.length; i++) {
17279                                  if (!dom.hasClass(node, classes[i]))
17280                                      return;
17281                              }
17282                          }
17283  
17284                          return format;
17285                      }
17286                  }
17287              }
17288          };
17289  
17290  		function match(name, vars, node) {
17291              var startNode;
17292  
17293  			function matchParents(node) {
17294                  // Find first node with similar format settings
17295                  node = dom.getParent(node, function(node) {
17296                      return !!matchNode(node, name, vars, true);
17297                  });
17298  
17299                  // Do an exact check on the similar format element
17300                  return matchNode(node, name, vars);
17301              };
17302  
17303              // Check specified node
17304              if (node)
17305                  return matchParents(node);
17306  
17307              // Check selected node
17308              node = selection.getNode();
17309              if (matchParents(node))
17310                  return TRUE;
17311  
17312              // Check start node if it's different
17313              startNode = selection.getStart();
17314              if (startNode != node) {
17315                  if (matchParents(startNode))
17316                      return TRUE;
17317              }
17318  
17319              return FALSE;
17320          };
17321  
17322  		function matchAll(names, vars) {
17323              var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
17324  
17325              // Check start of selection for formats
17326              startElement = selection.getStart();
17327              dom.getParent(startElement, function(node) {
17328                  var i, name;
17329  
17330                  for (i = 0; i < names.length; i++) {
17331                      name = names[i];
17332  
17333                      if (!checkedMap[name] && matchNode(node, name, vars)) {
17334                          checkedMap[name] = true;
17335                          matchedFormatNames.push(name);
17336                      }
17337                  }
17338              }, dom.getRoot());
17339  
17340              return matchedFormatNames;
17341          };
17342  
17343  		function canApply(name) {
17344              var formatList = get(name), startNode, parents, i, x, selector;
17345  
17346              if (formatList) {
17347                  startNode = selection.getStart();
17348                  parents = getParents(startNode);
17349  
17350                  for (x = formatList.length - 1; x >= 0; x--) {
17351                      selector = formatList[x].selector;
17352  
17353                      // Format is not selector based, then always return TRUE
17354                      if (!selector)
17355                          return TRUE;
17356  
17357                      for (i = parents.length - 1; i >= 0; i--) {
17358                          if (dom.is(parents[i], selector))
17359                              return TRUE;
17360                      }
17361                  }
17362              }
17363  
17364              return FALSE;
17365          };
17366  
17367  		function formatChanged(formats, callback, similar) {
17368              var currentFormats;
17369  
17370              // Setup format node change logic
17371              if (!formatChangeData) {
17372                  formatChangeData = {};
17373                  currentFormats = {};
17374  
17375                  ed.onNodeChange.addToTop(function(ed, cm, node) {
17376                      var parents = getParents(node), matchedFormats = {};
17377  
17378                      // Check for new formats
17379                      each(formatChangeData, function(callbacks, format) {
17380                          each(parents, function(node) {
17381                              if (matchNode(node, format, {}, callbacks.similar)) {
17382                                  if (!currentFormats[format]) {
17383                                      // Execute callbacks
17384                                      each(callbacks, function(callback) {
17385                                          callback(true, {node: node, format: format, parents: parents});
17386                                      });
17387  
17388                                      currentFormats[format] = callbacks;
17389                                  }
17390  
17391                                  matchedFormats[format] = callbacks;
17392                                  return false;
17393                              }
17394                          });
17395                      });
17396  
17397                      // Check if current formats still match
17398                      each(currentFormats, function(callbacks, format) {
17399                          if (!matchedFormats[format]) {
17400                              delete currentFormats[format];
17401  
17402                              each(callbacks, function(callback) {
17403                                  callback(false, {node: node, format: format, parents: parents});
17404                              });
17405                          }
17406                      });
17407                  });
17408              }
17409  
17410              // Add format listeners
17411              each(formats.split(','), function(format) {
17412                  if (!formatChangeData[format]) {
17413                      formatChangeData[format] = [];
17414                      formatChangeData[format].similar = similar;
17415                  }
17416  
17417                  formatChangeData[format].push(callback);
17418              });
17419  
17420              return this;
17421          };
17422  
17423          // Expose to public
17424          tinymce.extend(this, {
17425              get : get,
17426              register : register,
17427              apply : apply,
17428              remove : remove,
17429              toggle : toggle,
17430              match : match,
17431              matchAll : matchAll,
17432              matchNode : matchNode,
17433              canApply : canApply,
17434              formatChanged: formatChanged
17435          });
17436  
17437          // Initialize
17438          defaultFormats();
17439          addKeyboardShortcuts();
17440  
17441          // Private functions
17442  
17443  		function matchName(node, format) {
17444              // Check for inline match
17445              if (isEq(node, format.inline))
17446                  return TRUE;
17447  
17448              // Check for block match
17449              if (isEq(node, format.block))
17450                  return TRUE;
17451  
17452              // Check for selector match
17453              if (format.selector)
17454                  return dom.is(node, format.selector);
17455          };
17456  
17457  		function isEq(str1, str2) {
17458              str1 = str1 || '';
17459              str2 = str2 || '';
17460  
17461              str1 = '' + (str1.nodeName || str1);
17462              str2 = '' + (str2.nodeName || str2);
17463  
17464              return str1.toLowerCase() == str2.toLowerCase();
17465          };
17466  
17467  		function getStyle(node, name) {
17468              var styleVal = dom.getStyle(node, name);
17469  
17470              // Force the format to hex
17471              if (name == 'color' || name == 'backgroundColor')
17472                  styleVal = dom.toHex(styleVal);
17473  
17474              // Opera will return bold as 700
17475              if (name == 'fontWeight' && styleVal == 700)
17476                  styleVal = 'bold';
17477  
17478              return '' + styleVal;
17479          };
17480  
17481  		function replaceVars(value, vars) {
17482              if (typeof(value) != "string")
17483                  value = value(vars);
17484              else if (vars) {
17485                  value = value.replace(/%(\w+)/g, function(str, name) {
17486                      return vars[name] || str;
17487                  });
17488              }
17489  
17490              return value;
17491          };
17492  
17493  		function isWhiteSpaceNode(node) {
17494              return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17495          };
17496  
17497  		function wrap(node, name, attrs) {
17498              var wrapper = dom.create(name, attrs);
17499  
17500              node.parentNode.insertBefore(wrapper, node);
17501              wrapper.appendChild(node);
17502  
17503              return wrapper;
17504          };
17505  
17506  		function expandRng(rng, format, remove) {
17507              var sibling, lastIdx, leaf, endPoint,
17508                  startContainer = rng.startContainer,
17509                  startOffset = rng.startOffset,
17510                  endContainer = rng.endContainer,
17511                  endOffset = rng.endOffset;
17512  
17513              // This function walks up the tree if there is no siblings before/after the node
17514  			function findParentContainer(start) {
17515                  var container, parent, child, sibling, siblingName, root;
17516  
17517                  container = parent = start ? startContainer : endContainer;
17518                  siblingName = start ? 'previousSibling' : 'nextSibling';
17519                  root = dom.getRoot();
17520  
17521  				function isBogusBr(node) {
17522                      return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
17523                  };
17524  
17525                  // If it's a text node and the offset is inside the text
17526                  if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17527                      if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17528                          return container;
17529                      }
17530                  }
17531  
17532                  for (;;) {
17533                      // Stop expanding on block elements
17534                      if (!format[0].block_expand && isBlock(parent))
17535                          return parent;
17536  
17537                      // Walk left/right
17538                      for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17539                          if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
17540                              return parent;
17541                          }
17542                      }
17543  
17544                      // Check if we can move up are we at root level or body level
17545                      if (parent.parentNode == root) {
17546                          container = parent;
17547                          break;
17548                      }
17549  
17550                      parent = parent.parentNode;
17551                  }
17552  
17553                  return container;
17554              };
17555  
17556              // This function walks down the tree to find the leaf at the selection.
17557              // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17558  			function findLeaf(node, offset) {
17559                  if (offset === undef)
17560                      offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17561                  while (node && node.hasChildNodes()) {
17562                      node = node.childNodes[offset];
17563                      if (node)
17564                          offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17565                  }
17566                  return { node: node, offset: offset };
17567              }
17568  
17569              // If index based start position then resolve it
17570              if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17571                  lastIdx = startContainer.childNodes.length - 1;
17572                  startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17573  
17574                  if (startContainer.nodeType == 3)
17575                      startOffset = 0;
17576              }
17577  
17578              // If index based end position then resolve it
17579              if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17580                  lastIdx = endContainer.childNodes.length - 1;
17581                  endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17582  
17583                  if (endContainer.nodeType == 3)
17584                      endOffset = endContainer.nodeValue.length;
17585              }
17586  
17587              // Expands the node to the closes contentEditable false element if it exists
17588  			function findParentContentEditable(node) {
17589                  var parent = node;
17590  
17591                  while (parent) {
17592                      if (parent.nodeType === 1 && getContentEditable(parent)) {
17593                          return getContentEditable(parent) === "false" ? parent : node;
17594                      }
17595  
17596                      parent = parent.parentNode;
17597                  }
17598  
17599                  return node;
17600              };
17601  
17602  			function findWordEndPoint(container, offset, start) {
17603                  var walker, node, pos, lastTextNode;
17604  
17605  				function findSpace(node, offset) {
17606                      var pos, pos2, str = node.nodeValue;
17607  
17608                      if (typeof(offset) == "undefined") {
17609                          offset = start ? str.length : 0;
17610                      }
17611  
17612                      if (start) {
17613                          pos = str.lastIndexOf(' ', offset);
17614                          pos2 = str.lastIndexOf('\u00a0', offset);
17615                          pos = pos > pos2 ? pos : pos2;
17616  
17617                          // Include the space on remove to avoid tag soup
17618                          if (pos !== -1 && !remove) {
17619                              pos++;
17620                          }
17621                      } else {
17622                          pos = str.indexOf(' ', offset);
17623                          pos2 = str.indexOf('\u00a0', offset);
17624                          pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17625                      }
17626  
17627                      return pos;
17628                  };
17629  
17630                  if (container.nodeType === 3) {
17631                      pos = findSpace(container, offset);
17632  
17633                      if (pos !== -1) {
17634                          return {container : container, offset : pos};
17635                      }
17636  
17637                      lastTextNode = container;
17638                  }
17639  
17640                  // Walk the nodes inside the block
17641                  walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17642                  while (node = walker[start ? 'prev' : 'next']()) {
17643                      if (node.nodeType === 3) {
17644                          lastTextNode = node;
17645                          pos = findSpace(node);
17646  
17647                          if (pos !== -1) {
17648                              return {container : node, offset : pos};
17649                          }
17650                      } else if (isBlock(node)) {
17651                          break;
17652                      }
17653                  }
17654  
17655                  if (lastTextNode) {
17656                      if (start) {
17657                          offset = 0;
17658                      } else {
17659                          offset = lastTextNode.length;
17660                      }
17661  
17662                      return {container: lastTextNode, offset: offset};
17663                  }
17664              };
17665  
17666  			function findSelectorEndPoint(container, sibling_name) {
17667                  var parents, i, y, curFormat;
17668  
17669                  if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17670                      container = container[sibling_name];
17671  
17672                  parents = getParents(container);
17673                  for (i = 0; i < parents.length; i++) {
17674                      for (y = 0; y < format.length; y++) {
17675                          curFormat = format[y];
17676  
17677                          // If collapsed state is set then skip formats that doesn't match that
17678                          if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17679                              continue;
17680  
17681                          if (dom.is(parents[i], curFormat.selector))
17682                              return parents[i];
17683                      }
17684                  }
17685  
17686                  return container;
17687              };
17688  
17689  			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17690                  var node;
17691  
17692                  // Expand to block of similar type
17693                  if (!format[0].wrapper)
17694                      node = dom.getParent(container, format[0].block);
17695  
17696                  // Expand to first wrappable block element or any block element
17697                  if (!node)
17698                      node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock);
17699  
17700                  // Exclude inner lists from wrapping
17701                  if (node && format[0].wrapper)
17702                      node = getParents(node, 'ul,ol').reverse()[0] || node;
17703  
17704                  // Didn't find a block element look for first/last wrappable element
17705                  if (!node) {
17706                      node = container;
17707  
17708                      while (node[sibling_name] && !isBlock(node[sibling_name])) {
17709                          node = node[sibling_name];
17710  
17711                          // Break on BR but include it will be removed later on
17712                          // we can't remove it now since we need to check if it can be wrapped
17713                          if (isEq(node, 'br'))
17714                              break;
17715                      }
17716                  }
17717  
17718                  return node || container;
17719              };
17720  
17721              // Expand to closest contentEditable element
17722              startContainer = findParentContentEditable(startContainer);
17723              endContainer = findParentContentEditable(endContainer);
17724  
17725              // Exclude bookmark nodes if possible
17726              if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17727                  startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17728                  startContainer = startContainer.nextSibling || startContainer;
17729  
17730                  if (startContainer.nodeType == 3)
17731                      startOffset = 0;
17732              }
17733  
17734              if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17735                  endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17736                  endContainer = endContainer.previousSibling || endContainer;
17737  
17738                  if (endContainer.nodeType == 3)
17739                      endOffset = endContainer.length;
17740              }
17741  
17742              if (format[0].inline) {
17743                  if (rng.collapsed) {
17744                      // Expand left to closest word boundery
17745                      endPoint = findWordEndPoint(startContainer, startOffset, true);
17746                      if (endPoint) {
17747                          startContainer = endPoint.container;
17748                          startOffset = endPoint.offset;
17749                      }
17750  
17751                      // Expand right to closest word boundery
17752                      endPoint = findWordEndPoint(endContainer, endOffset);
17753                      if (endPoint) {
17754                          endContainer = endPoint.container;
17755                          endOffset = endPoint.offset;
17756                      }
17757                  }
17758  
17759                  // Avoid applying formatting to a trailing space.
17760                  leaf = findLeaf(endContainer, endOffset);
17761                  if (leaf.node) {
17762                      while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17763                          leaf = findLeaf(leaf.node.previousSibling);
17764  
17765                      if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17766                              leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17767  
17768                          if (leaf.offset > 1) {
17769                              endContainer = leaf.node;
17770                              endContainer.splitText(leaf.offset - 1);
17771                          }
17772                      }
17773                  }
17774              }
17775  
17776              // Move start/end point up the tree if the leaves are sharp and if we are in different containers
17777              // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17778              // This will reduce the number of wrapper elements that needs to be created
17779              // Move start point up the tree
17780              if (format[0].inline || format[0].block_expand) {
17781                  if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17782                      startContainer = findParentContainer(true);
17783                  }
17784  
17785                  if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17786                      endContainer = findParentContainer();
17787                  }
17788              }
17789  
17790              // Expand start/end container to matching selector
17791              if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17792                  // Find new startContainer/endContainer if there is better one
17793                  startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17794                  endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17795              }
17796  
17797              // Expand start/end container to matching block element or text node
17798              if (format[0].block || format[0].selector) {
17799                  // Find new startContainer/endContainer if there is better one
17800                  startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17801                  endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17802  
17803                  // Non block element then try to expand up the leaf
17804                  if (format[0].block) {
17805                      if (!isBlock(startContainer))
17806                          startContainer = findParentContainer(true);
17807  
17808                      if (!isBlock(endContainer))
17809                          endContainer = findParentContainer();
17810                  }
17811              }
17812  
17813              // Setup index for startContainer
17814              if (startContainer.nodeType == 1) {
17815                  startOffset = nodeIndex(startContainer);
17816                  startContainer = startContainer.parentNode;
17817              }
17818  
17819              // Setup index for endContainer
17820              if (endContainer.nodeType == 1) {
17821                  endOffset = nodeIndex(endContainer) + 1;
17822                  endContainer = endContainer.parentNode;
17823              }
17824  
17825              // Return new range like object
17826              return {
17827                  startContainer : startContainer,
17828                  startOffset : startOffset,
17829                  endContainer : endContainer,
17830                  endOffset : endOffset
17831              };
17832          }
17833  
17834  		function removeFormat(format, vars, node, compare_node) {
17835              var i, attrs, stylesModified;
17836  
17837              // Check if node matches format
17838              if (!matchName(node, format))
17839                  return FALSE;
17840  
17841              // Should we compare with format attribs and styles
17842              if (format.remove != 'all') {
17843                  // Remove styles
17844                  each(format.styles, function(value, name) {
17845                      value = replaceVars(value, vars);
17846  
17847                      // Indexed array
17848                      if (typeof(name) === 'number') {
17849                          name = value;
17850                          compare_node = 0;
17851                      }
17852  
17853                      if (!compare_node || isEq(getStyle(compare_node, name), value))
17854                          dom.setStyle(node, name, '');
17855  
17856                      stylesModified = 1;
17857                  });
17858  
17859                  // Remove style attribute if it's empty
17860                  if (stylesModified && dom.getAttrib(node, 'style') == '') {
17861                      node.removeAttribute('style');
17862                      node.removeAttribute('data-mce-style');
17863                  }
17864  
17865                  // Remove attributes
17866                  each(format.attributes, function(value, name) {
17867                      var valueOut;
17868  
17869                      value = replaceVars(value, vars);
17870  
17871                      // Indexed array
17872                      if (typeof(name) === 'number') {
17873                          name = value;
17874                          compare_node = 0;
17875                      }
17876  
17877                      if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17878                          // Keep internal classes
17879                          if (name == 'class') {
17880                              value = dom.getAttrib(node, name);
17881                              if (value) {
17882                                  // Build new class value where everything is removed except the internal prefixed classes
17883                                  valueOut = '';
17884                                  each(value.split(/\s+/), function(cls) {
17885                                      if (/mce\w+/.test(cls))
17886                                          valueOut += (valueOut ? ' ' : '') + cls;
17887                                  });
17888  
17889                                  // We got some internal classes left
17890                                  if (valueOut) {
17891                                      dom.setAttrib(node, name, valueOut);
17892                                      return;
17893                                  }
17894                              }
17895                          }
17896  
17897                          // IE6 has a bug where the attribute doesn't get removed correctly
17898                          if (name == "class")
17899                              node.removeAttribute('className');
17900  
17901                          // Remove mce prefixed attributes
17902                          if (MCE_ATTR_RE.test(name))
17903                              node.removeAttribute('data-mce-' + name);
17904  
17905                          node.removeAttribute(name);
17906                      }
17907                  });
17908  
17909                  // Remove classes
17910                  each(format.classes, function(value) {
17911                      value = replaceVars(value, vars);
17912  
17913                      if (!compare_node || dom.hasClass(compare_node, value))
17914                          dom.removeClass(node, value);
17915                  });
17916  
17917                  // Check for non internal attributes
17918                  attrs = dom.getAttribs(node);
17919                  for (i = 0; i < attrs.length; i++) {
17920                      if (attrs[i].nodeName.indexOf('_') !== 0)
17921                          return FALSE;
17922                  }
17923              }
17924  
17925              // Remove the inline child if it's empty for example <b> or <span>
17926              if (format.remove != 'none') {
17927                  removeNode(node, format);
17928                  return TRUE;
17929              }
17930          };
17931  
17932  		function removeNode(node, format) {
17933              var parentNode = node.parentNode, rootBlockElm;
17934  
17935  			function find(node, next, inc) {
17936                  node = getNonWhiteSpaceSibling(node, next, inc);
17937  
17938                  return !node || (node.nodeName == 'BR' || isBlock(node));
17939              };
17940  
17941              if (format.block) {
17942                  if (!forcedRootBlock) {
17943                      // Append BR elements if needed before we remove the block
17944                      if (isBlock(node) && !isBlock(parentNode)) {
17945                          if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17946                              node.insertBefore(dom.create('br'), node.firstChild);
17947  
17948                          if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17949                              node.appendChild(dom.create('br'));
17950                      }
17951                  } else {
17952                      // Wrap the block in a forcedRootBlock if we are at the root of document
17953                      if (parentNode == dom.getRoot()) {
17954                          if (!format.list_block || !isEq(node, format.list_block)) {
17955                              each(tinymce.grep(node.childNodes), function(node) {
17956                                  if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17957                                      if (!rootBlockElm)
17958                                          rootBlockElm = wrap(node, forcedRootBlock);
17959                                      else
17960                                          rootBlockElm.appendChild(node);
17961                                  } else
17962                                      rootBlockElm = 0;
17963                              });
17964                          }
17965                      }
17966                  }
17967              }
17968  
17969              // Never remove nodes that isn't the specified inline element if a selector is specified too
17970              if (format.selector && format.inline && !isEq(format.inline, node))
17971                  return;
17972  
17973              dom.remove(node, 1);
17974          };
17975  
17976  		function getNonWhiteSpaceSibling(node, next, inc) {
17977              if (node) {
17978                  next = next ? 'nextSibling' : 'previousSibling';
17979  
17980                  for (node = inc ? node : node[next]; node; node = node[next]) {
17981                      if (node.nodeType == 1 || !isWhiteSpaceNode(node))
17982                          return node;
17983                  }
17984              }
17985          };
17986  
17987  		function isBookmarkNode(node) {
17988              return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
17989          };
17990  
17991  		function mergeSiblings(prev, next) {
17992              var marker, sibling, tmpSibling;
17993  
17994  			function compareElements(node1, node2) {
17995                  // Not the same name
17996                  if (node1.nodeName != node2.nodeName)
17997                      return FALSE;
17998  
17999  				function getAttribs(node) {
18000                      var attribs = {};
18001  
18002                      each(dom.getAttribs(node), function(attr) {
18003                          var name = attr.nodeName.toLowerCase();
18004  
18005                          // Don't compare internal attributes or style
18006                          if (name.indexOf('_') !== 0 && name !== 'style')
18007                              attribs[name] = dom.getAttrib(node, name);
18008                      });
18009  
18010                      return attribs;
18011                  };
18012  
18013  				function compareObjects(obj1, obj2) {
18014                      var value, name;
18015  
18016                      for (name in obj1) {
18017                          // Obj1 has item obj2 doesn't have
18018                          if (obj1.hasOwnProperty(name)) {
18019                              value = obj2[name];
18020  
18021                              // Obj2 doesn't have obj1 item
18022                              if (value === undef)
18023                                  return FALSE;
18024  
18025                              // Obj2 item has a different value
18026                              if (obj1[name] != value)
18027                                  return FALSE;
18028  
18029                              // Delete similar value
18030                              delete obj2[name];
18031                          }
18032                      }
18033  
18034                      // Check if obj 2 has something obj 1 doesn't have
18035                      for (name in obj2) {
18036                          // Obj2 has item obj1 doesn't have
18037                          if (obj2.hasOwnProperty(name))
18038                              return FALSE;
18039                      }
18040  
18041                      return TRUE;
18042                  };
18043  
18044                  // Attribs are not the same
18045                  if (!compareObjects(getAttribs(node1), getAttribs(node2)))
18046                      return FALSE;
18047  
18048                  // Styles are not the same
18049                  if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
18050                      return FALSE;
18051  
18052                  return TRUE;
18053              };
18054  
18055  			function findElementSibling(node, sibling_name) {
18056                  for (sibling = node; sibling; sibling = sibling[sibling_name]) {
18057                      if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
18058                          return node;
18059  
18060                      if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
18061                          return sibling;
18062                  }
18063  
18064                  return node;
18065              };
18066  
18067              // Check if next/prev exists and that they are elements
18068              if (prev && next) {
18069                  // If previous sibling is empty then jump over it
18070                  prev = findElementSibling(prev, 'previousSibling');
18071                  next = findElementSibling(next, 'nextSibling');
18072  
18073                  // Compare next and previous nodes
18074                  if (compareElements(prev, next)) {
18075                      // Append nodes between
18076                      for (sibling = prev.nextSibling; sibling && sibling != next;) {
18077                          tmpSibling = sibling;
18078                          sibling = sibling.nextSibling;
18079                          prev.appendChild(tmpSibling);
18080                      }
18081  
18082                      // Remove next node
18083                      dom.remove(next);
18084  
18085                      // Move children into prev node
18086                      each(tinymce.grep(next.childNodes), function(node) {
18087                          prev.appendChild(node);
18088                      });
18089  
18090                      return prev;
18091                  }
18092              }
18093  
18094              return next;
18095          };
18096  
18097  		function getContainer(rng, start) {
18098              var container, offset, lastIdx, walker;
18099  
18100              container = rng[start ? 'startContainer' : 'endContainer'];
18101              offset = rng[start ? 'startOffset' : 'endOffset'];
18102  
18103              if (container.nodeType == 1) {
18104                  lastIdx = container.childNodes.length - 1;
18105  
18106                  if (!start && offset)
18107                      offset--;
18108  
18109                  container = container.childNodes[offset > lastIdx ? lastIdx : offset];
18110              }
18111  
18112              // If start text node is excluded then walk to the next node
18113              if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
18114                  container = new TreeWalker(container, ed.getBody()).next() || container;
18115              }
18116  
18117              // If end text node is excluded then walk to the previous node
18118              if (container.nodeType === 3 && !start && offset === 0) {
18119                  container = new TreeWalker(container, ed.getBody()).prev() || container;
18120              }
18121  
18122              return container;
18123          };
18124  
18125  		function performCaretAction(type, name, vars) {
18126              var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
18127  
18128              // Creates a caret container bogus element
18129  			function createCaretContainer(fill) {
18130                  var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
18131  
18132                  if (fill) {
18133                      caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
18134                  }
18135  
18136                  return caretContainer;
18137              };
18138  
18139  			function isCaretContainerEmpty(node, nodes) {
18140                  while (node) {
18141                      if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
18142                          return false;
18143                      }
18144  
18145                      // Collect nodes
18146                      if (nodes && node.nodeType === 1) {
18147                          nodes.push(node);
18148                      }
18149  
18150                      node = node.firstChild;
18151                  }
18152  
18153                  return true;
18154              };
18155              
18156              // Returns any parent caret container element
18157  			function getParentCaretContainer(node) {
18158                  while (node) {
18159                      if (node.id === caretContainerId) {
18160                          return node;
18161                      }
18162  
18163                      node = node.parentNode;
18164                  }
18165              };
18166  
18167              // Finds the first text node in the specified node
18168  			function findFirstTextNode(node) {
18169                  var walker;
18170  
18171                  if (node) {
18172                      walker = new TreeWalker(node, node);
18173  
18174                      for (node = walker.current(); node; node = walker.next()) {
18175                          if (node.nodeType === 3) {
18176                              return node;
18177                          }
18178                      }
18179                  }
18180              };
18181  
18182              // Removes the caret container for the specified node or all on the current document
18183  			function removeCaretContainer(node, move_caret) {
18184                  var child, rng;
18185  
18186                  if (!node) {
18187                      node = getParentCaretContainer(selection.getStart());
18188  
18189                      if (!node) {
18190                          while (node = dom.get(caretContainerId)) {
18191                              removeCaretContainer(node, false);
18192                          }
18193                      }
18194                  } else {
18195                      rng = selection.getRng(true);
18196  
18197                      if (isCaretContainerEmpty(node)) {
18198                          if (move_caret !== false) {
18199                              rng.setStartBefore(node);
18200                              rng.setEndBefore(node);
18201                          }
18202  
18203                          dom.remove(node);
18204                      } else {
18205                          child = findFirstTextNode(node);
18206  
18207                          if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
18208                              child = child.deleteData(0, 1);
18209                          }
18210  
18211                          dom.remove(node, 1);
18212                      }
18213  
18214                      selection.setRng(rng);
18215                  }
18216              };
18217              
18218              // Applies formatting to the caret postion
18219  			function applyCaretFormat() {
18220                  var rng, caretContainer, textNode, offset, bookmark, container, text;
18221  
18222                  rng = selection.getRng(true);
18223                  offset = rng.startOffset;
18224                  container = rng.startContainer;
18225                  text = container.nodeValue;
18226  
18227                  caretContainer = getParentCaretContainer(selection.getStart());
18228                  if (caretContainer) {
18229                      textNode = findFirstTextNode(caretContainer);
18230                  }
18231  
18232                  // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
18233                  if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
18234                      // Get bookmark of caret position
18235                      bookmark = selection.getBookmark();
18236  
18237                      // Collapse bookmark range (WebKit)
18238                      rng.collapse(true);
18239  
18240                      // Expand the range to the closest word and split it at those points
18241                      rng = expandRng(rng, get(name));
18242                      rng = rangeUtils.split(rng);
18243  
18244                      // Apply the format to the range
18245                      apply(name, vars, rng);
18246  
18247                      // Move selection back to caret position
18248                      selection.moveToBookmark(bookmark);
18249                  } else {
18250                      if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
18251                          caretContainer = createCaretContainer(true);
18252                          textNode = caretContainer.firstChild;
18253  
18254                          rng.insertNode(caretContainer);
18255                          offset = 1;
18256  
18257                          apply(name, vars, caretContainer);
18258                      } else {
18259                          apply(name, vars, caretContainer);
18260                      }
18261  
18262                      // Move selection to text node
18263                      selection.setCursorLocation(textNode, offset);
18264                  }
18265              };
18266  
18267  			function removeCaretFormat() {
18268                  var rng = selection.getRng(true), container, offset, bookmark,
18269                      hasContentAfter, node, formatNode, parents = [], i, caretContainer;
18270  
18271                  container = rng.startContainer;
18272                  offset = rng.startOffset;
18273                  node = container;
18274  
18275                  if (container.nodeType == 3) {
18276                      if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
18277                          hasContentAfter = true;
18278                      }
18279  
18280                      node = node.parentNode;
18281                  }
18282  
18283                  while (node) {
18284                      if (matchNode(node, name, vars)) {
18285                          formatNode = node;
18286                          break;
18287                      }
18288  
18289                      if (node.nextSibling) {
18290                          hasContentAfter = true;
18291                      }
18292  
18293                      parents.push(node);
18294                      node = node.parentNode;
18295                  }
18296  
18297                  // Node doesn't have the specified format
18298                  if (!formatNode) {
18299                      return;
18300                  }
18301  
18302                  // Is there contents after the caret then remove the format on the element
18303                  if (hasContentAfter) {
18304                      // Get bookmark of caret position
18305                      bookmark = selection.getBookmark();
18306  
18307                      // Collapse bookmark range (WebKit)
18308                      rng.collapse(true);
18309  
18310                      // Expand the range to the closest word and split it at those points
18311                      rng = expandRng(rng, get(name), true);
18312                      rng = rangeUtils.split(rng);
18313  
18314                      // Remove the format from the range
18315                      remove(name, vars, rng);
18316  
18317                      // Move selection back to caret position
18318                      selection.moveToBookmark(bookmark);
18319                  } else {
18320                      caretContainer = createCaretContainer();
18321  
18322                      node = caretContainer;
18323                      for (i = parents.length - 1; i >= 0; i--) {
18324                          node.appendChild(dom.clone(parents[i], false));
18325                          node = node.firstChild;
18326                      }
18327  
18328                      // Insert invisible character into inner most format element
18329                      node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
18330                      node = node.firstChild;
18331  
18332                      var block = dom.getParent(formatNode, isTextBlock);
18333  
18334                      if (block && dom.isEmpty(block)) {
18335                          // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
18336                          formatNode.parentNode.replaceChild(caretContainer, formatNode);
18337                      } else {
18338                          // Insert caret container after the formated node
18339                          dom.insertAfter(caretContainer, formatNode);
18340                      }
18341  
18342                      // Move selection to text node
18343                      selection.setCursorLocation(node, 1);
18344  
18345                      // If the formatNode is empty, we can remove it safely. 
18346                      if (dom.isEmpty(formatNode)) {
18347                          dom.remove(formatNode);
18348                      }
18349                  }
18350              };
18351  
18352              // Checks if the parent caret container node isn't empty if that is the case it
18353              // will remove the bogus state on all children that isn't empty
18354  			function unmarkBogusCaretParents() {
18355                  var i, caretContainer, node;
18356  
18357                  caretContainer = getParentCaretContainer(selection.getStart());
18358                  if (caretContainer && !dom.isEmpty(caretContainer)) {
18359                      tinymce.walk(caretContainer, function(node) {
18360                          if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
18361                              dom.setAttrib(node, 'data-mce-bogus', null);
18362                          }
18363                      }, 'childNodes');
18364                  }
18365              };
18366  
18367              // Only bind the caret events once
18368              if (!self._hasCaretEvents) {
18369                  // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
18370                  ed.onBeforeGetContent.addToTop(function() {
18371                      var nodes = [], i;
18372  
18373                      if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
18374                          // Mark children
18375                          i = nodes.length;
18376                          while (i--) {
18377                              dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
18378                          }
18379                      }
18380                  });
18381  
18382                  // Remove caret container on mouse up and on key up
18383                  tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
18384                      ed[name].addToTop(function() {
18385                          removeCaretContainer();
18386                          unmarkBogusCaretParents();
18387                      });
18388                  });
18389  
18390                  // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18391                  ed.onKeyDown.addToTop(function(ed, e) {
18392                      var keyCode = e.keyCode;
18393  
18394                      if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18395                          removeCaretContainer(getParentCaretContainer(selection.getStart()));
18396                      }
18397  
18398                      unmarkBogusCaretParents();
18399                  });
18400  
18401                  // Remove bogus state if they got filled by contents using editor.selection.setContent
18402                  selection.onSetContent.add(unmarkBogusCaretParents);
18403  
18404                  self._hasCaretEvents = true;
18405              }
18406  
18407              // Do apply or remove caret format
18408              if (type == "apply") {
18409                  applyCaretFormat();
18410              } else {
18411                  removeCaretFormat();
18412              }
18413          };
18414  
18415  		function moveStart(rng) {
18416              var container = rng.startContainer,
18417                      offset = rng.startOffset, isAtEndOfText,
18418                      walker, node, nodes, tmpNode;
18419  
18420              // Convert text node into index if possible
18421              if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18422                  // Get the parent container location and walk from there
18423                  offset = nodeIndex(container);
18424                  container = container.parentNode;
18425                  isAtEndOfText = true;
18426              }
18427  
18428              // Move startContainer/startOffset in to a suitable node
18429              if (container.nodeType == 1) {
18430                  nodes = container.childNodes;
18431                  container = nodes[Math.min(offset, nodes.length - 1)];
18432                  walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18433  
18434                  // If offset is at end of the parent node walk to the next one
18435                  if (offset > nodes.length - 1 || isAtEndOfText)
18436                      walker.next();
18437  
18438                  for (node = walker.current(); node; node = walker.next()) {
18439                      if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18440                          // IE has a "neat" feature where it moves the start node into the closest element
18441                          // we can avoid this by inserting an element before it and then remove it after we set the selection
18442                          tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18443                          node.parentNode.insertBefore(tmpNode, node);
18444  
18445                          // Set selection and remove tmpNode
18446                          rng.setStart(node, 0);
18447                          selection.setRng(rng);
18448                          dom.remove(tmpNode);
18449  
18450                          return;
18451                      }
18452                  }
18453              }
18454          };
18455      };
18456  })(tinymce);
18457  tinymce.onAddEditor.add(function(tinymce, ed) {
18458      var filters, fontSizes, dom, settings = ed.settings;
18459  
18460  	function replaceWithSpan(node, styles) {
18461          tinymce.each(styles, function(value, name) {
18462              if (value)
18463                  dom.setStyle(node, name, value);
18464          });
18465  
18466          dom.rename(node, 'span');
18467      };
18468  
18469  	function convert(editor, params) {
18470          dom = editor.dom;
18471  
18472          if (settings.convert_fonts_to_spans) {
18473              tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18474                  filters[node.nodeName.toLowerCase()](ed.dom, node);
18475              });
18476          }
18477      };
18478  
18479      if (settings.inline_styles) {
18480          fontSizes = tinymce.explode(settings.font_size_legacy_values);
18481  
18482          filters = {
18483              font : function(dom, node) {
18484                  replaceWithSpan(node, {
18485                      backgroundColor : node.style.backgroundColor,
18486                      color : node.color,
18487                      fontFamily : node.face,
18488                      fontSize : fontSizes[parseInt(node.size, 10) - 1]
18489                  });
18490              },
18491  
18492              u : function(dom, node) {
18493                  replaceWithSpan(node, {
18494                      textDecoration : 'underline'
18495                  });
18496              },
18497  
18498              strike : function(dom, node) {
18499                  replaceWithSpan(node, {
18500                      textDecoration : 'line-through'
18501                  });
18502              }
18503          };
18504  
18505          ed.onPreProcess.add(convert);
18506          ed.onSetContent.add(convert);
18507  
18508          ed.onInit.add(function() {
18509              ed.selection.onSetContent.add(convert);
18510          });
18511      }
18512  });
18513  (function(tinymce) {
18514      var TreeWalker = tinymce.dom.TreeWalker;
18515  
18516      tinymce.EnterKey = function(editor) {
18517          var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18518  
18519  		function handleEnterKey(evt) {
18520              var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
18521                  newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18522  
18523              // Returns true if the block can be split into two blocks or not
18524  			function canSplitBlock(node) {
18525                  return node &&
18526                      dom.isBlock(node) &&
18527                      !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18528                      !/^(fixed|absolute)/i.test(node.style.position) && 
18529                      dom.getContentEditable(node) !== "true";
18530              };
18531  
18532              // Renders empty block on IE
18533  			function renderBlockOnIE(block) {
18534                  var oldRng;
18535  
18536                  if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) {
18537                      oldRng = selection.getRng();
18538                      block.appendChild(dom.create('span', null, '\u00a0'));
18539                      selection.select(block);
18540                      block.lastChild.outerHTML = '';
18541                      selection.setRng(oldRng);
18542                  }
18543              };
18544  
18545              // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18546  			function trimInlineElementsOnLeftSideOfBlock(block) {
18547                  var node = block, firstChilds = [], i;
18548  
18549                  // Find inner most first child ex: <p><i><b>*</b></i></p>
18550                  while (node = node.firstChild) {
18551                      if (dom.isBlock(node)) {
18552                          return;
18553                      }
18554  
18555                      if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18556                          firstChilds.push(node);
18557                      }
18558                  }
18559  
18560                  i = firstChilds.length;
18561                  while (i--) {
18562                      node = firstChilds[i];
18563                      if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18564                          dom.remove(node);
18565                      } else {
18566                          // Remove <a> </a> see #5381
18567                          if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
18568                              dom.remove(node);
18569                          }
18570                      }
18571                  }
18572              };
18573              
18574              // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18575  			function moveToCaretPosition(root) {
18576                  var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18577  
18578                  rng = dom.createRng();
18579  
18580                  if (root.hasChildNodes()) {
18581                      walker = new TreeWalker(root, root);
18582  
18583                      while (node = walker.current()) {
18584                          if (node.nodeType == 3) {
18585                              rng.setStart(node, 0);
18586                              rng.setEnd(node, 0);
18587                              break;
18588                          }
18589  
18590                          if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18591                              rng.setStartBefore(node);
18592                              rng.setEndBefore(node);
18593                              break;
18594                          }
18595  
18596                          lastNode = node;
18597                          node = walker.next();
18598                      }
18599  
18600                      if (!node) {
18601                          rng.setStart(lastNode, 0);
18602                          rng.setEnd(lastNode, 0);
18603                      }
18604                  } else {
18605                      if (root.nodeName == 'BR') {
18606                          if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18607                              // Trick on older IE versions to render the caret before the BR between two lists
18608                              if (!documentMode || documentMode < 9) {
18609                                  tempElm = dom.create('br');
18610                                  root.parentNode.insertBefore(tempElm, root);
18611                              }
18612  
18613                              rng.setStartBefore(root);
18614                              rng.setEndBefore(root);
18615                          } else {
18616                              rng.setStartAfter(root);
18617                              rng.setEndAfter(root);
18618                          }
18619                      } else {
18620                          rng.setStart(root, 0);
18621                          rng.setEnd(root, 0);
18622                      }
18623                  }
18624  
18625                  selection.setRng(rng);
18626  
18627                  // Remove tempElm created for old IE:s
18628                  dom.remove(tempElm);
18629  
18630                  viewPort = dom.getViewPort(editor.getWin());
18631  
18632                  // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18633                  y = dom.getPos(root).y;
18634                  if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18635                      editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18636                  }
18637              };
18638  
18639              // Creates a new block element by cloning the current one or creating a new one if the name is specified
18640              // This function will also copy any text formatting from the parent block and add it to the new one
18641  			function createNewBlock(name) {
18642                  var node = container, block, clonedNode, caretNode;
18643  
18644                  block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18645                  caretNode = block;
18646  
18647                  // Clone any parent styles
18648                  if (settings.keep_styles !== false) {
18649                      do {
18650                          if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18651                              // Never clone a caret containers
18652                              if (node.id == '_mce_caret') {
18653                                  continue;
18654                              }
18655  
18656                              clonedNode = node.cloneNode(false);
18657                              dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18658  
18659                              if (block.hasChildNodes()) {
18660                                  clonedNode.appendChild(block.firstChild);
18661                                  block.appendChild(clonedNode);
18662                              } else {
18663                                  caretNode = clonedNode;
18664                                  block.appendChild(clonedNode);
18665                              }
18666                          }
18667                      } while (node = node.parentNode);
18668                  }
18669  
18670                  // BR is needed in empty blocks on non IE browsers
18671                  if (!tinymce.isIE || tinymce.isIE11) {
18672                      caretNode.innerHTML = '<br data-mce-bogus="1">';
18673                  }
18674  
18675                  return block;
18676              };
18677  
18678              // Returns true/false if the caret is at the start/end of the parent block element
18679  			function isCaretAtStartOrEndOfBlock(start) {
18680                  var walker, node, name;
18681  
18682                  // Caret is in the middle of a text node like "a|b"
18683                  if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18684                      return false;
18685                  }
18686  
18687                  // If after the last element in block node edge case for #5091
18688                  if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18689                      return true;
18690                  }
18691  
18692                  // If the caret if before the first element in parentBlock
18693                  if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18694                      return true;
18695                  }
18696  
18697                  // Caret can be before/after a table
18698                  if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18699                      return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18700                  }
18701  
18702                  // Walk the DOM and look for text nodes or non empty elements
18703                  walker = new TreeWalker(container, parentBlock);
18704      
18705                  // If caret is in beginning or end of a text block then jump to the next/previous node
18706                  if (container.nodeType == 3) {
18707                      if (start && offset == 0) {
18708                          walker.prev();
18709                      } else if (!start && offset == container.nodeValue.length) {
18710                          walker.next();
18711                      }
18712                  }
18713  
18714                  while (node = walker.current()) {
18715                      if (node.nodeType === 1) {
18716                          // Ignore bogus elements
18717                          if (!node.getAttribute('data-mce-bogus')) {
18718                              // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18719                              name = node.nodeName.toLowerCase();
18720                              if (nonEmptyElementsMap[name] && name !== 'br') {
18721                                  return false;
18722                              }
18723                          }
18724                      } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18725                          return false;
18726                      }
18727  
18728                      if (start) {
18729                          walker.prev();
18730                      } else {
18731                          walker.next();
18732                      }
18733                  }
18734  
18735                  return true;
18736              };
18737  
18738              // Wraps any text nodes or inline elements in the specified forced root block name
18739  			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18740                  var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18741  
18742                  // Not in a block element or in a table cell or caption
18743                  parentBlock = dom.getParent(container, dom.isBlock);
18744                  if (!parentBlock || !canSplitBlock(parentBlock)) {
18745                      parentBlock = parentBlock || editableRoot;
18746  
18747                      if (!parentBlock.hasChildNodes()) {
18748                          newBlock = dom.create(blockName);
18749                          parentBlock.appendChild(newBlock);
18750                          rng.setStart(newBlock, 0);
18751                          rng.setEnd(newBlock, 0);
18752                          return newBlock;
18753                      }
18754  
18755                      // Find parent that is the first child of parentBlock
18756                      node = container;
18757                      while (node.parentNode != parentBlock) {
18758                          node = node.parentNode;
18759                      }
18760  
18761                      // Loop left to find start node start wrapping at
18762                      while (node && !dom.isBlock(node)) {
18763                          startNode = node;
18764                          node = node.previousSibling;
18765                      }
18766  
18767                      if (startNode) {
18768                          newBlock = dom.create(blockName);
18769                          startNode.parentNode.insertBefore(newBlock, startNode);
18770  
18771                          // Start wrapping until we hit a block
18772                          node = startNode;
18773                          while (node && !dom.isBlock(node)) {
18774                              next = node.nextSibling;
18775                              newBlock.appendChild(node);
18776                              node = next;
18777                          }
18778  
18779                          // Restore range to it's past location
18780                          rng.setStart(container, offset);
18781                          rng.setEnd(container, offset);
18782                      }
18783                  }
18784  
18785                  return container;
18786              };
18787  
18788              // Inserts a block or br before/after or in the middle of a split list of the LI is empty
18789  			function handleEmptyListItem() {
18790  				function isFirstOrLastLi(first) {
18791                      var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18792  
18793                      // Find first/last element since there might be whitespace there
18794                      while (node) {
18795                          if (node.nodeType == 1) {
18796                              break;
18797                          }
18798  
18799                          node = node[first ? 'nextSibling' : 'previousSibling'];
18800                      }
18801  
18802                      return node === parentBlock;
18803                  };
18804  
18805                  newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18806  
18807                  if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18808                      // Is first and last list item then replace the OL/UL with a text block
18809                      dom.replace(newBlock, containerBlock);
18810                  } else if (isFirstOrLastLi(true)) {
18811                      // First LI in list then remove LI and add text block before list
18812                      containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18813                  } else if (isFirstOrLastLi()) {
18814                      // Last LI in list then temove LI and add text block after list
18815                      dom.insertAfter(newBlock, containerBlock);
18816                      renderBlockOnIE(newBlock);
18817                  } else {
18818                      // Middle LI in list the split the list and insert a text block in the middle
18819                      // Extract after fragment and insert it after the current block
18820                      tmpRng = rng.cloneRange();
18821                      tmpRng.setStartAfter(parentBlock);
18822                      tmpRng.setEndAfter(containerBlock);
18823                      fragment = tmpRng.extractContents();
18824                      dom.insertAfter(fragment, containerBlock);
18825                      dom.insertAfter(newBlock, containerBlock);
18826                  }
18827  
18828                  dom.remove(parentBlock);
18829                  moveToCaretPosition(newBlock);
18830                  undoManager.add();
18831              };
18832  
18833              // Walks the parent block to the right and look for any contents
18834  			function hasRightSideContent() {
18835                  var walker = new TreeWalker(container, parentBlock), node;
18836  
18837                  while (node = walker.next()) {
18838                      if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
18839                          return true;
18840                      }
18841                  }
18842              }
18843  
18844              // Inserts a BR element if the forced_root_block option is set to false or empty string
18845  			function insertBr() {
18846                  var brElm, extraBr, marker;
18847  
18848                  if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18849                      // Insert extra BR element at the end block elements
18850                      if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) {
18851                          brElm = dom.create('br');
18852                          rng.insertNode(brElm);
18853                          rng.setStartAfter(brElm);
18854                          rng.setEndAfter(brElm);
18855                          extraBr = true;
18856                      }
18857                  }
18858  
18859                  brElm = dom.create('br');
18860                  rng.insertNode(brElm);
18861  
18862                  // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18863                  if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18864                      brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18865                  }
18866  
18867                  // Insert temp marker and scroll to that
18868                  marker = dom.create('span', {}, '&nbsp;');
18869                  brElm.parentNode.insertBefore(marker, brElm);
18870                  selection.scrollIntoView(marker);
18871                  dom.remove(marker);
18872  
18873                  if (!extraBr) {
18874                      rng.setStartAfter(brElm);
18875                      rng.setEndAfter(brElm);
18876                  } else {
18877                      rng.setStartBefore(brElm);
18878                      rng.setEndBefore(brElm);
18879                  }
18880  
18881                  selection.setRng(rng);
18882                  undoManager.add();
18883              };
18884  
18885              // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18886  			function trimLeadingLineBreaks(node) {
18887                  do {
18888                      if (node.nodeType === 3) {
18889                          node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18890                      }
18891  
18892                      node = node.firstChild;
18893                  } while (node);
18894              };
18895  
18896  			function getEditableRoot(node) {
18897                  var root = dom.getRoot(), parent, editableRoot;
18898  
18899                  // Get all parents until we hit a non editable parent or the root
18900                  parent = node;
18901                  while (parent !== root && dom.getContentEditable(parent) !== "false") {
18902                      if (dom.getContentEditable(parent) === "true") {
18903                          editableRoot = parent;
18904                      }
18905  
18906                      parent = parent.parentNode;
18907                  }
18908                  
18909                  return parent !== root ? editableRoot : root;
18910              };
18911  
18912              // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18913  			function addBrToBlockIfNeeded(block) {
18914                  var lastChild;
18915  
18916                  // IE will render the blocks correctly other browsers needs a BR
18917                  if (!tinymce.isIE || tinymce.isIE11) {
18918                      block.normalize(); // Remove empty text nodes that got left behind by the extract
18919  
18920                      // Check if the block is empty or contains a floated last child
18921                      lastChild = block.lastChild;
18922                      if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18923                          dom.add(block, 'br');
18924                      }
18925                  }
18926              };
18927  
18928              // Delete any selected contents
18929              if (!rng.collapsed) {
18930                  editor.execCommand('Delete');
18931                  return;
18932              }
18933  
18934              // Event is blocked by some other handler for example the lists plugin
18935              if (evt.isDefaultPrevented()) {
18936                  return;
18937              }
18938  
18939              // Setup range items and newBlockName
18940              container = rng.startContainer;
18941              offset = rng.startOffset;
18942              newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
18943              newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18944              documentMode = dom.doc.documentMode;
18945              shiftKey = evt.shiftKey;
18946  
18947              // Resolve node index
18948              if (container.nodeType == 1 && container.hasChildNodes()) {
18949                  isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18950                  container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18951                  if (isAfterLastNodeInContainer && container.nodeType == 3) {
18952                      offset = container.nodeValue.length;
18953                  } else {
18954                      offset = 0;
18955                  }
18956              }
18957  
18958              // Get editable root node normaly the body element but sometimes a div or span
18959              editableRoot = getEditableRoot(container);
18960  
18961              // If there is no editable root then enter is done inside a contentEditable false element
18962              if (!editableRoot) {
18963                  return;
18964              }
18965  
18966              undoManager.beforeChange();
18967  
18968              // If editable root isn't block nor the root of the editor
18969              if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18970                  if (!newBlockName || shiftKey) {
18971                      insertBr();
18972                  }
18973  
18974                  return;
18975              }
18976  
18977              // Wrap the current node and it's sibling in a default block if it's needed.
18978              // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
18979              // This won't happen if root blocks are disabled or the shiftKey is pressed
18980              if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
18981                  container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
18982              }
18983  
18984              // Find parent block and setup empty block paddings
18985              parentBlock = dom.getParent(container, dom.isBlock);
18986              containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
18987  
18988              // Setup block names
18989              parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18990              containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
18991  
18992              // Enter inside block contained within a LI then split or insert before/after LI
18993              if (containerBlockName == 'LI' && !evt.ctrlKey) {
18994                  parentBlock = containerBlock;
18995                  parentBlockName = containerBlockName;
18996              }
18997  
18998              // Handle enter in LI
18999              if (parentBlockName == 'LI') {
19000                  if (!newBlockName && shiftKey) {
19001                      insertBr();
19002                      return;
19003                  }
19004  
19005                  // Handle enter inside an empty list item
19006                  if (dom.isEmpty(parentBlock)) {
19007                      // Let the list plugin or browser handle nested lists for now
19008                      if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
19009                          return false;
19010                      }
19011  
19012                      handleEmptyListItem();
19013                      return;
19014                  }
19015              }
19016  
19017              // Don't split PRE tags but insert a BR instead easier when writing code samples etc
19018              if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
19019                  if (!shiftKey) {
19020                      insertBr();
19021                      return;
19022                  }
19023              } else {
19024                  // If no root block is configured then insert a BR by default or if the shiftKey is pressed
19025                  if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
19026                      insertBr();
19027                      return;
19028                  }
19029              }
19030  
19031              // Default block name if it's not configured
19032              newBlockName = newBlockName || 'P';
19033  
19034              // Insert new block before/after the parent block depending on caret location
19035              if (isCaretAtStartOrEndOfBlock()) {
19036                  // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
19037                  if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
19038                      newBlock = createNewBlock(newBlockName);
19039                  } else {
19040                      newBlock = createNewBlock();
19041                  }
19042  
19043                  // Split the current container block element if enter is pressed inside an empty inner block element
19044                  if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
19045                      // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
19046                      newBlock = dom.split(containerBlock, parentBlock);
19047                  } else {
19048                      dom.insertAfter(newBlock, parentBlock);
19049                  }
19050  
19051                  moveToCaretPosition(newBlock);
19052              } else if (isCaretAtStartOrEndOfBlock(true)) {
19053                  // Insert new block before
19054                  newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
19055                  renderBlockOnIE(newBlock);
19056              } else {
19057                  // Extract after fragment and insert it after the current block
19058                  tmpRng = rng.cloneRange();
19059                  tmpRng.setEndAfter(parentBlock);
19060                  fragment = tmpRng.extractContents();
19061                  trimLeadingLineBreaks(fragment);
19062                  newBlock = fragment.firstChild;
19063                  dom.insertAfter(fragment, parentBlock);
19064                  trimInlineElementsOnLeftSideOfBlock(newBlock);
19065                  addBrToBlockIfNeeded(parentBlock);
19066                  moveToCaretPosition(newBlock);
19067              }
19068  
19069              dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
19070              undoManager.add();
19071          }
19072  
19073          editor.onKeyDown.add(function(ed, evt) {
19074              if (evt.keyCode == 13) {
19075                  if (handleEnterKey(evt) !== false) {
19076                      evt.preventDefault();
19077                  }
19078              }
19079          });
19080      };
19081  })(tinymce);


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1