[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/editor/tinymce/tiny_mce/3.5.11/ -> 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.11',
  10  
  11          releaseDate : '2014-05-08',
  12  
  13          _init : function() {
  14              var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
  15  
  16              t.isIE11 = ua.indexOf('Trident/') != -1 && (ua.indexOf('rv:') != -1 || na.appName.indexOf('Netscape') != -1);
  17  
  18              t.isOpera = win.opera && opera.buildNumber;
  19  
  20              t.isWebKit = /WebKit/.test(ua);
  21  
  22              t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName) || t.isIE11;
  23  
  24              t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
  25  
  26              t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
  27  
  28              t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
  29  
  30              t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
  31  
  32              t.isGecko = !t.isWebKit && !t.isIE11 && /Gecko/.test(ua);
  33  
  34              t.isMac = ua.indexOf('Mac') != -1;
  35  
  36              t.isAir = /adobeair/i.test(ua);
  37  
  38              t.isIDevice = /(iPad|iPhone)/.test(ua);
  39              
  40              t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
  41  
  42              // TinyMCE .NET webcontrol might be setting the values for TinyMCE
  43              if (win.tinyMCEPreInit) {
  44                  t.suffix = tinyMCEPreInit.suffix;
  45                  t.baseURL = tinyMCEPreInit.base;
  46                  t.query = tinyMCEPreInit.query;
  47                  return;
  48              }
  49  
  50              // Get suffix and base
  51              t.suffix = '';
  52  
  53              // If base element found, add that infront of baseURL
  54              nl = d.getElementsByTagName('base');
  55              for (i=0; i<nl.length; i++) {
  56                  v = nl[i].href;
  57                  if (v) {
  58                      // Host only value like http://site.com or http://site.com:8008
  59                      if (/^https?:\/\/[^\/]+$/.test(v))
  60                          v += '/';
  61  
  62                      base = v ? v.match(/.*\//)[0] : ''; // Get only directory
  63                  }
  64              }
  65  
  66  			function getBase(n) {
  67                  if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
  68                      if (/_(src|dev)\.js/g.test(n.src))
  69                          t.suffix = '_src';
  70  
  71                      if ((p = n.src.indexOf('?')) != -1)
  72                          t.query = n.src.substring(p + 1);
  73  
  74                      t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
  75  
  76                      // If path to script is relative and a base href was found add that one infront
  77                      // the src property will always be an absolute one on non IE browsers and IE 8
  78                      // so this logic will basically only be executed on older IE versions
  79                      if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
  80                          t.baseURL = base + t.baseURL;
  81  
  82                      return t.baseURL;
  83                  }
  84  
  85                  return null;
  86              };
  87  
  88              // Check document
  89              nl = d.getElementsByTagName('script');
  90              for (i=0; i<nl.length; i++) {
  91                  if (getBase(nl[i]))
  92                      return;
  93              }
  94  
  95              // Check head
  96              n = d.getElementsByTagName('head')[0];
  97              if (n) {
  98                  nl = n.getElementsByTagName('script');
  99                  for (i=0; i<nl.length; i++) {
 100                      if (getBase(nl[i]))
 101                          return;
 102                  }
 103              }
 104  
 105              return;
 106          },
 107  
 108          is : function(o, t) {
 109              if (!t)
 110                  return o !== undef;
 111  
 112              if (t == 'array' && tinymce.isArray(o))
 113                  return true;
 114  
 115              return typeof(o) == t;
 116          },
 117  
 118          isArray: Array.isArray || function(obj) {
 119              return Object.prototype.toString.call(obj) === "[object Array]";
 120          },
 121  
 122          makeMap : function(items, delim, map) {
 123              var i;
 124  
 125              items = items || [];
 126              delim = delim || ',';
 127  
 128              if (typeof(items) == "string")
 129                  items = items.split(delim);
 130  
 131              map = map || {};
 132  
 133              i = items.length;
 134              while (i--)
 135                  map[items[i]] = {};
 136  
 137              return map;
 138          },
 139  
 140          each : function(o, cb, s) {
 141              var n, l;
 142  
 143              if (!o)
 144                  return 0;
 145  
 146              s = s || o;
 147  
 148              if (o.length !== undef) {
 149                  // Indexed arrays, needed for Safari
 150                  for (n=0, l = o.length; n < l; n++) {
 151                      if (cb.call(s, o[n], n, o) === false)
 152                          return 0;
 153                  }
 154              } else {
 155                  // Hashtables
 156                  for (n in o) {
 157                      if (o.hasOwnProperty(n)) {
 158                          if (cb.call(s, o[n], n, o) === false)
 159                              return 0;
 160                      }
 161                  }
 162              }
 163  
 164              return 1;
 165          },
 166  
 167  
 168          map : function(a, f) {
 169              var o = [];
 170  
 171              tinymce.each(a, function(v) {
 172                  o.push(f(v));
 173              });
 174  
 175              return o;
 176          },
 177  
 178          grep : function(a, f) {
 179              var o = [];
 180  
 181              tinymce.each(a, function(v) {
 182                  if (!f || f(v))
 183                      o.push(v);
 184              });
 185  
 186              return o;
 187          },
 188  
 189          inArray : function(a, v) {
 190              var i, l;
 191  
 192              if (a) {
 193                  for (i = 0, l = a.length; i < l; i++) {
 194                      if (a[i] === v)
 195                          return i;
 196                  }
 197              }
 198  
 199              return -1;
 200          },
 201  
 202          extend : function(obj, ext) {
 203              var i, l, name, args = arguments, value;
 204  
 205              for (i = 1, l = args.length; i < l; i++) {
 206                  ext = args[i];
 207                  for (name in ext) {
 208                      if (ext.hasOwnProperty(name)) {
 209                          value = ext[name];
 210  
 211                          if (value !== undef) {
 212                              obj[name] = value;
 213                          }
 214                      }
 215                  }
 216              }
 217  
 218              return obj;
 219          },
 220  
 221  
 222          trim : function(s) {
 223              return (s ? '' + s : '').replace(whiteSpaceRe, '');
 224          },
 225  
 226          create : function(s, p, root) {
 227              var t = this, sp, ns, cn, scn, c, de = 0;
 228  
 229              // Parse : <prefix> <class>:<super class>
 230              s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
 231              cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
 232  
 233              // Create namespace for new class
 234              ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
 235  
 236              // Class already exists
 237              if (ns[cn])
 238                  return;
 239  
 240              // Make pure static class
 241              if (s[2] == 'static') {
 242                  ns[cn] = p;
 243  
 244                  if (this.onCreate)
 245                      this.onCreate(s[2], s[3], ns[cn]);
 246  
 247                  return;
 248              }
 249  
 250              // Create default constructor
 251              if (!p[cn]) {
 252                  p[cn] = function() {};
 253                  de = 1;
 254              }
 255  
 256              // Add constructor and methods
 257              ns[cn] = p[cn];
 258              t.extend(ns[cn].prototype, p);
 259  
 260              // Extend
 261              if (s[5]) {
 262                  sp = t.resolve(s[5]).prototype;
 263                  scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
 264  
 265                  // Extend constructor
 266                  c = ns[cn];
 267                  if (de) {
 268                      // Add passthrough constructor
 269                      ns[cn] = function() {
 270                          return sp[scn].apply(this, arguments);
 271                      };
 272                  } else {
 273                      // Add inherit constructor
 274                      ns[cn] = function() {
 275                          this.parent = sp[scn];
 276                          return c.apply(this, arguments);
 277                      };
 278                  }
 279                  ns[cn].prototype[cn] = ns[cn];
 280  
 281                  // Add super methods
 282                  t.each(sp, function(f, n) {
 283                      ns[cn].prototype[n] = sp[n];
 284                  });
 285  
 286                  // Add overridden methods
 287                  t.each(p, function(f, n) {
 288                      // Extend methods if needed
 289                      if (sp[n]) {
 290                          ns[cn].prototype[n] = function() {
 291                              this.parent = sp[n];
 292                              return f.apply(this, arguments);
 293                          };
 294                      } else {
 295                          if (n != cn)
 296                              ns[cn].prototype[n] = f;
 297                      }
 298                  });
 299              }
 300  
 301              // Add static methods
 302              t.each(p['static'], function(f, n) {
 303                  ns[cn][n] = f;
 304              });
 305  
 306              if (this.onCreate)
 307                  this.onCreate(s[2], s[3], ns[cn].prototype);
 308          },
 309  
 310          walk : function(o, f, n, s) {
 311              s = s || this;
 312  
 313              if (o) {
 314                  if (n)
 315                      o = o[n];
 316  
 317                  tinymce.each(o, function(o, i) {
 318                      if (f.call(s, o, i, n) === false)
 319                          return false;
 320  
 321                      tinymce.walk(o, f, n, s);
 322                  });
 323              }
 324          },
 325  
 326          createNS : function(n, o) {
 327              var i, v;
 328  
 329              o = o || win;
 330  
 331              n = n.split('.');
 332              for (i=0; i<n.length; i++) {
 333                  v = n[i];
 334  
 335                  if (!o[v])
 336                      o[v] = {};
 337  
 338                  o = o[v];
 339              }
 340  
 341              return o;
 342          },
 343  
 344          resolve : function(n, o) {
 345              var i, l;
 346  
 347              o = o || win;
 348  
 349              n = n.split('.');
 350              for (i = 0, l = n.length; i < l; i++) {
 351                  o = o[n[i]];
 352  
 353                  if (!o)
 354                      break;
 355              }
 356  
 357              return o;
 358          },
 359  
 360          addUnload : function(f, s) {
 361              var t = this, unload;
 362  
 363              unload = function() {
 364                  var li = t.unloads, o, n;
 365  
 366                  if (li) {
 367                      // Call unload handlers
 368                      for (n in li) {
 369                          o = li[n];
 370  
 371                          if (o && o.func)
 372                              o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
 373                      }
 374  
 375                      // Detach unload function
 376                      if (win.detachEvent) {
 377                          win.detachEvent('onbeforeunload', fakeUnload);
 378                          win.detachEvent('onunload', unload);
 379                      } else if (win.removeEventListener)
 380                          win.removeEventListener('unload', unload, false);
 381  
 382                      // Destroy references
 383                      t.unloads = o = li = w = unload = 0;
 384  
 385                      // Run garbarge collector on IE
 386                      if (win.CollectGarbage)
 387                          CollectGarbage();
 388                  }
 389              };
 390  
 391  			function fakeUnload() {
 392                  var d = document;
 393  
 394  				function stop() {
 395                      // Prevent memory leak
 396                      d.detachEvent('onstop', stop);
 397  
 398                      // Call unload handler
 399                      if (unload)
 400                          unload();
 401  
 402                      d = 0;
 403                  };
 404  
 405                  // Is there things still loading, then do some magic
 406                  if (d.readyState == 'interactive') {
 407                      // Fire unload when the currently loading page is stopped
 408                      if (d)
 409                          d.attachEvent('onstop', stop);
 410  
 411                      // Remove onstop listener after a while to prevent the unload function
 412                      // to execute if the user presses cancel in an onbeforeunload
 413                      // confirm dialog and then presses the browser stop button
 414                      win.setTimeout(function() {
 415                          if (d)
 416                              d.detachEvent('onstop', stop);
 417                      }, 0);
 418                  }
 419              };
 420  
 421              f = {func : f, scope : s || this};
 422  
 423              if (!t.unloads) {
 424                  // Attach unload handler
 425                  if (win.attachEvent) {
 426                      win.attachEvent('onunload', unload);
 427                      win.attachEvent('onbeforeunload', fakeUnload);
 428                  } else if (win.addEventListener)
 429                      win.addEventListener('unload', unload, false);
 430  
 431                  // Setup initial unload handler array
 432                  t.unloads = [f];
 433              } else
 434                  t.unloads.push(f);
 435  
 436              return f;
 437          },
 438  
 439          removeUnload : function(f) {
 440              var u = this.unloads, r = null;
 441  
 442              tinymce.each(u, function(o, i) {
 443                  if (o && o.func == f) {
 444                      u.splice(i, 1);
 445                      r = f;
 446                      return false;
 447                  }
 448              });
 449  
 450              return r;
 451          },
 452  
 453          explode : function(s, d) {
 454              if (!s || tinymce.is(s, 'array')) {
 455                  return s;
 456              }
 457  
 458              return tinymce.map(s.split(d || ','), tinymce.trim);
 459          },
 460  
 461          _addVer : function(u) {
 462              var v;
 463  
 464              if (!this.query)
 465                  return u;
 466  
 467              v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
 468  
 469              if (u.indexOf('#') == -1)
 470                  return u + v;
 471  
 472              return u.replace('#', v + '#');
 473          },
 474  
 475          // Fix function for IE 9 where regexps isn't working correctly
 476          // Todo: remove me once MS fixes the bug
 477          _replace : function(find, replace, str) {
 478              // On IE9 we have to fake $x replacement
 479              if (isRegExpBroken) {
 480                  return str.replace(find, function() {
 481                      var val = replace, args = arguments, i;
 482  
 483                      for (i = 0; i < args.length - 2; i++) {
 484                          if (args[i] === undef) {
 485                              val = val.replace(new RegExp('\\$' + i, 'g'), '');
 486                          } else {
 487                              val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
 488                          }
 489                      }
 490  
 491                      return val;
 492                  });
 493              }
 494  
 495              return str.replace(find, replace);
 496          }
 497  
 498          };
 499  
 500      // Initialize the API
 501      tinymce._init();
 502  
 503      // Expose tinymce namespace to the global namespace (window)
 504      win.tinymce = win.tinyMCE = tinymce;
 505  
 506      // Describe the different namespaces
 507  
 508      })(window);
 509  tinymce.create('tinymce.util.Dispatcher', {
 510      scope : null,
 511      listeners : null,
 512      inDispatch: false,
 513  
 514      Dispatcher : function(scope) {
 515          this.scope = scope || this;
 516          this.listeners = [];
 517      },
 518  
 519      add : function(callback, scope) {
 520          this.listeners.push({cb : callback, scope : scope || this.scope});
 521  
 522          return callback;
 523      },
 524  
 525      addToTop : function(callback, scope) {
 526          var self = this, listener = {cb : callback, scope : scope || self.scope};
 527  
 528          // Create new listeners if addToTop is executed in a dispatch loop
 529          if (self.inDispatch) {
 530              self.listeners = [listener].concat(self.listeners);
 531          } else {
 532              self.listeners.unshift(listener);
 533          }
 534  
 535          return callback;
 536      },
 537  
 538      remove : function(callback) {
 539          var listeners = this.listeners, output = null;
 540  
 541          tinymce.each(listeners, function(listener, i) {
 542              if (callback == listener.cb) {
 543                  output = listener;
 544                  listeners.splice(i, 1);
 545                  return false;
 546              }
 547          });
 548  
 549          return output;
 550      },
 551  
 552      dispatch : function() {
 553          var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
 554  
 555          self.inDispatch = true;
 556          
 557          // Needs to be a real loop since the listener count might change while looping
 558          // And this is also more efficient
 559          for (i = 0; i < listeners.length; i++) {
 560              listener = listeners[i];
 561              returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
 562  
 563              if (returnValue === false)
 564                  break;
 565          }
 566  
 567          self.inDispatch = false;
 568  
 569          return returnValue;
 570      }
 571  
 572      });
 573  (function() {
 574      var each = tinymce.each;
 575  
 576      tinymce.create('tinymce.util.URI', {
 577          URI : function(u, s) {
 578              var t = this, o, a, b, base_url;
 579  
 580              // Trim whitespace
 581              u = tinymce.trim(u);
 582  
 583              // Default settings
 584              s = t.settings = s || {};
 585  
 586              // Strange app protocol that isn't http/https or local anchor
 587              // For example: mailto,skype,tel etc.
 588              if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
 589                  t.source = u;
 590                  return;
 591              }
 592  
 593              // Absolute path with no host, fake host and protocol
 594              if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
 595                  u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
 596  
 597              // Relative path http:// or protocol relative //path
 598              if (!/^[\w\-]*:?\/\//.test(u)) {
 599                  base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
 600                  u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
 601              }
 602  
 603              // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
 604              u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
 605              u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
 606              each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
 607                  var s = u[i];
 608  
 609                  // Zope 3 workaround, they use @@something
 610                  if (s)
 611                      s = s.replace(/\(mce_at\)/g, '@@');
 612  
 613                  t[v] = s;
 614              });
 615  
 616              b = s.base_uri;
 617              if (b) {
 618                  if (!t.protocol)
 619                      t.protocol = b.protocol;
 620  
 621                  if (!t.userInfo)
 622                      t.userInfo = b.userInfo;
 623  
 624                  if (!t.port && t.host === 'mce_host')
 625                      t.port = b.port;
 626  
 627                  if (!t.host || t.host === 'mce_host')
 628                      t.host = b.host;
 629  
 630                  t.source = '';
 631              }
 632  
 633              //t.path = t.path || '/';
 634          },
 635  
 636          setPath : function(p) {
 637              var t = this;
 638  
 639              p = /^(.*?)\/?(\w+)?$/.exec(p);
 640  
 641              // Update path parts
 642              t.path = p[0];
 643              t.directory = p[1];
 644              t.file = p[2];
 645  
 646              // Rebuild source
 647              t.source = '';
 648              t.getURI();
 649          },
 650  
 651          toRelative : function(u) {
 652              var t = this, o;
 653  
 654              if (u === "./")
 655                  return u;
 656  
 657              u = new tinymce.util.URI(u, {base_uri : t});
 658  
 659              // Not on same domain/port or protocol
 660              if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
 661                  return u.getURI();
 662  
 663              var tu = t.getURI(), uu = u.getURI();
 664              
 665              // Allow usage of the base_uri when relative_urls = true
 666              if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
 667                  return tu;
 668  
 669              o = t.toRelPath(t.path, u.path);
 670  
 671              // Add query
 672              if (u.query)
 673                  o += '?' + u.query;
 674  
 675              // Add anchor
 676              if (u.anchor)
 677                  o += '#' + u.anchor;
 678  
 679              return o;
 680          },
 681      
 682          toAbsolute : function(u, nh) {
 683              u = new tinymce.util.URI(u, {base_uri : this});
 684  
 685              return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
 686          },
 687  
 688          toRelPath : function(base, path) {
 689              var items, bp = 0, out = '', i, l;
 690  
 691              // Split the paths
 692              base = base.substring(0, base.lastIndexOf('/'));
 693              base = base.split('/');
 694              items = path.split('/');
 695  
 696              if (base.length >= items.length) {
 697                  for (i = 0, l = base.length; i < l; i++) {
 698                      if (i >= items.length || base[i] != items[i]) {
 699                          bp = i + 1;
 700                          break;
 701                      }
 702                  }
 703              }
 704  
 705              if (base.length < items.length) {
 706                  for (i = 0, l = items.length; i < l; i++) {
 707                      if (i >= base.length || base[i] != items[i]) {
 708                          bp = i + 1;
 709                          break;
 710                      }
 711                  }
 712              }
 713  
 714              if (bp === 1)
 715                  return path;
 716  
 717              for (i = 0, l = base.length - (bp - 1); i < l; i++)
 718                  out += "../";
 719  
 720              for (i = bp - 1, l = items.length; i < l; i++) {
 721                  if (i != bp - 1)
 722                      out += "/" + items[i];
 723                  else
 724                      out += items[i];
 725              }
 726  
 727              return out;
 728          },
 729  
 730          toAbsPath : function(base, path) {
 731              var i, nb = 0, o = [], tr, outPath;
 732  
 733              // Split paths
 734              tr = /\/$/.test(path) ? '/' : '';
 735              base = base.split('/');
 736              path = path.split('/');
 737  
 738              // Remove empty chunks
 739              each(base, function(k) {
 740                  if (k)
 741                      o.push(k);
 742              });
 743  
 744              base = o;
 745  
 746              // Merge relURLParts chunks
 747              for (i = path.length - 1, o = []; i >= 0; i--) {
 748                  // Ignore empty or .
 749                  if (path[i].length === 0 || path[i] === ".")
 750                      continue;
 751  
 752                  // Is parent
 753                  if (path[i] === '..') {
 754                      nb++;
 755                      continue;
 756                  }
 757  
 758                  // Move up
 759                  if (nb > 0) {
 760                      nb--;
 761                      continue;
 762                  }
 763  
 764                  o.push(path[i]);
 765              }
 766  
 767              i = base.length - nb;
 768  
 769              // If /a/b/c or /
 770              if (i <= 0)
 771                  outPath = o.reverse().join('/');
 772              else
 773                  outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
 774  
 775              // Add front / if it's needed
 776              if (outPath.indexOf('/') !== 0)
 777                  outPath = '/' + outPath;
 778  
 779              // Add traling / if it's needed
 780              if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
 781                  outPath += tr;
 782  
 783              return outPath;
 784          },
 785  
 786          getURI : function(nh) {
 787              var s, t = this;
 788  
 789              // Rebuild source
 790              if (!t.source || nh) {
 791                  s = '';
 792  
 793                  if (!nh) {
 794                      if (t.protocol)
 795                          s += t.protocol + '://';
 796  
 797                      if (t.userInfo)
 798                          s += t.userInfo + '@';
 799  
 800                      if (t.host)
 801                          s += t.host;
 802  
 803                      if (t.port)
 804                          s += ':' + t.port;
 805                  }
 806  
 807                  if (t.path)
 808                      s += t.path;
 809  
 810                  if (t.query)
 811                      s += '?' + t.query;
 812  
 813                  if (t.anchor)
 814                      s += '#' + t.anchor;
 815  
 816                  t.source = s;
 817              }
 818  
 819              return t.source;
 820          }
 821      });
 822  })();
 823  (function() {
 824      var each = tinymce.each;
 825  
 826      tinymce.create('static tinymce.util.Cookie', {
 827          getHash : function(n) {
 828              var v = this.get(n), h;
 829  
 830              if (v) {
 831                  each(v.split('&'), function(v) {
 832                      v = v.split('=');
 833                      h = h || {};
 834                      h[unescape(v[0])] = unescape(v[1]);
 835                  });
 836              }
 837  
 838              return h;
 839          },
 840  
 841          setHash : function(n, v, e, p, d, s) {
 842              var o = '';
 843  
 844              each(v, function(v, k) {
 845                  o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
 846              });
 847  
 848              this.set(n, o, e, p, d, s);
 849          },
 850  
 851          get : function(n) {
 852              var c = document.cookie, e, p = n + "=", b;
 853  
 854              // Strict mode
 855              if (!c)
 856                  return;
 857  
 858              b = c.indexOf("; " + p);
 859  
 860              if (b == -1) {
 861                  b = c.indexOf(p);
 862  
 863                  if (b !== 0)
 864                      return null;
 865              } else
 866                  b += 2;
 867  
 868              e = c.indexOf(";", b);
 869  
 870              if (e == -1)
 871                  e = c.length;
 872  
 873              return unescape(c.substring(b + p.length, e));
 874          },
 875  
 876          set : function(n, v, e, p, d, s) {
 877              document.cookie = n + "=" + escape(v) +
 878                  ((e) ? "; expires=" + e.toGMTString() : "") +
 879                  ((p) ? "; path=" + escape(p) : "") +
 880                  ((d) ? "; domain=" + d : "") +
 881                  ((s) ? "; secure" : "");
 882          },
 883  
 884          remove : function(name, path, domain) {
 885              var date = new Date();
 886  
 887              date.setTime(date.getTime() - 1000);
 888  
 889              this.set(name, '', date, path, domain);
 890          }
 891      });
 892  })();
 893  (function() {
 894  	function serialize(o, quote) {
 895          var i, v, t, name;
 896  
 897          quote = quote || '"';
 898  
 899          if (o == null)
 900              return 'null';
 901  
 902          t = typeof o;
 903  
 904          if (t == 'string') {
 905              v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
 906  
 907              return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
 908                  // Make sure single quotes never get encoded inside double quotes for JSON compatibility
 909                  if (quote === '"' && a === "'")
 910                      return a;
 911  
 912                  i = v.indexOf(b);
 913  
 914                  if (i + 1)
 915                      return '\\' + v.charAt(i + 1);
 916  
 917                  a = b.charCodeAt().toString(16);
 918  
 919                  return '\\u' + '0000'.substring(a.length) + a;
 920              }) + quote;
 921          }
 922  
 923          if (t == 'object') {
 924              if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {
 925                      for (i=0, v = '['; i<o.length; i++)
 926                          v += (i > 0 ? ',' : '') + serialize(o[i], quote);
 927  
 928                      return v + ']';
 929                  }
 930  
 931                  v = '{';
 932  
 933                  for (name in o) {
 934                      if (o.hasOwnProperty(name)) {
 935                          v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
 936                      }
 937                  }
 938  
 939                  return v + '}';
 940          }
 941  
 942          return '' + o;
 943      };
 944  
 945      tinymce.util.JSON = {
 946          serialize: serialize,
 947  
 948          parse: function(s) {
 949              try {
 950                  return eval('(' + s + ')');
 951              } catch (ex) {
 952                  // Ignore
 953              }
 954          }
 955  
 956          };
 957  })();
 958  tinymce.create('static tinymce.util.XHR', {
 959      send : function(o) {
 960          var x, t, w = window, c = 0;
 961  
 962  		function ready() {
 963              if (!o.async || x.readyState == 4 || c++ > 10000) {
 964                  if (o.success && c < 10000 && x.status == 200)
 965                      o.success.call(o.success_scope, '' + x.responseText, x, o);
 966                  else if (o.error)
 967                      o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
 968  
 969                  x = null;
 970              } else
 971                  w.setTimeout(ready, 10);
 972          };
 973  
 974          // Default settings
 975          o.scope = o.scope || this;
 976          o.success_scope = o.success_scope || o.scope;
 977          o.error_scope = o.error_scope || o.scope;
 978          o.async = o.async === false ? false : true;
 979          o.data = o.data || '';
 980  
 981  		function get(s) {
 982              x = 0;
 983  
 984              try {
 985                  x = new ActiveXObject(s);
 986              } catch (ex) {
 987              }
 988  
 989              return x;
 990          };
 991  
 992          x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
 993  
 994          if (x) {
 995              if (x.overrideMimeType)
 996                  x.overrideMimeType(o.content_type);
 997  
 998              x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
 999  
1000              if (o.content_type)
1001                  x.setRequestHeader('Content-Type', o.content_type);
1002  
1003              x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1004  
1005              x.send(o.data);
1006  
1007              // Syncronous request
1008              if (!o.async)
1009                  return ready();
1010  
1011              // Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1012              t = w.setTimeout(ready, 10);
1013          }
1014      }
1015  });
1016  (function() {
1017      var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1018  
1019      tinymce.create('tinymce.util.JSONRequest', {
1020          JSONRequest : function(s) {
1021              this.settings = extend({
1022              }, s);
1023              this.count = 0;
1024          },
1025  
1026          send : function(o) {
1027              var ecb = o.error, scb = o.success;
1028  
1029              o = extend(this.settings, o);
1030  
1031              o.success = function(c, x) {
1032                  c = JSON.parse(c);
1033  
1034                  if (typeof(c) == 'undefined') {
1035                      c = {
1036                          error : 'JSON Parse error.'
1037                      };
1038                  }
1039  
1040                  if (c.error)
1041                      ecb.call(o.error_scope || o.scope, c.error, x);
1042                  else
1043                      scb.call(o.success_scope || o.scope, c.result);
1044              };
1045  
1046              o.error = function(ty, x) {
1047                  if (ecb)
1048                      ecb.call(o.error_scope || o.scope, ty, x);
1049              };
1050  
1051              o.data = JSON.serialize({
1052                  id : o.id || 'c' + (this.count++),
1053                  method : o.method,
1054                  params : o.params
1055              });
1056  
1057              // JSON content type for Ruby on rails. Bug: #1883287
1058              o.content_type = 'application/json';
1059  
1060              XHR.send(o);
1061          },
1062  
1063          'static' : {
1064              sendRPC : function(o) {
1065                  return new tinymce.util.JSONRequest().send(o);
1066              }
1067          }
1068      });
1069  }());
1070  (function(tinymce){
1071      tinymce.VK = {
1072          BACKSPACE: 8,
1073          DELETE: 46,
1074          DOWN: 40,
1075          ENTER: 13,
1076          LEFT: 37,
1077          RIGHT: 39,
1078          SPACEBAR: 32,
1079          TAB: 9,
1080          UP: 38,
1081  
1082          modifierPressed: function (e) {
1083              return e.shiftKey || e.ctrlKey || e.altKey;
1084          },
1085  
1086          metaKeyPressed: function(e) {
1087              // Check if ctrl or meta key is pressed also check if alt is false for Polish users
1088              return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;
1089          }
1090      };
1091  })(tinymce);
1092  tinymce.util.Quirks = function(editor) {
1093      var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,
1094          settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each;
1095  
1096  	function setEditorCommandState(cmd, state) {
1097          try {
1098              editor.getDoc().execCommand(cmd, false, state);
1099          } catch (ex) {
1100              // Ignore
1101          }
1102      }
1103  
1104  	function getDocumentMode() {
1105          var documentMode = editor.getDoc().documentMode;
1106  
1107          return documentMode ? documentMode : 6;
1108      };
1109  
1110  	function isDefaultPrevented(e) {
1111          return e.isDefaultPrevented();
1112      };
1113  
1114  	function cleanupStylesWhenDeleting() {
1115  		function removeMergedFormatSpans(isDelete) {
1116              var rng, blockElm, wrapperElm, bookmark, container, offset, elm;
1117  
1118  			function isAtStartOrEndOfElm() {
1119                  if (container.nodeType == 3) {
1120                      if (isDelete && offset == container.length) {
1121                          return true;
1122                      }
1123  
1124                      if (!isDelete && offset === 0) {
1125                          return true;
1126                      }
1127                  }
1128              }
1129  
1130              rng = selection.getRng();
1131              var tmpRng = [rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset];
1132  
1133              if (!rng.collapsed) {
1134                  isDelete = true;
1135              }
1136  
1137              container = rng[(isDelete ? 'start' : 'end') + 'Container'];
1138              offset = rng[(isDelete ? 'start' : 'end') + 'Offset'];
1139  
1140              if (container.nodeType == 3) {
1141                  blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1142  
1143                  // On delete clone the root span of the next block element
1144                  if (isDelete) {
1145                      blockElm = dom.getNext(blockElm, dom.isBlock);
1146                  }
1147  
1148                  if (blockElm && (isAtStartOrEndOfElm() || !rng.collapsed)) {
1149                      // Wrap children of block in a EM and let WebKit stick is
1150                      // runtime styles junk into that EM
1151                      wrapperElm = dom.create('em', {'id': '__mceDel'});
1152  
1153                      each(tinymce.grep(blockElm.childNodes), function(node) {
1154                          wrapperElm.appendChild(node);
1155                      });
1156  
1157                      blockElm.appendChild(wrapperElm);
1158                  }
1159              }
1160  
1161              // Do the backspace/delete action
1162              rng = dom.createRng();
1163              rng.setStart(tmpRng[0], tmpRng[1]);
1164              rng.setEnd(tmpRng[2], tmpRng[3]);
1165              selection.setRng(rng);
1166              editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1167  
1168              // Remove temp wrapper element
1169              if (wrapperElm) {
1170                  bookmark = selection.getBookmark();
1171  
1172                  while (elm = dom.get('__mceDel')) {
1173                      dom.remove(elm, true);
1174                  }
1175  
1176                  selection.moveToBookmark(bookmark);
1177              }
1178          }
1179  
1180          editor.onKeyDown.add(function(editor, e) {
1181              var isDelete;
1182  
1183              isDelete = e.keyCode == DELETE;
1184              if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1185                  e.preventDefault();
1186                  removeMergedFormatSpans(isDelete);
1187              }
1188          });
1189  
1190          editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1191      };
1192      
1193  	function emptyEditorWhenDeleting() {
1194  		function serializeRng(rng) {
1195              var body = dom.create("body");
1196              var contents = rng.cloneContents();
1197              body.appendChild(contents);
1198              return selection.serializer.serialize(body, {format: 'html'});
1199          }
1200  
1201  		function allContentsSelected(rng) {
1202              var selection = serializeRng(rng);
1203  
1204              var allRng = dom.createRng();
1205              allRng.selectNode(editor.getBody());
1206  
1207              var allSelection = serializeRng(allRng);
1208              return selection === allSelection;
1209          }
1210  
1211          editor.onKeyDown.add(function(editor, e) {
1212              var keyCode = e.keyCode, isCollapsed;
1213  
1214              // Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1215              if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {
1216                  isCollapsed = editor.selection.isCollapsed();
1217  
1218                  // Selection is collapsed but the editor isn't empty
1219                  if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1220                      return;
1221                  }
1222  
1223                  // IE deletes all contents correctly when everything is selected
1224                  if (tinymce.isIE && !isCollapsed) {
1225                      return;
1226                  }
1227  
1228                  // Selection isn't collapsed but not all the contents is selected
1229                  if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1230                      return;
1231                  }
1232  
1233                  // Manually empty the editor
1234                  editor.setContent('');
1235                  editor.selection.setCursorLocation(editor.getBody(), 0);
1236                  editor.nodeChanged();
1237              }
1238          });
1239      };
1240  
1241  	function selectAll() {
1242          editor.onKeyDown.add(function(editor, e) {
1243              if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {
1244                  e.preventDefault();
1245                  editor.execCommand('SelectAll');
1246              }
1247          });
1248      };
1249  
1250  	function inputMethodFocus() {
1251          if (!editor.settings.content_editable) {
1252              // Case 1 IME doesn't initialize if you focus the document
1253              dom.bind(editor.getDoc(), 'focusin', function(e) {
1254                  selection.setRng(selection.getRng());
1255              });
1256  
1257              // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1258              dom.bind(editor.getDoc(), 'mousedown', function(e) {
1259                  if (e.target == editor.getDoc().documentElement) {
1260                      editor.getWin().focus();
1261                      selection.setRng(selection.getRng());
1262                  }
1263              });
1264          }
1265      };
1266  
1267  	function removeHrOnBackspace() {
1268          editor.onKeyDown.add(function(editor, e) {
1269              if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1270                  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1271                      var node = selection.getNode();
1272                      var previousSibling = node.previousSibling;
1273  
1274                      if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1275                          dom.remove(previousSibling);
1276                          tinymce.dom.Event.cancel(e);
1277                      }
1278                  }
1279              }
1280          })
1281      }
1282  
1283  	function focusBody() {
1284          // Fix for a focus bug in FF 3.x where the body element
1285          // wouldn't get proper focus if the user clicked on the HTML element
1286          if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1287              editor.onMouseDown.add(function(editor, e) {
1288                  if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {
1289                      var body = editor.getBody();
1290  
1291                      // Blur the body it's focused but not correctly focused
1292                      body.blur();
1293  
1294                      // Refocus the body after a little while
1295                      setTimeout(function() {
1296                          body.focus();
1297                      }, 0);
1298                  }
1299              });
1300          }
1301      };
1302  
1303  	function selectControlElements() {
1304          editor.onClick.add(function(editor, e) {
1305              e = e.target;
1306  
1307              // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1308              // WebKit can't even do simple things like selecting an image
1309              // Needs tobe the setBaseAndExtend or it will fail to select floated images
1310              if (/^(IMG|HR)$/.test(e.nodeName)) {
1311                  selection.getSel().setBaseAndExtent(e, 0, e, 1);
1312              }
1313  
1314              if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1315                  selection.select(e);
1316              }
1317  
1318              editor.nodeChanged();
1319          });
1320      };
1321  
1322  	function removeStylesWhenDeletingAccrossBlockElements() {
1323  		function getAttributeApplyFunction() {
1324              var template = dom.getAttribs(selection.getStart().cloneNode(false));
1325  
1326              return function() {
1327                  var target = selection.getStart();
1328  
1329                  if (target !== editor.getBody()) {
1330                      dom.setAttrib(target, "style", null);
1331  
1332                      each(template, function(attr) {
1333                          target.setAttributeNode(attr.cloneNode(true));
1334                      });
1335                  }
1336              };
1337          }
1338  
1339  		function isSelectionAcrossElements() {
1340              return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);
1341          }
1342  
1343  		function blockEvent(editor, e) {
1344              e.preventDefault();
1345              return false;
1346          }
1347  
1348          editor.onKeyPress.add(function(editor, e) {
1349              var applyAttributes;
1350  
1351              if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1352                  applyAttributes = getAttributeApplyFunction();
1353                  editor.getDoc().execCommand('delete', false, null);
1354                  applyAttributes();
1355                  e.preventDefault();
1356                  return false;
1357              }
1358          });
1359  
1360          dom.bind(editor.getDoc(), 'cut', function(e) {
1361              var applyAttributes;
1362  
1363              if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
1364                  applyAttributes = getAttributeApplyFunction();
1365                  editor.onKeyUp.addToTop(blockEvent);
1366  
1367                  setTimeout(function() {
1368                      applyAttributes();
1369                      editor.onKeyUp.remove(blockEvent);
1370                  }, 0);
1371              }
1372          });
1373      }
1374  
1375  	function selectionChangeNodeChanged() {
1376          var lastRng, selectionTimer;
1377  
1378          dom.bind(editor.getDoc(), 'selectionchange', function() {
1379              if (selectionTimer) {
1380                  clearTimeout(selectionTimer);
1381                  selectionTimer = 0;
1382              }
1383  
1384              selectionTimer = window.setTimeout(function() {
1385                  var rng = selection.getRng();
1386  
1387                  // Compare the ranges to see if it was a real change or not
1388                  if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1389                      editor.nodeChanged();
1390                      lastRng = rng;
1391                  }
1392              }, 50);
1393          });
1394      }
1395  
1396  	function ensureBodyHasRoleApplication() {
1397          document.body.setAttribute("role", "application");
1398      }
1399  
1400  	function disableBackspaceIntoATable() {
1401          editor.onKeyDown.add(function(editor, e) {
1402              if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
1403                  if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1404                      var previousSibling = selection.getNode().previousSibling;
1405                      if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1406                          return tinymce.dom.Event.cancel(e);
1407                      }
1408                  }
1409              }
1410          })
1411      }
1412  
1413  	function addNewLinesBeforeBrInPre() {
1414          // IE8+ rendering mode does the right thing with BR in PRE
1415          if (getDocumentMode() > 7) {
1416              return;
1417          }
1418  
1419           // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1420           // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1421          setEditorCommandState('RespectVisibilityInDesign', true);
1422          editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1423          dom.addClass(editor.getBody(), 'mceHideBrInPre');
1424  
1425          // Adds a \n before all BR elements in PRE to get them visual
1426          parser.addNodeFilter('pre', function(nodes, name) {
1427              var i = nodes.length, brNodes, j, brElm, sibling;
1428  
1429              while (i--) {
1430                  brNodes = nodes[i].getAll('br');
1431                  j = brNodes.length;
1432                  while (j--) {
1433                      brElm = brNodes[j];
1434  
1435                      // Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1436                      sibling = brElm.prev;
1437                      if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1438                          sibling.value += '\n';
1439                      } else {
1440                          brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1441                      }
1442                  }
1443              }
1444          });
1445  
1446          // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1447          serializer.addNodeFilter('pre', function(nodes, name) {
1448              var i = nodes.length, brNodes, j, brElm, sibling;
1449  
1450              while (i--) {
1451                  brNodes = nodes[i].getAll('br');
1452                  j = brNodes.length;
1453                  while (j--) {
1454                      brElm = brNodes[j];
1455                      sibling = brElm.prev;
1456                      if (sibling && sibling.type == 3) {
1457                          sibling.value = sibling.value.replace(/\r?\n$/, '');
1458                      }
1459                  }
1460              }
1461          });
1462      }
1463  
1464      function removePreSerializedStylesWhenSelectingControls() {
1465          dom.bind(editor.getBody(), 'mouseup', function(e) {
1466              var value, node = selection.getNode();
1467  
1468              // Moved styles to attributes on IMG eements
1469              if (node.nodeName == 'IMG') {
1470                  // Convert style width to width attribute
1471                  if (value = dom.getStyle(node, 'width')) {
1472                      dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1473                      dom.setStyle(node, 'width', '');
1474                  }
1475  
1476                  // Convert style height to height attribute
1477                  if (value = dom.getStyle(node, 'height')) {
1478                      dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1479                      dom.setStyle(node, 'height', '');
1480                  }
1481              }
1482          });
1483      }
1484  
1485  	function keepInlineElementOnDeleteBackspace() {
1486          editor.onKeyDown.add(function(editor, e) {
1487              var isDelete, rng, container, offset, brElm, sibling, collapsed;
1488  
1489              isDelete = e.keyCode == DELETE;
1490              if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1491                  rng = selection.getRng();
1492                  container = rng.startContainer;
1493                  offset = rng.startOffset;
1494                  collapsed = rng.collapsed;
1495  
1496                  // Override delete if the start container is a text node and is at the beginning of text or
1497                  // just before/after the last character to be deleted in collapsed mode
1498                  if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1499                      // Edge case when deleting <p><b><img> |x</b></p>
1500                      sibling = container.previousSibling;
1501                      if (sibling && sibling.nodeName == "IMG") {
1502                          return;
1503                      }
1504  
1505                      nonEmptyElements = editor.schema.getNonEmptyElements();
1506  
1507                      // Prevent default logic since it's broken
1508                      e.preventDefault();
1509  
1510                      // Insert a BR before the text node this will prevent the containing element from being deleted/converted
1511                      brElm = dom.create('br', {id: '__tmp'});
1512                      container.parentNode.insertBefore(brElm, container);
1513  
1514                      // Do the browser delete
1515                      editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1516  
1517                      // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1518                      container = selection.getRng().startContainer;
1519                      sibling = container.previousSibling;
1520                      if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1521                          dom.remove(sibling);
1522                      }
1523  
1524                      // Remove the temp element we inserted
1525                      dom.remove('__tmp');
1526                  }
1527              }
1528          });
1529      }
1530  
1531  	function removeBlockQuoteOnBackSpace() {
1532          // Add block quote deletion handler
1533          editor.onKeyDown.add(function(editor, e) {
1534              var rng, container, offset, root, parent;
1535  
1536              if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {
1537                  return;
1538              }
1539  
1540              rng = selection.getRng();
1541              container = rng.startContainer;
1542              offset = rng.startOffset;
1543              root = dom.getRoot();
1544              parent = container;
1545  
1546              if (!rng.collapsed || offset !== 0) {
1547                  return;
1548              }
1549  
1550              while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1551                  parent = parent.parentNode;
1552              }
1553  
1554              // Is the cursor at the beginning of a blockquote?
1555              if (parent.tagName === 'BLOCKQUOTE') {
1556                  // Remove the blockquote
1557                  editor.formatter.toggle('blockquote', null, parent);
1558  
1559                  // Move the caret to the beginning of container
1560                  rng = dom.createRng();
1561                  rng.setStart(container, 0);
1562                  rng.setEnd(container, 0);
1563                  selection.setRng(rng);
1564              }
1565          });
1566      };
1567  
1568  	function setGeckoEditingOptions() {
1569  		function setOpts() {
1570              editor._refreshContentEditable();
1571  
1572              setEditorCommandState("StyleWithCSS", false);
1573              setEditorCommandState("enableInlineTableEditing", false);
1574  
1575              if (!settings.object_resizing) {
1576                  setEditorCommandState("enableObjectResizing", false);
1577              }
1578          };
1579  
1580          if (!settings.readonly) {
1581              editor.onBeforeExecCommand.add(setOpts);
1582              editor.onMouseDown.add(setOpts);
1583          }
1584      };
1585  
1586  	function addBrAfterLastLinks() {
1587  		function fixLinks(editor, o) {
1588              each(dom.select('a'), function(node) {
1589                  var parentNode = node.parentNode, root = dom.getRoot();
1590  
1591                  if (parentNode.lastChild === node) {
1592                      while (parentNode && !dom.isBlock(parentNode)) {
1593                          if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1594                              return;
1595                          }
1596  
1597                          parentNode = parentNode.parentNode;
1598                      }
1599  
1600                      dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1601                  }
1602              });
1603          };
1604  
1605          editor.onExecCommand.add(function(editor, cmd) {
1606              if (cmd === 'CreateLink') {
1607                  fixLinks(editor);
1608              }
1609          });
1610  
1611          editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1612      };
1613  
1614  	function setDefaultBlockType() {
1615          if (settings.forced_root_block) {
1616              editor.onInit.add(function() {
1617                  setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1618              });
1619          }
1620      }
1621  
1622  	function removeGhostSelection() {
1623  		function repaint(sender, args) {
1624              if (!sender || !args.initial) {
1625                  editor.execCommand('mceRepaint');
1626              }
1627          };
1628  
1629          editor.onUndo.add(repaint);
1630          editor.onRedo.add(repaint);
1631          editor.onSetContent.add(repaint);
1632      };
1633  
1634  	function deleteControlItemOnBackSpace() {
1635          editor.onKeyDown.add(function(editor, e) {
1636              var rng;
1637  
1638              if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {
1639                  rng = editor.getDoc().selection.createRange();
1640                  if (rng && rng.item) {
1641                      e.preventDefault();
1642                      editor.undoManager.beforeChange();
1643                      dom.remove(rng.item(0));
1644                      editor.undoManager.add();
1645                  }
1646              }
1647          });
1648      };
1649  
1650  	function renderEmptyBlocksFix() {
1651          var emptyBlocksCSS;
1652  
1653          // IE10+
1654          if (getDocumentMode() >= 10) {
1655              emptyBlocksCSS = '';
1656              each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1657                  emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1658              });
1659  
1660              editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1661          }
1662      };
1663  
1664  	function fakeImageResize() {
1665          var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,
1666              resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();
1667  
1668          if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1669              return;
1670          }
1671  
1672          // Try disabling object resizing if WebKit implements resizing in the future
1673          setEditorCommandState("enableObjectResizing", false);
1674  
1675          // Details about each resize handle how to scale etc
1676          resizeHandles = {
1677              // Name: x multiplier, y multiplier, delta size x, delta size y
1678              n: [.5, 0, 0, -1],
1679              e: [1, .5, 1, 0],
1680              s: [.5, 1, 0, 1],
1681              w: [0, .5, -1, 0],
1682              nw: [0, 0, -1, -1],
1683              ne: [1, 0, 1, -1],
1684              se: [1, 1, 1, 1],
1685              sw : [0, 1, -1, 1]
1686          };
1687  
1688  		function resizeElement(e) {
1689              var deltaX, deltaY;
1690  
1691              // Calc new width/height
1692              deltaX = e.screenX - startX;
1693              deltaY = e.screenY - startY;
1694  
1695              // Calc new size
1696              width = deltaX * selectedHandle[2] + startW;
1697              height = deltaY * selectedHandle[3] + startH;
1698  
1699              // Never scale down lower than 5 pixels
1700              width = width < 5 ? 5 : width;
1701              height = height < 5 ? 5 : height;
1702  
1703              // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image
1704              if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {
1705                  width = Math.round(height / ratio);
1706                  height = Math.round(width * ratio);
1707              }
1708  
1709              // Update ghost size
1710              dom.setStyles(selectedElmGhost, {
1711                  width: width,
1712                  height: height
1713              });
1714  
1715              // Update ghost X position if needed
1716              if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
1717                  dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
1718              }
1719  
1720              // Update ghost Y position if needed
1721              if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
1722                  dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
1723              }
1724          }
1725  
1726  		function endResize() {
1727  			function setSizeProp(name, value) {
1728                  if (value) {
1729                      // Resize by using style or attribute
1730                      if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {
1731                          dom.setStyle(selectedElm, name, value);
1732                      } else {
1733                          dom.setAttrib(selectedElm, name, value);
1734                      }
1735                  }
1736              }
1737  
1738              // Set width/height properties
1739              setSizeProp('width', width);
1740              setSizeProp('height', height);
1741  
1742              dom.unbind(editableDoc, 'mousemove', resizeElement);
1743              dom.unbind(editableDoc, 'mouseup', endResize);
1744  
1745              if (rootDocument != editableDoc) {
1746                  dom.unbind(rootDocument, 'mousemove', resizeElement);
1747                  dom.unbind(rootDocument, 'mouseup', endResize);
1748              }
1749  
1750              // Remove ghost and update resize handle positions
1751              dom.remove(selectedElmGhost);
1752              showResizeRect(selectedElm);
1753          }
1754  
1755  		function showResizeRect(targetElm) {
1756              var position, targetWidth, targetHeight;
1757  
1758              hideResizeRect();
1759  
1760              // Get position and size of target
1761              position = dom.getPos(targetElm);
1762              selectedElmX = position.x;
1763              selectedElmY = position.y;
1764              targetWidth = targetElm.offsetWidth;
1765              targetHeight = targetElm.offsetHeight;
1766  
1767              // Reset width/height if user selects a new image/table
1768              if (selectedElm != targetElm) {
1769                  selectedElm = targetElm;
1770                  width = height = 0;
1771              }
1772  
1773              each(resizeHandles, function(handle, name) {
1774                  var handleElm;
1775  
1776                  // Get existing or render resize handle
1777                  handleElm = dom.get('mceResizeHandle' + name);
1778                  if (!handleElm) {
1779                      handleElm = dom.add(editableDoc.documentElement, 'div', {
1780                          id: 'mceResizeHandle' + name,
1781                          'class': 'mceResizeHandle',
1782                          style: 'cursor:' + name + '-resize; margin:0; padding:0'
1783                      });
1784  
1785                      dom.bind(handleElm, 'mousedown', function(e) {
1786                          e.preventDefault();
1787  
1788                          endResize();
1789  
1790                          startX = e.screenX;
1791                          startY = e.screenY;
1792                          startW = selectedElm.clientWidth;
1793                          startH = selectedElm.clientHeight;
1794                          ratio = startH / startW;
1795                          selectedHandle = handle;
1796  
1797                          selectedElmGhost = selectedElm.cloneNode(true);
1798                          dom.addClass(selectedElmGhost, 'mceClonedResizable');
1799                          dom.setStyles(selectedElmGhost, {
1800                              left: selectedElmX,
1801                              top: selectedElmY,
1802                              margin: 0
1803                          });
1804  
1805                          editableDoc.documentElement.appendChild(selectedElmGhost);
1806  
1807                          dom.bind(editableDoc, 'mousemove', resizeElement);
1808                          dom.bind(editableDoc, 'mouseup', endResize);
1809  
1810                          if (rootDocument != editableDoc) {
1811                              dom.bind(rootDocument, 'mousemove', resizeElement);
1812                              dom.bind(rootDocument, 'mouseup', endResize);
1813                          }
1814                      });
1815                  } else {
1816                      dom.show(handleElm);
1817                  }
1818  
1819                  // Position element
1820                  dom.setStyles(handleElm, {
1821                      left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),
1822                      top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)
1823                  });
1824              });
1825  
1826              // Only add resize rectangle on WebKit and only on images
1827              if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {
1828                  selectedElm.setAttribute('data-mce-selected', '1');
1829              }
1830          }
1831  
1832  		function hideResizeRect() {
1833              if (selectedElm) {
1834                  selectedElm.removeAttribute('data-mce-selected');
1835              }
1836  
1837              for (var name in resizeHandles) {
1838                  dom.hide('mceResizeHandle' + name);
1839              }
1840          }
1841  
1842          // Add CSS for resize handles, cloned element and selected
1843          editor.contentStyles.push(
1844              '.mceResizeHandle {' +
1845                  'position: absolute;' +
1846                  'border: 1px solid black;' +
1847                  'background: #FFF;' +
1848                  'width: 5px;' +
1849                  'height: 5px;' +
1850                  'z-index: 10000' +
1851              '}' +
1852              '.mceResizeHandle:hover {' +
1853                  'background: #000' +
1854              '}' +
1855              'img[data-mce-selected] {' +
1856                  'outline: 1px solid black' +
1857              '}' +
1858              'img.mceClonedResizable, table.mceClonedResizable {' +
1859                  'position: absolute;' +
1860                  'outline: 1px dashed black;' +
1861                  'opacity: .5;' +
1862                  'z-index: 10000' +
1863              '}'
1864          );
1865  
1866  		function updateResizeRect() {
1867              var controlElm = dom.getParent(selection.getNode(), 'table,img');
1868  
1869              // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v
1870              each(dom.select('img[data-mce-selected]'), function(img) {
1871                  img.removeAttribute('data-mce-selected');
1872              });
1873  
1874              if (controlElm) {
1875                  showResizeRect(controlElm);
1876              } else {
1877                  hideResizeRect();
1878              }
1879          }
1880  
1881          // Show/hide resize rect when image is selected
1882          editor.onNodeChange.add(updateResizeRect);
1883  
1884          // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container
1885          dom.bind(editableDoc, 'selectionchange', updateResizeRect);
1886  
1887          // Remove the internal attribute when serializing the DOM
1888          editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {
1889              var i = nodes.length;
1890  
1891              while (i--) {
1892                  nodes[i].attr(name, null);
1893              }
1894          });
1895      }
1896  
1897  	function keepNoScriptContents() {
1898          if (getDocumentMode() < 9) {
1899              parser.addNodeFilter('noscript', function(nodes) {
1900                  var i = nodes.length, node, textNode;
1901  
1902                  while (i--) {
1903                      node = nodes[i];
1904                      textNode = node.firstChild;
1905  
1906                      if (textNode) {
1907                          node.attr('data-mce-innertext', textNode.value);
1908                      }
1909                  }
1910              });
1911  
1912              serializer.addNodeFilter('noscript', function(nodes) {
1913                  var i = nodes.length, node, textNode, value;
1914  
1915                  while (i--) {
1916                      node = nodes[i];
1917                      textNode = nodes[i].firstChild;
1918  
1919                      if (textNode) {
1920                          textNode.value = tinymce.html.Entities.decode(textNode.value);
1921                      } else {
1922                          // Old IE can't retain noscript value so an attribute is used to store it
1923                          value = node.attributes.map['data-mce-innertext'];
1924                          if (value) {
1925                              node.attr('data-mce-innertext', null);
1926                              textNode = new tinymce.html.Node('#text', 3);
1927                              textNode.value = value;
1928                              textNode.raw = true;
1929                              node.append(textNode);
1930                          }
1931                      }
1932                  }
1933              });
1934          }
1935      }
1936  
1937  	function bodyHeight() {
1938          editor.contentStyles.push('body {min-height: 100px}');
1939          editor.onClick.add(function(ed, e) {
1940              if (e.target.nodeName == 'HTML') {
1941                  editor.execCommand('SelectAll');
1942                  editor.selection.collapse(true);
1943                  editor.nodeChanged();
1944              }
1945          });
1946      }
1947  
1948  	function fixControlSelection() {
1949          editor.onInit.add(function() {
1950              var selectedRng;
1951  
1952              editor.getBody().addEventListener('mscontrolselect', function(e) {
1953                  setTimeout(function() {
1954                      if (editor.selection.getNode() != e.target) {
1955                          selectedRng = editor.selection.getRng();
1956                          selection.fakeRng = editor.dom.createRng();
1957                          selection.fakeRng.setStartBefore(e.target);
1958                          selection.fakeRng.setEndAfter(e.target);
1959                      }
1960                  }, 0);
1961              }, false);
1962  
1963              editor.getDoc().addEventListener('selectionchange', function(e) {
1964                  if (selectedRng && !tinymce.dom.RangeUtils.compareRanges(editor.selection.getRng(), selectedRng)) {
1965                      selection.fakeRng = selectedRng = null;
1966                  }
1967              }, false);
1968          });
1969      }
1970  
1971      // All browsers
1972      disableBackspaceIntoATable();
1973      removeBlockQuoteOnBackSpace();
1974      emptyEditorWhenDeleting();
1975  
1976      // WebKit
1977      if (tinymce.isWebKit) {
1978          keepInlineElementOnDeleteBackspace();
1979          cleanupStylesWhenDeleting();
1980          inputMethodFocus();
1981          selectControlElements();
1982          setDefaultBlockType();
1983  
1984          // iOS
1985          if (tinymce.isIDevice) {
1986              selectionChangeNodeChanged();
1987          } else {
1988              fakeImageResize();
1989              selectAll();
1990          }
1991      }
1992  
1993      // IE
1994      if (tinymce.isIE && !tinymce.isIE11) {
1995          removeHrOnBackspace();
1996          ensureBodyHasRoleApplication();
1997          addNewLinesBeforeBrInPre();
1998          removePreSerializedStylesWhenSelectingControls();
1999          deleteControlItemOnBackSpace();
2000          renderEmptyBlocksFix();
2001          keepNoScriptContents();
2002      }
2003  
2004      // IE 11+
2005      if (tinymce.isIE11) {
2006          bodyHeight();
2007          fixControlSelection();
2008      }
2009  
2010      // Gecko
2011      if (tinymce.isGecko && !tinymce.isIE11) {
2012          removeHrOnBackspace();
2013          focusBody();
2014          removeStylesWhenDeletingAccrossBlockElements();
2015          setGeckoEditingOptions();
2016          addBrAfterLastLinks();
2017          removeGhostSelection();
2018      }
2019  
2020      // Opera
2021      if (tinymce.isOpera) {
2022          fakeImageResize();
2023      }
2024  };
2025  (function(tinymce) {
2026      var namedEntities, baseEntities, reverseEntities,
2027          attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2028          textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2029          rawCharsRegExp = /[<>&\"\']/g,
2030          entityRegExp = /&(#x|#)?([\w]+);/g,
2031          asciiMap = {
2032                  128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2033                  135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2034                  142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2035                  150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2036                  156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2037          };
2038  
2039      // Raw entities
2040      baseEntities = {
2041          '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
2042          "'" : '&#39;',
2043          '<' : '&lt;',
2044          '>' : '&gt;',
2045          '&' : '&amp;'
2046      };
2047  
2048      // Reverse lookup table for raw entities
2049      reverseEntities = {
2050          '&lt;' : '<',
2051          '&gt;' : '>',
2052          '&amp;' : '&',
2053          '&quot;' : '"',
2054          '&apos;' : "'"
2055      };
2056  
2057      // Decodes text by using the browser
2058  	function nativeDecode(text) {
2059          var elm;
2060  
2061          elm = document.createElement("div");
2062          elm.innerHTML = text;
2063  
2064          return elm.textContent || elm.innerText || text;
2065      };
2066  
2067      // Build a two way lookup table for the entities
2068  	function buildEntitiesLookup(items, radix) {
2069          var i, chr, entity, lookup = {};
2070  
2071          if (items) {
2072              items = items.split(',');
2073              radix = radix || 10;
2074  
2075              // Build entities lookup table
2076              for (i = 0; i < items.length; i += 2) {
2077                  chr = String.fromCharCode(parseInt(items[i], radix));
2078  
2079                  // Only add non base entities
2080                  if (!baseEntities[chr]) {
2081                      entity = '&' + items[i + 1] + ';';
2082                      lookup[chr] = entity;
2083                      lookup[entity] = chr;
2084                  }
2085              }
2086  
2087              return lookup;
2088          }
2089      };
2090  
2091      // Unpack entities lookup where the numbers are in radix 32 to reduce the size
2092      namedEntities = buildEntitiesLookup(
2093          '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2094          '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2095          '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2096          '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2097          '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2098          '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2099          '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2100          '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2101          '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2102          '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2103          'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2104          'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2105          't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2106          'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2107          'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2108          '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2109          '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2110          '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2111          '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2112          '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2113          'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2114          'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2115          'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2116          '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2117          '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2118  
2119      tinymce.html = tinymce.html || {};
2120  
2121      tinymce.html.Entities = {
2122          encodeRaw : function(text, attr) {
2123              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2124                  return baseEntities[chr] || chr;
2125              });
2126          },
2127  
2128          encodeAllRaw : function(text) {
2129              return ('' + text).replace(rawCharsRegExp, function(chr) {
2130                  return baseEntities[chr] || chr;
2131              });
2132          },
2133  
2134          encodeNumeric : function(text, attr) {
2135              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2136                  // Multi byte sequence convert it to a single entity
2137                  if (chr.length > 1)
2138                      return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2139  
2140                  return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2141              });
2142          },
2143  
2144          encodeNamed : function(text, attr, entities) {
2145              entities = entities || namedEntities;
2146  
2147              return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2148                  return baseEntities[chr] || entities[chr] || chr;
2149              });
2150          },
2151  
2152          getEncodeFunc : function(name, entities) {
2153              var Entities = tinymce.html.Entities;
2154  
2155              entities = buildEntitiesLookup(entities) || namedEntities;
2156  
2157  			function encodeNamedAndNumeric(text, attr) {
2158                  return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2159                      return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2160                  });
2161              };
2162  
2163  			function encodeCustomNamed(text, attr) {
2164                  return Entities.encodeNamed(text, attr, entities);
2165              };
2166  
2167              // Replace + with , to be compatible with previous TinyMCE versions
2168              name = tinymce.makeMap(name.replace(/\+/g, ','));
2169  
2170              // Named and numeric encoder
2171              if (name.named && name.numeric)
2172                  return encodeNamedAndNumeric;
2173  
2174              // Named encoder
2175              if (name.named) {
2176                  // Custom names
2177                  if (entities)
2178                      return encodeCustomNamed;
2179  
2180                  return Entities.encodeNamed;
2181              }
2182  
2183              // Numeric
2184              if (name.numeric)
2185                  return Entities.encodeNumeric;
2186  
2187              // Raw encoder
2188              return Entities.encodeRaw;
2189          },
2190  
2191          decode : function(text) {
2192              return text.replace(entityRegExp, function(all, numeric, value) {
2193                  if (numeric) {
2194                      value = parseInt(value, numeric.length === 2 ? 16 : 10);
2195  
2196                      // Support upper UTF
2197                      if (value > 0xFFFF) {
2198                          value -= 0x10000;
2199  
2200                          return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2201                      } else
2202                          return asciiMap[value] || String.fromCharCode(value);
2203                  }
2204  
2205                  return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2206              });
2207          }
2208      };
2209  })(tinymce);
2210  tinymce.html.Styles = function(settings, schema) {
2211      var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2212          urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2213          styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2214          trimRightRegExp = /\s+$/,
2215          urlColorRegExp = /rgb/,
2216          undef, i, encodingLookup = {}, encodingItems;
2217  
2218      settings = settings || {};
2219  
2220      encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2221      for (i = 0; i < encodingItems.length; i++) {
2222          encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2223          encodingLookup['\uFEFF' + i] = encodingItems[i];
2224      }
2225  
2226  	function toHex(match, r, g, b) {
2227  		function hex(val) {
2228              val = parseInt(val).toString(16);
2229  
2230              return val.length > 1 ? val : '0' + val; // 0 -> 00
2231          };
2232  
2233          return '#' + hex(r) + hex(g) + hex(b);
2234      };
2235  
2236      return {
2237          toHex : function(color) {
2238              return color.replace(rgbRegExp, toHex);
2239          },
2240  
2241          parse : function(css) {
2242              var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2243  
2244  			function compress(prefix, suffix) {
2245                  var top, right, bottom, left;
2246  
2247                  // IE 11 will produce a border-image: none when getting the style attribute from <p style="border: 1px solid red"></p>
2248                  // So lets asume it shouldn't be there
2249                  if (styles['border-image'] === 'none') {
2250                      delete styles['border-image'];
2251                  }
2252  
2253                  // Get values and check it it needs compressing
2254                  top = styles[prefix + '-top' + suffix];
2255                  if (!top)
2256                      return;
2257  
2258                  right = styles[prefix + '-right' + suffix];
2259                  if (top != right)
2260                      return;
2261  
2262                  bottom = styles[prefix + '-bottom' + suffix];
2263                  if (right != bottom)
2264                      return;
2265  
2266                  left = styles[prefix + '-left' + suffix];
2267                  if (bottom != left)
2268                      return;
2269  
2270                  // Compress
2271                  styles[prefix + suffix] = left;
2272                  delete styles[prefix + '-top' + suffix];
2273                  delete styles[prefix + '-right' + suffix];
2274                  delete styles[prefix + '-bottom' + suffix];
2275                  delete styles[prefix + '-left' + suffix];
2276              };
2277  
2278  			function canCompress(key) {
2279                  var value = styles[key], i;
2280  
2281                  if (!value || value.indexOf(' ') < 0)
2282                      return;
2283  
2284                  value = value.split(' ');
2285                  i = value.length;
2286                  while (i--) {
2287                      if (value[i] !== value[0])
2288                          return false;
2289                  }
2290  
2291                  styles[key] = value[0];
2292  
2293                  return true;
2294              };
2295  
2296  			function compress2(target, a, b, c) {
2297                  if (!canCompress(a))
2298                      return;
2299  
2300                  if (!canCompress(b))
2301                      return;
2302  
2303                  if (!canCompress(c))
2304                      return;
2305  
2306                  // Compress
2307                  styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2308                  delete styles[a];
2309                  delete styles[b];
2310                  delete styles[c];
2311              };
2312  
2313              // Encodes the specified string by replacing all \" \' ; : with _<num>
2314  			function encode(str) {
2315                  isEncoded = true;
2316  
2317                  return encodingLookup[str];
2318              };
2319  
2320              // Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2321              // It will also decode the \" \' if keep_slashes is set to fale or omitted
2322  			function decode(str, keep_slashes) {
2323                  if (isEncoded) {
2324                      str = str.replace(/\uFEFF[0-9]/g, function(str) {
2325                          return encodingLookup[str];
2326                      });
2327                  }
2328  
2329                  if (!keep_slashes)
2330                      str = str.replace(/\\([\'\";:])/g, "$1");
2331  
2332                  return str;
2333              };
2334  
2335  			function processUrl(match, url, url2, url3, str, str2) {
2336                  str = str || str2;
2337  
2338                  if (str) {
2339                      str = decode(str);
2340  
2341                      // Force strings into single quote format
2342                      return "'" + str.replace(/\'/g, "\\'") + "'";
2343                  }
2344  
2345                  url = decode(url || url2 || url3);
2346  
2347                  // Convert the URL to relative/absolute depending on config
2348                  if (urlConverter)
2349                      url = urlConverter.call(urlConverterScope, url, 'style');
2350  
2351                  // Output new URL format
2352                  return "url('" + url.replace(/\'/g, "\\'") + "')";
2353              };
2354  
2355              if (css) {
2356                  // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2357                  css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2358                      return str.replace(/[;:]/g, encode);
2359                  });
2360  
2361                  // Parse styles
2362                  while (matches = styleRegExp.exec(css)) {
2363                      name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2364                      value = matches[2].replace(trimRightRegExp, '');
2365  
2366                      if (name && value.length > 0) {
2367                          // Opera will produce 700 instead of bold in their style values
2368                          if (name === 'font-weight' && value === '700')
2369                              value = 'bold';
2370                          else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2371                              value = value.toLowerCase();        
2372  
2373                          // Convert RGB colors to HEX
2374                          value = value.replace(rgbRegExp, toHex);
2375  
2376                          // Convert URLs and force them into url('value') format
2377                          value = value.replace(urlOrStrRegExp, processUrl);
2378                          styles[name] = isEncoded ? decode(value, true) : value;
2379                      }
2380  
2381                      styleRegExp.lastIndex = matches.index + matches[0].length;
2382                  }
2383  
2384                  // Compress the styles to reduce it's size for example IE will expand styles
2385                  compress("border", "");
2386                  compress("border", "-width");
2387                  compress("border", "-color");
2388                  compress("border", "-style");
2389                  compress("padding", "");
2390                  compress("margin", "");
2391                  compress2('border', 'border-width', 'border-style', 'border-color');
2392  
2393                  // Remove pointless border, IE produces these
2394                  if (styles.border === 'medium none')
2395                      delete styles.border;
2396              }
2397  
2398              return styles;
2399          },
2400  
2401          serialize : function(styles, element_name) {
2402              var css = '', name, value;
2403  
2404  			function serializeStyles(name) {
2405                  var styleList, i, l, value;
2406  
2407                  styleList = schema.styles[name];
2408                  if (styleList) {
2409                      for (i = 0, l = styleList.length; i < l; i++) {
2410                          name = styleList[i];
2411                          value = styles[name];
2412  
2413                          if (value !== undef && value.length > 0)
2414                              css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2415                      }
2416                  }
2417              };
2418  
2419              // Serialize styles according to schema
2420              if (element_name && schema && schema.styles) {
2421                  // Serialize global styles and element specific styles
2422                  serializeStyles('*');
2423                  serializeStyles(element_name);
2424              } else {
2425                  // Output the styles in the order they are inside the object
2426                  for (name in styles) {
2427                      value = styles[name];
2428  
2429                      if (value !== undef && value.length > 0)
2430                          css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2431                  }
2432              }
2433  
2434              return css;
2435          }
2436      };
2437  };
2438  (function(tinymce) {
2439      var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2440  
2441  	function split(str, delim) {
2442          return str.split(delim || ',');
2443      };
2444  
2445  	function unpack(lookup, data) {
2446          var key, elements = {};
2447  
2448  		function replace(value) {
2449              return value.replace(/[A-Z]+/g, function(key) {
2450                  return replace(lookup[key]);
2451              });
2452          };
2453  
2454          // Unpack lookup
2455          for (key in lookup) {
2456              if (lookup.hasOwnProperty(key))
2457                  lookup[key] = replace(lookup[key]);
2458          }
2459  
2460          // Unpack and parse data into object map
2461          replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2462              attributes = split(attributes, '|');
2463  
2464              elements[name] = {
2465                  attributes : makeMap(attributes),
2466                  attributesOrder : attributes,
2467                  children : makeMap(children, '|', {'#comment' : {}})
2468              }
2469          });
2470  
2471          return elements;
2472      };
2473  
2474  	function getHTML5() {
2475          var html5 = mapCache.html5;
2476  
2477          if (!html5) {
2478              html5 = mapCache.html5 = unpack({
2479                      A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2480                      B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2481                          'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2482                      C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2483                          'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2484                          'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2485                  }, 'html[A|manifest][body|head]' +
2486                      'head[A][base|command|link|meta|noscript|script|style|title]' +
2487                      'title[A][#]' +
2488                      'base[A|href|target][]' +
2489                      'link[A|href|rel|media|type|sizes][]' +
2490                      'meta[A|http-equiv|name|content|charset][]' +
2491                      'style[A|type|media|scoped][#]' +
2492                      'script[A|charset|type|src|defer|async][#]' +
2493                      'noscript[A][C]' +
2494                      'body[A][C]' +
2495                      'section[A][C]' +
2496                      'nav[A][C]' +
2497                      'article[A][C]' +
2498                      'aside[A][C]' +
2499                      'h1[A][B]' +
2500                      'h2[A][B]' +
2501                      'h3[A][B]' +
2502                      'h4[A][B]' +
2503                      'h5[A][B]' +
2504                      'h6[A][B]' +
2505                      'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2506                      'header[A][C]' +
2507                      'footer[A][C]' +
2508                      'address[A][C]' +
2509                      'p[A][B]' +
2510                      'br[A][]' +
2511                      'pre[A][B]' +
2512                      'dialog[A][dd|dt]' +
2513                      'blockquote[A|cite][C]' +
2514                      'ol[A|start|reversed][li]' +
2515                      'ul[A][li]' +
2516                      'li[A|value][C]' +
2517                      'dl[A][dd|dt]' +
2518                      'dt[A][B]' +
2519                      'dd[A][C]' +
2520                      'a[A|href|target|ping|rel|media|type][B]' +
2521                      'em[A][B]' +
2522                      'strong[A][B]' +
2523                      'small[A][B]' +
2524                      'cite[A][B]' +
2525                      'q[A|cite][B]' +
2526                      'dfn[A][B]' +
2527                      'abbr[A][B]' +
2528                      'code[A][B]' +
2529                      'var[A][B]' +
2530                      'samp[A][B]' +
2531                      'kbd[A][B]' +
2532                      'sub[A][B]' +
2533                      'sup[A][B]' +
2534                      'i[A][B]' +
2535                      'b[A][B]' +
2536                      'mark[A][B]' +
2537                      'progress[A|value|max][B]' +
2538                      'meter[A|value|min|max|low|high|optimum][B]' +
2539                      'time[A|datetime][B]' +
2540                      'ruby[A][B|rt|rp]' +
2541                      'rt[A][B]' +
2542                      'rp[A][B]' +
2543                      'bdo[A][B]' +
2544                      'span[A][B]' +
2545                      'ins[A|cite|datetime][B]' +
2546                      'del[A|cite|datetime][B]' +
2547                      'figure[A][C|legend|figcaption]' +
2548                      'figcaption[A][C]' +
2549                      'img[A|alt|src|height|width|usemap|ismap][]' +
2550                      'iframe[A|name|src|height|width|sandbox|seamless][]' +
2551                      'embed[A|src|height|width|type][]' +
2552                      'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2553                      'param[A|name|value][]' +
2554                      'details[A|open][C|legend]' +
2555                      'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2556                      'menu[A|type|label][C|li]' +
2557                      'legend[A][C|B]' +
2558                      'div[A][C]' +
2559                      'source[A|src|type|media][]' +
2560                      'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2561                      'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2562                      'hr[A][]' +
2563                      'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2564                      'fieldset[A|disabled|form|name][C|legend]' +
2565                      'label[A|form|for][B]' +
2566                      'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2567                          'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2568                      'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2569                      'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2570                      'datalist[A][B|option]' +
2571                      'optgroup[A|disabled|label][option]' +
2572                      'option[A|disabled|selected|label|value][]' +
2573                      'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2574                      'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2575                      'output[A|for|form|name][B]' +
2576                      'canvas[A|width|height][]' +
2577                      'map[A|name][B|C]' +
2578                      'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2579                      'mathml[A][]' +
2580                      'svg[A][]' +
2581                      'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2582                      'caption[A][C]' +
2583                      'colgroup[A|span][col]' +
2584                      'col[A|span][]' +
2585                      'thead[A][tr]' +
2586                      'tfoot[A][tr]' +
2587                      'tbody[A][tr]' +
2588                      'tr[A][th|td]' +
2589                      'th[A|headers|rowspan|colspan|scope][B]' +
2590                      'td[A|headers|rowspan|colspan][C]' +
2591                      'wbr[A][]'
2592              );
2593          }
2594  
2595          return html5;
2596      };
2597  
2598  	function getHTML4() {
2599          var html4 = mapCache.html4;
2600  
2601          if (!html4) {
2602              // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2603              html4 = mapCache.html4 = unpack({
2604                  Z : 'H|K|N|O|P',
2605                  Y : 'X|form|R|Q',
2606                  ZG : 'E|span|width|align|char|charoff|valign',
2607                  X : 'p|T|div|U|W|isindex|fieldset|table',
2608                  ZF : 'E|align|char|charoff|valign',
2609                  W : 'pre|hr|blockquote|address|center|noframes',
2610                  ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2611                  ZD : '[E][S]',
2612                  U : 'ul|ol|dl|menu|dir',
2613                  ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2614                  T : 'h1|h2|h3|h4|h5|h6',
2615                  ZB : 'X|S|Q',
2616                  S : 'R|P',
2617                  ZA : 'a|G|J|M|O|P',
2618                  R : 'a|H|K|N|O',
2619                  Q : 'noscript|P',
2620                  P : 'ins|del|script',
2621                  O : 'input|select|textarea|label|button',
2622                  N : 'M|L',
2623                  M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2624                  L : 'sub|sup',
2625                  K : 'J|I',
2626                  J : 'tt|i|b|u|s|strike',
2627                  I : 'big|small|font|basefont',
2628                  H : 'G|F',
2629                  G : 'br|span|bdo',
2630                  F : 'object|applet|img|map|iframe',
2631                  E : 'A|B|C',
2632                  D : 'accesskey|tabindex|onfocus|onblur',
2633                  C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2634                  B : 'lang|xml:lang|dir',
2635                  A : 'id|class|style|title'
2636              }, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2637                  'style[B|id|type|media|title|xml:space][]' + 
2638                  'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2639                  'param[id|name|value|valuetype|type][]' + 
2640                  'p[E|align][#|S]' + 
2641                  'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2642                  'br[A|clear][]' + 
2643                  'span[E][#|S]' + 
2644                  'bdo[A|C|B][#|S]' + 
2645                  'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2646                  'h1[E|align][#|S]' + 
2647                  'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2648                  'map[B|C|A|name][X|form|Q|area]' + 
2649                  'h2[E|align][#|S]' + 
2650                  'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2651                  'h3[E|align][#|S]' + 
2652                  'tt[E][#|S]' + 
2653                  'i[E][#|S]' + 
2654                  'b[E][#|S]' + 
2655                  'u[E][#|S]' + 
2656                  's[E][#|S]' + 
2657                  'strike[E][#|S]' + 
2658                  'big[E][#|S]' + 
2659                  'small[E][#|S]' + 
2660                  'font[A|B|size|color|face][#|S]' + 
2661                  'basefont[id|size|color|face][]' + 
2662                  'em[E][#|S]' + 
2663                  'strong[E][#|S]' + 
2664                  'dfn[E][#|S]' + 
2665                  'code[E][#|S]' + 
2666                  'q[E|cite][#|S]' + 
2667                  'samp[E][#|S]' + 
2668                  'kbd[E][#|S]' + 
2669                  'var[E][#|S]' + 
2670                  'cite[E][#|S]' + 
2671                  'abbr[E][#|S]' + 
2672                  'acronym[E][#|S]' + 
2673                  'sub[E][#|S]' + 
2674                  'sup[E][#|S]' + 
2675                  'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2676                  'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2677                  'optgroup[E|disabled|label][option]' + 
2678                  'option[E|selected|disabled|label|value][]' + 
2679                  'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2680                  'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2681                  'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2682                  'h4[E|align][#|S]' + 
2683                  'ins[E|cite|datetime][#|Y]' + 
2684                  'h5[E|align][#|S]' + 
2685                  'del[E|cite|datetime][#|Y]' + 
2686                  'h6[E|align][#|S]' + 
2687                  'div[E|align][#|Y]' + 
2688                  'ul[E|type|compact][li]' + 
2689                  'li[E|type|value][#|Y]' + 
2690                  'ol[E|type|compact|start][li]' + 
2691                  'dl[E|compact][dt|dd]' + 
2692                  'dt[E][#|S]' + 
2693                  'dd[E][#|Y]' + 
2694                  'menu[E|compact][li]' + 
2695                  'dir[E|compact][li]' + 
2696                  'pre[E|width|xml:space][#|ZA]' + 
2697                  'hr[E|align|noshade|size|width][]' + 
2698                  'blockquote[E|cite][#|Y]' + 
2699                  'address[E][#|S|p]' + 
2700                  'center[E][#|Y]' + 
2701                  'noframes[E][#|Y]' + 
2702                  'isindex[A|B|prompt][]' + 
2703                  'fieldset[E][#|legend|Y]' + 
2704                  'legend[E|accesskey|align][#|S]' + 
2705                  'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2706                  'caption[E|align][#|S]' + 
2707                  'col[ZG][]' + 
2708                  'colgroup[ZG][col]' + 
2709                  'thead[ZF][tr]' + 
2710                  'tr[ZF|bgcolor][th|td]' + 
2711                  'th[E|ZE][#|Y]' + 
2712                  'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2713                  'noscript[E][#|Y]' + 
2714                  'td[E|ZE][#|Y]' + 
2715                  'tfoot[ZF][tr]' + 
2716                  'tbody[ZF][tr]' + 
2717                  'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2718                  'base[id|href|target][]' + 
2719                  'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2720              );
2721          }
2722  
2723          return html4;
2724      };
2725  
2726      tinymce.html.Schema = function(settings) {
2727          var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2728          var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2729  
2730          // Creates an lookup table map object for the specified option or the default value
2731  		function createLookupTable(option, default_value, extend) {
2732              var value = settings[option];
2733  
2734              if (!value) {
2735                  // Get cached default map or make it if needed
2736                  value = mapCache[option];
2737  
2738                  if (!value) {
2739                      value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2740                      value = tinymce.extend(value, extend);
2741  
2742                      mapCache[option] = value;
2743                  }
2744              } else {
2745                  // Create custom map
2746                  value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2747              }
2748  
2749              return value;
2750          };
2751  
2752          settings = settings || {};
2753          schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2754  
2755          // Allow all elements and attributes if verify_html is set to false
2756          if (settings.verify_html === false)
2757              settings.valid_elements = '*[*]';
2758  
2759          // Build styles list
2760          if (settings.valid_styles) {
2761              validStyles = {};
2762  
2763              // Convert styles into a rule list
2764              each(settings.valid_styles, function(value, key) {
2765                  validStyles[key] = tinymce.explode(value);
2766              });
2767          }
2768  
2769          // Setup map objects
2770          whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea');
2771          selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2772          shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2773          boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2774          nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object script', shortEndedElementsMap);
2775          textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + 
2776                          'blockquote center dir fieldset header footer article section hgroup aside nav figure');
2777          blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 
2778                          'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap);
2779  
2780          // Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2781  		function patternToRegExp(str) {
2782              return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2783          };
2784  
2785          // Parses the specified valid_elements string and adds to the current rules
2786          // This function is a bit hard to read since it's heavily optimized for speed
2787  		function addValidElements(valid_elements) {
2788              var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2789                  prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2790                  elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2791                  attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2792                  hasPatternsRegExp = /[*?+]/;
2793  
2794              if (valid_elements) {
2795                  // Split valid elements into an array with rules
2796                  valid_elements = split(valid_elements);
2797  
2798                  if (elements['@']) {
2799                      globalAttributes = elements['@'].attributes;
2800                      globalAttributesOrder = elements['@'].attributesOrder;
2801                  }
2802  
2803                  // Loop all rules
2804                  for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2805                      // Parse element rule
2806                      matches = elementRuleRegExp.exec(valid_elements[ei]);
2807                      if (matches) {
2808                          // Setup local names for matches
2809                          prefix = matches[1];
2810                          elementName = matches[2];
2811                          outputName = matches[3];
2812                          attrData = matches[4];
2813  
2814                          // Create new attributes and attributesOrder
2815                          attributes = {};
2816                          attributesOrder = [];
2817  
2818                          // Create the new element
2819                          element = {
2820                              attributes : attributes,
2821                              attributesOrder : attributesOrder
2822                          };
2823  
2824                          // Padd empty elements prefix
2825                          if (prefix === '#')
2826                              element.paddEmpty = true;
2827  
2828                          // Remove empty elements prefix
2829                          if (prefix === '-')
2830                              element.removeEmpty = true;
2831  
2832                          // Copy attributes from global rule into current rule
2833                          if (globalAttributes) {
2834                              for (key in globalAttributes)
2835                                  attributes[key] = globalAttributes[key];
2836  
2837                              attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2838                          }
2839  
2840                          // Attributes defined
2841                          if (attrData) {
2842                              attrData = split(attrData, '|');
2843                              for (ai = 0, al = attrData.length; ai < al; ai++) {
2844                                  matches = attrRuleRegExp.exec(attrData[ai]);
2845                                  if (matches) {
2846                                      attr = {};
2847                                      attrType = matches[1];
2848                                      attrName = matches[2].replace(/::/g, ':');
2849                                      prefix = matches[3];
2850                                      value = matches[4];
2851  
2852                                      // Required
2853                                      if (attrType === '!') {
2854                                          element.attributesRequired = element.attributesRequired || [];
2855                                          element.attributesRequired.push(attrName);
2856                                          attr.required = true;
2857                                      }
2858  
2859                                      // Denied from global
2860                                      if (attrType === '-') {
2861                                          delete attributes[attrName];
2862                                          attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2863                                          continue;
2864                                      }
2865  
2866                                      // Default value
2867                                      if (prefix) {
2868                                          // Default value
2869                                          if (prefix === '=') {
2870                                              element.attributesDefault = element.attributesDefault || [];
2871                                              element.attributesDefault.push({name: attrName, value: value});
2872                                              attr.defaultValue = value;
2873                                          }
2874  
2875                                          // Forced value
2876                                          if (prefix === ':') {
2877                                              element.attributesForced = element.attributesForced || [];
2878                                              element.attributesForced.push({name: attrName, value: value});
2879                                              attr.forcedValue = value;
2880                                          }
2881  
2882                                          // Required values
2883                                          if (prefix === '<')
2884                                              attr.validValues = makeMap(value, '?');
2885                                      }
2886  
2887                                      // Check for attribute patterns
2888                                      if (hasPatternsRegExp.test(attrName)) {
2889                                          element.attributePatterns = element.attributePatterns || [];
2890                                          attr.pattern = patternToRegExp(attrName);
2891                                          element.attributePatterns.push(attr);
2892                                      } else {
2893                                          // Add attribute to order list if it doesn't already exist
2894                                          if (!attributes[attrName])
2895                                              attributesOrder.push(attrName);
2896  
2897                                          attributes[attrName] = attr;
2898                                      }
2899                                  }
2900                              }
2901                          }
2902  
2903                          // Global rule, store away these for later usage
2904                          if (!globalAttributes && elementName == '@') {
2905                              globalAttributes = attributes;
2906                              globalAttributesOrder = attributesOrder;
2907                          }
2908  
2909                          // Handle substitute elements such as b/strong
2910                          if (outputName) {
2911                              element.outputName = elementName;
2912                              elements[outputName] = element;
2913                          }
2914  
2915                          // Add pattern or exact element
2916                          if (hasPatternsRegExp.test(elementName)) {
2917                              element.pattern = patternToRegExp(elementName);
2918                              patternElements.push(element);
2919                          } else
2920                              elements[elementName] = element;
2921                      }
2922                  }
2923              }
2924          };
2925  
2926  		function setValidElements(valid_elements) {
2927              elements = {};
2928              patternElements = [];
2929  
2930              addValidElements(valid_elements);
2931  
2932              each(schemaItems, function(element, name) {
2933                  children[name] = element.children;
2934              });
2935          };
2936  
2937          // Adds custom non HTML elements to the schema
2938  		function addCustomElements(custom_elements) {
2939              var customElementRegExp = /^(~)?(.+)$/;
2940  
2941              if (custom_elements) {
2942                  each(split(custom_elements), function(rule) {
2943                      var matches = customElementRegExp.exec(rule),
2944                          inline = matches[1] === '~',
2945                          cloneName = inline ? 'span' : 'div',
2946                          name = matches[2];
2947  
2948                      children[name] = children[cloneName];
2949                      customElementsMap[name] = cloneName;
2950  
2951                      // If it's not marked as inline then add it to valid block elements
2952                      if (!inline) {
2953                          blockElementsMap[name.toUpperCase()] = {};
2954                          blockElementsMap[name] = {};
2955                      }
2956  
2957                      // Add elements clone if needed
2958                      if (!elements[name]) {
2959                          elements[name] = elements[cloneName];
2960                      }
2961  
2962                      // Add custom elements at span/div positions
2963                      each(children, function(element, child) {
2964                          if (element[cloneName])
2965                              element[name] = element[cloneName];
2966                      });
2967                  });
2968              }
2969          };
2970  
2971          // Adds valid children to the schema object
2972  		function addValidChildren(valid_children) {
2973              var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2974  
2975              if (valid_children) {
2976                  each(split(valid_children), function(rule) {
2977                      var matches = childRuleRegExp.exec(rule), parent, prefix;
2978  
2979                      if (matches) {
2980                          prefix = matches[1];
2981  
2982                          // Add/remove items from default
2983                          if (prefix)
2984                              parent = children[matches[2]];
2985                          else
2986                              parent = children[matches[2]] = {'#comment' : {}};
2987  
2988                          parent = children[matches[2]];
2989  
2990                          each(split(matches[3], '|'), function(child) {
2991                              if (prefix === '-')
2992                                  delete parent[child];
2993                              else
2994                                  parent[child] = {};
2995                          });
2996                      }
2997                  });
2998              }
2999          };
3000  
3001  		function getElementRule(name) {
3002              var element = elements[name], i;
3003  
3004              // Exact match found
3005              if (element)
3006                  return element;
3007  
3008              // No exact match then try the patterns
3009              i = patternElements.length;
3010              while (i--) {
3011                  element = patternElements[i];
3012  
3013                  if (element.pattern.test(name))
3014                      return element;
3015              }
3016          };
3017  
3018          if (!settings.valid_elements) {
3019              // No valid elements defined then clone the elements from the schema spec
3020              each(schemaItems, function(element, name) {
3021                  elements[name] = {
3022                      attributes : element.attributes,
3023                      attributesOrder : element.attributesOrder
3024                  };
3025  
3026                  children[name] = element.children;
3027              });
3028  
3029              // Switch these on HTML4
3030              if (settings.schema != "html5") {
3031                  each(split('strong/b,em/i'), function(item) {
3032                      item = split(item, '/');
3033                      elements[item[1]].outputName = item[0];
3034                  });
3035              }
3036  
3037              // Add default alt attribute for images
3038              elements.img.attributesDefault = [{name: 'alt', value: ''}];
3039  
3040              // Remove these if they are empty by default
3041              each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3042                  if (elements[name]) {
3043                      elements[name].removeEmpty = true;
3044                  }
3045              });
3046  
3047              // Padd these by default
3048              each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3049                  elements[name].paddEmpty = true;
3050              });
3051          } else
3052              setValidElements(settings.valid_elements);
3053  
3054          addCustomElements(settings.custom_elements);
3055          addValidChildren(settings.valid_children);
3056          addValidElements(settings.extended_valid_elements);
3057  
3058          // Todo: Remove this when we fix list handling to be valid
3059          addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3060  
3061          // Delete invalid elements
3062          if (settings.invalid_elements) {
3063              tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3064                  if (elements[item])
3065                      delete elements[item];
3066              });
3067          }
3068  
3069          // If the user didn't allow span only allow internal spans
3070          if (!getElementRule('span'))
3071              addValidElements('span[!data-mce-type|*]');
3072  
3073          self.children = children;
3074  
3075          self.styles = validStyles;
3076  
3077          self.getBoolAttrs = function() {
3078              return boolAttrMap;
3079          };
3080  
3081          self.getBlockElements = function() {
3082              return blockElementsMap;
3083          };
3084  
3085          self.getTextBlockElements = function() {
3086              return textBlockElementsMap;
3087          };
3088  
3089          self.getShortEndedElements = function() {
3090              return shortEndedElementsMap;
3091          };
3092  
3093          self.getSelfClosingElements = function() {
3094              return selfClosingElementsMap;
3095          };
3096  
3097          self.getNonEmptyElements = function() {
3098              return nonEmptyElementsMap;
3099          };
3100  
3101          self.getWhiteSpaceElements = function() {
3102              return whiteSpaceElementsMap;
3103          };
3104  
3105          self.isValidChild = function(name, child) {
3106              var parent = children[name];
3107  
3108              return !!(parent && parent[child]);
3109          };
3110  
3111          self.isValid = function(name, attr) {
3112              var attrPatterns, i, rule = getElementRule(name);
3113  
3114              // Check if it's a valid element
3115              if (rule) {
3116                  if (attr) {
3117                      // Check if attribute name exists
3118                      if (rule.attributes[attr]) {
3119                          return true;
3120                      }
3121  
3122                      // Check if attribute matches a regexp pattern
3123                      attrPatterns = rule.attributePatterns;
3124                      if (attrPatterns) {
3125                          i = attrPatterns.length;
3126                          while (i--) {
3127                              if (attrPatterns[i].pattern.test(name)) {
3128                                  return true;
3129                              }
3130                          }
3131                      }
3132                  } else {
3133                      return true;
3134                  }
3135              }
3136  
3137              // No match
3138              return false;
3139          };
3140          
3141          self.getElementRule = getElementRule;
3142  
3143          self.getCustomElements = function() {
3144              return customElementsMap;
3145          };
3146  
3147          self.addValidElements = addValidElements;
3148  
3149          self.setValidElements = setValidElements;
3150  
3151          self.addCustomElements = addCustomElements;
3152  
3153          self.addValidChildren = addValidChildren;
3154  
3155          self.elements = elements;
3156      };
3157  })(tinymce);
3158  (function(tinymce) {
3159      tinymce.html.SaxParser = function(settings, schema) {
3160          var self = this, noop = function() {};
3161  
3162          settings = settings || {};
3163          self.schema = schema = schema || new tinymce.html.Schema();
3164  
3165          if (settings.fix_self_closing !== false)
3166              settings.fix_self_closing = true;
3167  
3168          // Add handler functions from settings and setup default handlers
3169          tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3170              if (name)
3171                  self[name] = settings[name] || noop;
3172          });
3173  
3174          self.parse = function(html) {
3175              var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3176                  shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3177                  validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3178                  tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3179  
3180  			function processEndTag(name) {
3181                  var pos, i;
3182  
3183                  // Find position of parent of the same type
3184                  pos = stack.length;
3185                  while (pos--) {
3186                      if (stack[pos].name === name)
3187                          break;                        
3188                  }
3189  
3190                  // Found parent
3191                  if (pos >= 0) {
3192                      // Close all the open elements
3193                      for (i = stack.length - 1; i >= pos; i--) {
3194                          name = stack[i];
3195  
3196                          if (name.valid)
3197                              self.end(name.name);
3198                      }
3199  
3200                      // Remove the open elements from the stack
3201                      stack.length = pos;
3202                  }
3203              };
3204  
3205  			function parseAttribute(match, name, value, val2, val3) {
3206                  var attrRule, i;
3207  
3208                  name = name.toLowerCase();
3209                  value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3210  
3211                  // Validate name and value
3212                  if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3213                      attrRule = validAttributesMap[name];
3214  
3215                      // Find rule by pattern matching
3216                      if (!attrRule && validAttributePatterns) {
3217                          i = validAttributePatterns.length;
3218                          while (i--) {
3219                              attrRule = validAttributePatterns[i];
3220                              if (attrRule.pattern.test(name))
3221                                  break;
3222                          }
3223  
3224                          // No rule matched
3225                          if (i === -1)
3226                              attrRule = null;
3227                      }
3228  
3229                      // No attribute rule found
3230                      if (!attrRule)
3231                          return;
3232  
3233                      // Validate value
3234                      if (attrRule.validValues && !(value in attrRule.validValues))
3235                          return;
3236                  }
3237  
3238                  // Add attribute to list and map
3239                  attrList.map[name] = value;
3240                  attrList.push({
3241                      name: name,
3242                      value: value
3243                  });
3244              };
3245  
3246              // Precompile RegExps and map objects
3247              tokenRegExp = new RegExp('<(?:' +
3248                  '(?:!--([\\w\\W]*?)-->)|' + // Comment
3249                  '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3250                  '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3251                  '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3252                  '(?:\\/([^>]+)>)|' + // End element
3253                  '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3254              ')', 'g');
3255  
3256              attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;
3257              specialElements = {
3258                  'script' : /<\/script[^>]*>/gi,
3259                  'style' : /<\/style[^>]*>/gi,
3260                  'noscript' : /<\/noscript[^>]*>/gi
3261              };
3262  
3263              // Setup lookup tables for empty elements and boolean attributes
3264              shortEndedElements = schema.getShortEndedElements();
3265              selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3266              fillAttrsMap = schema.getBoolAttrs();
3267              validate = settings.validate;
3268              removeInternalElements = settings.remove_internals;
3269              fixSelfClosing = settings.fix_self_closing;
3270              isIE = tinymce.isIE;
3271              invalidPrefixRegExp = /^:/;
3272  
3273              while (matches = tokenRegExp.exec(html)) {
3274                  // Text
3275                  if (index < matches.index)
3276                      self.text(decode(html.substr(index, matches.index - index)));
3277  
3278                  if (value = matches[6]) { // End element
3279                      value = value.toLowerCase();
3280  
3281                      // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3282                      if (isIE && invalidPrefixRegExp.test(value))
3283                          value = value.substr(1);
3284  
3285                      processEndTag(value);
3286                  } else if (value = matches[7]) { // Start element
3287                      value = value.toLowerCase();
3288  
3289                      // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3290                      if (isIE && invalidPrefixRegExp.test(value))
3291                          value = value.substr(1);
3292  
3293                      isShortEnded = value in shortEndedElements;
3294  
3295                      // Is self closing tag for example an <li> after an open <li>
3296                      if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3297                          processEndTag(value);
3298  
3299                      // Validate element
3300                      if (!validate || (elementRule = schema.getElementRule(value))) {
3301                          isValidElement = true;
3302  
3303                          // Grab attributes map and patters when validation is enabled
3304                          if (validate) {
3305                              validAttributesMap = elementRule.attributes;
3306                              validAttributePatterns = elementRule.attributePatterns;
3307                          }
3308  
3309                          // Parse attributes
3310                          if (attribsValue = matches[8]) {
3311                              isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3312  
3313                              // If the element has internal attributes then remove it if we are told to do so
3314                              if (isInternalElement && removeInternalElements)
3315                                  isValidElement = false;
3316  
3317                              attrList = [];
3318                              attrList.map = {};
3319  
3320                              attribsValue.replace(attrRegExp, parseAttribute);
3321                          } else {
3322                              attrList = [];
3323                              attrList.map = {};
3324                          }
3325  
3326                          // Process attributes if validation is enabled
3327                          if (validate && !isInternalElement) {
3328                              attributesRequired = elementRule.attributesRequired;
3329                              attributesDefault = elementRule.attributesDefault;
3330                              attributesForced = elementRule.attributesForced;
3331  
3332                              // Handle forced attributes
3333                              if (attributesForced) {
3334                                  i = attributesForced.length;
3335                                  while (i--) {
3336                                      attr = attributesForced[i];
3337                                      name = attr.name;
3338                                      attrValue = attr.value;
3339  
3340                                      if (attrValue === '{$uid}')
3341                                          attrValue = 'mce_' + idCount++;
3342  
3343                                      attrList.map[name] = attrValue;
3344                                      attrList.push({name: name, value: attrValue});
3345                                  }
3346                              }
3347  
3348                              // Handle default attributes
3349                              if (attributesDefault) {
3350                                  i = attributesDefault.length;
3351                                  while (i--) {
3352                                      attr = attributesDefault[i];
3353                                      name = attr.name;
3354  
3355                                      if (!(name in attrList.map)) {
3356                                          attrValue = attr.value;
3357  
3358                                          if (attrValue === '{$uid}')
3359                                              attrValue = 'mce_' + idCount++;
3360  
3361                                          attrList.map[name] = attrValue;
3362                                          attrList.push({name: name, value: attrValue});
3363                                      }
3364                                  }
3365                              }
3366  
3367                              // Handle required attributes
3368                              if (attributesRequired) {
3369                                  i = attributesRequired.length;
3370                                  while (i--) {
3371                                      if (attributesRequired[i] in attrList.map)
3372                                          break;
3373                                  }
3374  
3375                                  // None of the required attributes where found
3376                                  if (i === -1)
3377                                      isValidElement = false;
3378                              }
3379  
3380                              // Invalidate element if it's marked as bogus
3381                              if (attrList.map['data-mce-bogus'])
3382                                  isValidElement = false;
3383                          }
3384  
3385                          if (isValidElement)
3386                              self.start(value, attrList, isShortEnded);
3387                      } else
3388                          isValidElement = false;
3389  
3390                      // Treat script, noscript and style a bit different since they may include code that looks like elements
3391                      if (endRegExp = specialElements[value]) {
3392                          endRegExp.lastIndex = index = matches.index + matches[0].length;
3393  
3394                          if (matches = endRegExp.exec(html)) {
3395                              if (isValidElement)
3396                                  text = html.substr(index, matches.index - index);
3397  
3398                              index = matches.index + matches[0].length;
3399                          } else {
3400                              text = html.substr(index);
3401                              index = html.length;
3402                          }
3403  
3404                          if (isValidElement && text.length > 0)
3405                              self.text(text, true);
3406  
3407                          if (isValidElement)
3408                              self.end(value);
3409  
3410                          tokenRegExp.lastIndex = index;
3411                          continue;
3412                      }
3413  
3414                      // Push value on to stack
3415                      if (!isShortEnded) {
3416                          if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3417                              stack.push({name: value, valid: isValidElement});
3418                          else if (isValidElement)
3419                              self.end(value);
3420                      }
3421                  } else if (value = matches[1]) { // Comment
3422                      self.comment(value);
3423                  } else if (value = matches[2]) { // CDATA
3424                      self.cdata(value);
3425                  } else if (value = matches[3]) { // DOCTYPE
3426                      self.doctype(value);
3427                  } else if (value = matches[4]) { // PI
3428                      self.pi(value, matches[5]);
3429                  }
3430  
3431                  index = matches.index + matches[0].length;
3432              }
3433  
3434              // Text
3435              if (index < html.length)
3436                  self.text(decode(html.substr(index)));
3437  
3438              // Close any open elements
3439              for (i = stack.length - 1; i >= 0; i--) {
3440                  value = stack[i];
3441  
3442                  if (value.valid)
3443                      self.end(value.name);
3444              }
3445          };
3446      }
3447  })(tinymce);
3448  (function(tinymce) {
3449      var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3450          '#text' : 3,
3451          '#comment' : 8,
3452          '#cdata' : 4,
3453          '#pi' : 7,
3454          '#doctype' : 10,
3455          '#document-fragment' : 11
3456      };
3457  
3458      // Walks the tree left/right
3459  	function walk(node, root_node, prev) {
3460          var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3461  
3462          // Walk into nodes if it has a start
3463          if (node[startName])
3464              return node[startName];
3465  
3466          // Return the sibling if it has one
3467          if (node !== root_node) {
3468              sibling = node[siblingName];
3469  
3470              if (sibling)
3471                  return sibling;
3472  
3473              // Walk up the parents to look for siblings
3474              for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3475                  sibling = parent[siblingName];
3476  
3477                  if (sibling)
3478                      return sibling;
3479              }
3480          }
3481      };
3482  
3483  	function Node(name, type) {
3484          this.name = name;
3485          this.type = type;
3486  
3487          if (type === 1) {
3488              this.attributes = [];
3489              this.attributes.map = {};
3490          }
3491      }
3492  
3493      tinymce.extend(Node.prototype, {
3494          replace : function(node) {
3495              var self = this;
3496  
3497              if (node.parent)
3498                  node.remove();
3499  
3500              self.insert(node, self);
3501              self.remove();
3502  
3503              return self;
3504          },
3505  
3506          attr : function(name, value) {
3507              var self = this, attrs, i, undef;
3508  
3509              if (typeof name !== "string") {
3510                  for (i in name)
3511                      self.attr(i, name[i]);
3512  
3513                  return self;
3514              }
3515  
3516              if (attrs = self.attributes) {
3517                  if (value !== undef) {
3518                      // Remove attribute
3519                      if (value === null) {
3520                          if (name in attrs.map) {
3521                              delete attrs.map[name];
3522  
3523                              i = attrs.length;
3524                              while (i--) {
3525                                  if (attrs[i].name === name) {
3526                                      attrs = attrs.splice(i, 1);
3527                                      return self;
3528                                  }
3529                              }
3530                          }
3531  
3532                          return self;
3533                      }
3534  
3535                      // Set attribute
3536                      if (name in attrs.map) {
3537                          // Set attribute
3538                          i = attrs.length;
3539                          while (i--) {
3540                              if (attrs[i].name === name) {
3541                                  attrs[i].value = value;
3542                                  break;
3543                              }
3544                          }
3545                      } else
3546                          attrs.push({name: name, value: value});
3547  
3548                      attrs.map[name] = value;
3549  
3550                      return self;
3551                  } else {
3552                      return attrs.map[name];
3553                  }
3554              }
3555          },
3556  
3557          clone : function() {
3558              var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3559  
3560              // Clone element attributes
3561              if (selfAttrs = self.attributes) {
3562                  cloneAttrs = [];
3563                  cloneAttrs.map = {};
3564  
3565                  for (i = 0, l = selfAttrs.length; i < l; i++) {
3566                      selfAttr = selfAttrs[i];
3567  
3568                      // Clone everything except id
3569                      if (selfAttr.name !== 'id') {
3570                          cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3571                          cloneAttrs.map[selfAttr.name] = selfAttr.value;
3572                      }
3573                  }
3574  
3575                  clone.attributes = cloneAttrs;
3576              }
3577  
3578              clone.value = self.value;
3579              clone.shortEnded = self.shortEnded;
3580  
3581              return clone;
3582          },
3583  
3584          wrap : function(wrapper) {
3585              var self = this;
3586  
3587              self.parent.insert(wrapper, self);
3588              wrapper.append(self);
3589  
3590              return self;
3591          },
3592  
3593          unwrap : function() {
3594              var self = this, node, next;
3595  
3596              for (node = self.firstChild; node; ) {
3597                  next = node.next;
3598                  self.insert(node, self, true);
3599                  node = next;
3600              }
3601  
3602              self.remove();
3603          },
3604  
3605          remove : function() {
3606              var self = this, parent = self.parent, next = self.next, prev = self.prev;
3607  
3608              if (parent) {
3609                  if (parent.firstChild === self) {
3610                      parent.firstChild = next;
3611  
3612                      if (next)
3613                          next.prev = null;
3614                  } else {
3615                      prev.next = next;
3616                  }
3617  
3618                  if (parent.lastChild === self) {
3619                      parent.lastChild = prev;
3620  
3621                      if (prev)
3622                          prev.next = null;
3623                  } else {
3624                      next.prev = prev;
3625                  }
3626  
3627                  self.parent = self.next = self.prev = null;
3628              }
3629  
3630              return self;
3631          },
3632  
3633          append : function(node) {
3634              var self = this, last;
3635  
3636              if (node.parent)
3637                  node.remove();
3638  
3639              last = self.lastChild;
3640              if (last) {
3641                  last.next = node;
3642                  node.prev = last;
3643                  self.lastChild = node;
3644              } else
3645                  self.lastChild = self.firstChild = node;
3646  
3647              node.parent = self;
3648  
3649              return node;
3650          },
3651  
3652          insert : function(node, ref_node, before) {
3653              var parent;
3654  
3655              if (node.parent)
3656                  node.remove();
3657  
3658              parent = ref_node.parent || this;
3659  
3660              if (before) {
3661                  if (ref_node === parent.firstChild)
3662                      parent.firstChild = node;
3663                  else
3664                      ref_node.prev.next = node;
3665  
3666                  node.prev = ref_node.prev;
3667                  node.next = ref_node;
3668                  ref_node.prev = node;
3669              } else {
3670                  if (ref_node === parent.lastChild)
3671                      parent.lastChild = node;
3672                  else
3673                      ref_node.next.prev = node;
3674  
3675                  node.next = ref_node.next;
3676                  node.prev = ref_node;
3677                  ref_node.next = node;
3678              }
3679  
3680              node.parent = parent;
3681  
3682              return node;
3683          },
3684  
3685          getAll : function(name) {
3686              var self = this, node, collection = [];
3687  
3688              for (node = self.firstChild; node; node = walk(node, self)) {
3689                  if (node.name === name)
3690                      collection.push(node);
3691              }
3692  
3693              return collection;
3694          },
3695  
3696          empty : function() {
3697              var self = this, nodes, i, node;
3698  
3699              // Remove all children
3700              if (self.firstChild) {
3701                  nodes = [];
3702  
3703                  // Collect the children
3704                  for (node = self.firstChild; node; node = walk(node, self))
3705                      nodes.push(node);
3706  
3707                  // Remove the children
3708                  i = nodes.length;
3709                  while (i--) {
3710                      node = nodes[i];
3711                      node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3712                  }
3713              }
3714  
3715              self.firstChild = self.lastChild = null;
3716  
3717              return self;
3718          },
3719  
3720          isEmpty : function(elements) {
3721              var self = this, node = self.firstChild, i, name;
3722  
3723              if (node) {
3724                  do {
3725                      if (node.type === 1) {
3726                          // Ignore bogus elements
3727                          if (node.attributes.map['data-mce-bogus'])
3728                              continue;
3729  
3730                          // Keep empty elements like <img />
3731                          if (elements[node.name])
3732                              return false;
3733  
3734                          // Keep elements with data attributes or name attribute like <a name="1"></a>
3735                          i = node.attributes.length;
3736                          while (i--) {
3737                              name = node.attributes[i].name;
3738                              if (name === "name" || name.indexOf('data-mce-') === 0)
3739                                  return false;
3740                          }
3741                      }
3742  
3743                      // Keep comments
3744                      if (node.type === 8)
3745                          return false;
3746                      
3747                      // Keep non whitespace text nodes
3748                      if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3749                          return false;
3750                  } while (node = walk(node, self));
3751              }
3752  
3753              return true;
3754          },
3755  
3756          walk : function(prev) {
3757              return walk(this, null, prev);
3758          }
3759      });
3760  
3761      tinymce.extend(Node, {
3762          create : function(name, attrs) {
3763              var node, attrName;
3764  
3765              // Create node
3766              node = new Node(name, typeLookup[name] || 1);
3767  
3768              // Add attributes if needed
3769              if (attrs) {
3770                  for (attrName in attrs)
3771                      node.attr(attrName, attrs[attrName]);
3772              }
3773  
3774              return node;
3775          }
3776      });
3777  
3778      tinymce.html.Node = Node;
3779  })(tinymce);
3780  (function(tinymce) {
3781      var Node = tinymce.html.Node;
3782  
3783      tinymce.html.DomParser = function(settings, schema) {
3784          var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3785  
3786          settings = settings || {};
3787          settings.validate = "validate" in settings ? settings.validate : true;
3788          settings.root_name = settings.root_name || 'body';
3789          self.schema = schema = schema || new tinymce.html.Schema();
3790  
3791  		function fixInvalidChildren(nodes) {
3792              var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3793                  childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;
3794  
3795              nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3796              nonEmptyElements = schema.getNonEmptyElements();
3797              textBlockElements = schema.getTextBlockElements();
3798  
3799              for (ni = 0; ni < nodes.length; ni++) {
3800                  node = nodes[ni];
3801  
3802                  // Already removed or fixed
3803                  if (!node.parent || node.fixed)
3804                      continue;
3805  
3806                  // If the invalid element is a text block and the text block is within a parent LI element
3807                  // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office
3808                  if (textBlockElements[node.name] && node.parent.name == 'li') {
3809                      // Move sibling text blocks after LI element
3810                      sibling = node.next;
3811                      while (sibling) {
3812                          if (textBlockElements[sibling.name]) {
3813                              sibling.name = 'li';
3814                              sibling.fixed = true;
3815                              node.parent.insert(sibling, node.parent);
3816                          } else {
3817                              break;
3818                          }
3819  
3820                          sibling = sibling.next;
3821                      }
3822  
3823                      // Unwrap current text block
3824                      node.unwrap(node);
3825                      continue;
3826                  }
3827  
3828                  // Get list of all parent nodes until we find a valid parent to stick the child into
3829                  parents = [node];
3830                  for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3831                      parents.push(parent);
3832  
3833                  // Found a suitable parent
3834                  if (parent && parents.length > 1) {
3835                      // Reverse the array since it makes looping easier
3836                      parents.reverse();
3837  
3838                      // Clone the related parent and insert that after the moved node
3839                      newParent = currentNode = self.filterNode(parents[0].clone());
3840  
3841                      // Start cloning and moving children on the left side of the target node
3842                      for (i = 0; i < parents.length - 1; i++) {
3843                          if (schema.isValidChild(currentNode.name, parents[i].name)) {
3844                              tempNode = self.filterNode(parents[i].clone());
3845                              currentNode.append(tempNode);
3846                          } else
3847                              tempNode = currentNode;
3848  
3849                          for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3850                              nextNode = childNode.next;
3851                              tempNode.append(childNode);
3852                              childNode = nextNode;
3853                          }
3854  
3855                          currentNode = tempNode;
3856                      }
3857  
3858                      if (!newParent.isEmpty(nonEmptyElements)) {
3859                          parent.insert(newParent, parents[0], true);
3860                          parent.insert(node, newParent);
3861                      } else {
3862                          parent.insert(node, parents[0], true);
3863                      }
3864  
3865                      // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3866                      parent = parents[0];
3867                      if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3868                          parent.empty().remove();
3869                      }
3870                  } else if (node.parent) {
3871                      // If it's an LI try to find a UL/OL for it or wrap it
3872                      if (node.name === 'li') {
3873                          sibling = node.prev;
3874                          if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3875                              sibling.append(node);
3876                              continue;
3877                          }
3878  
3879                          sibling = node.next;
3880                          if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3881                              sibling.insert(node, sibling.firstChild, true);
3882                              continue;
3883                          }
3884  
3885                          node.wrap(self.filterNode(new Node('ul', 1)));
3886                          continue;
3887                      }
3888  
3889                      // Try wrapping the element in a DIV
3890                      if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3891                          node.wrap(self.filterNode(new Node('div', 1)));
3892                      } else {
3893                          // We failed wrapping it, then remove or unwrap it
3894                          if (node.name === 'style' || node.name === 'script')
3895                              node.empty().remove();
3896                          else
3897                              node.unwrap();
3898                      }
3899                  }
3900              }
3901          };
3902  
3903          self.filterNode = function(node) {
3904              var i, name, list;
3905  
3906              // Run element filters
3907              if (name in nodeFilters) {
3908                  list = matchedNodes[name];
3909  
3910                  if (list)
3911                      list.push(node);
3912                  else
3913                      matchedNodes[name] = [node];
3914              }
3915  
3916              // Run attribute filters
3917              i = attributeFilters.length;
3918              while (i--) {
3919                  name = attributeFilters[i].name;
3920  
3921                  if (name in node.attributes.map) {
3922                      list = matchedAttributes[name];
3923  
3924                      if (list)
3925                          list.push(node);
3926                      else
3927                          matchedAttributes[name] = [node];
3928                  }
3929              }
3930  
3931              return node;
3932          };
3933  
3934          self.addNodeFilter = function(name, callback) {
3935              tinymce.each(tinymce.explode(name), function(name) {
3936                  var list = nodeFilters[name];
3937  
3938                  if (!list)
3939                      nodeFilters[name] = list = [];
3940  
3941                  list.push(callback);
3942              });
3943          };
3944  
3945          self.addAttributeFilter = function(name, callback) {
3946              tinymce.each(tinymce.explode(name), function(name) {
3947                  var i;
3948  
3949                  for (i = 0; i < attributeFilters.length; i++) {
3950                      if (attributeFilters[i].name === name) {
3951                          attributeFilters[i].callbacks.push(callback);
3952                          return;
3953                      }
3954                  }
3955  
3956                  attributeFilters.push({name: name, callbacks: [callback]});
3957              });
3958          };
3959  
3960          self.parse = function(html, args) {
3961              var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3962                  blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3963                  endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3964  
3965              args = args || {};
3966              matchedNodes = {};
3967              matchedAttributes = {};
3968              blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3969              nonEmptyElements = schema.getNonEmptyElements();
3970              children = schema.children;
3971              validate = settings.validate;
3972              rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3973  
3974              whiteSpaceElements = schema.getWhiteSpaceElements();
3975              startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3976              endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3977              allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3978              isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3979  
3980  			function addRootBlocks() {
3981                  var node = rootNode.firstChild, next, rootBlockNode;
3982  
3983                  while (node) {
3984                      next = node.next;
3985  
3986                      if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3987                          if (!rootBlockNode) {
3988                              // Create a new root block element
3989                              rootBlockNode = createNode(rootBlockName, 1);
3990                              rootNode.insert(rootBlockNode, node);
3991                              rootBlockNode.append(node);
3992                          } else
3993                              rootBlockNode.append(node);
3994                      } else {
3995                          rootBlockNode = null;
3996                      }
3997  
3998                      node = next;
3999                  };
4000              };
4001  
4002  			function createNode(name, type) {
4003                  var node = new Node(name, type), list;
4004  
4005                  if (name in nodeFilters) {
4006                      list = matchedNodes[name];
4007  
4008                      if (list)
4009                          list.push(node);
4010                      else
4011                          matchedNodes[name] = [node];
4012                  }
4013  
4014                  return node;
4015              };
4016  
4017  			function removeWhitespaceBefore(node) {
4018                  var textNode, textVal, sibling;
4019  
4020                  for (textNode = node.prev; textNode && textNode.type === 3; ) {
4021                      textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
4022  
4023                      if (textVal.length > 0) {
4024                          textNode.value = textVal;
4025                          textNode = textNode.prev;
4026                      } else {
4027                          sibling = textNode.prev;
4028                          textNode.remove();
4029                          textNode = sibling;
4030                      }
4031                  }
4032              };
4033  
4034  			function cloneAndExcludeBlocks(input) {
4035                  var name, output = {};
4036  
4037                  for (name in input) {
4038                      if (name !== 'li' && name != 'p') {
4039                          output[name] = input[name];
4040                      }
4041                  }
4042  
4043                  return output;
4044              };
4045  
4046              parser = new tinymce.html.SaxParser({
4047                  validate : validate,
4048  
4049                  // Exclude P and LI from DOM parsing since it's treated better by the DOM parser
4050                  self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
4051  
4052                  cdata: function(text) {
4053                      node.append(createNode('#cdata', 4)).value = text;
4054                  },
4055  
4056                  text: function(text, raw) {
4057                      var textNode;
4058  
4059                      // Trim all redundant whitespace on non white space elements
4060                      if (!isInWhiteSpacePreservedElement) {
4061                          text = text.replace(allWhiteSpaceRegExp, ' ');
4062  
4063                          if (node.lastChild && blockElements[node.lastChild.name])
4064                              text = text.replace(startWhiteSpaceRegExp, '');
4065                      }
4066  
4067                      // Do we need to create the node
4068                      if (text.length !== 0) {
4069                          textNode = createNode('#text', 3);
4070                          textNode.raw = !!raw;
4071                          node.append(textNode).value = text;
4072                      }
4073                  },
4074  
4075                  comment: function(text) {
4076                      node.append(createNode('#comment', 8)).value = text;
4077                  },
4078  
4079                  pi: function(name, text) {
4080                      node.append(createNode(name, 7)).value = text;
4081                      removeWhitespaceBefore(node);
4082                  },
4083  
4084                  doctype: function(text) {
4085                      var newNode;
4086          
4087                      newNode = node.append(createNode('#doctype', 10));
4088                      newNode.value = text;
4089                      removeWhitespaceBefore(node);
4090                  },
4091  
4092                  start: function(name, attrs, empty) {
4093                      var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4094  
4095                      elementRule = validate ? schema.getElementRule(name) : {};
4096                      if (elementRule) {
4097                          newNode = createNode(elementRule.outputName || name, 1);
4098                          newNode.attributes = attrs;
4099                          newNode.shortEnded = empty;
4100  
4101                          node.append(newNode);
4102  
4103                          // Check if node is valid child of the parent node is the child is
4104                          // unknown we don't collect it since it's probably a custom element
4105                          parent = children[node.name];
4106                          if (parent && children[newNode.name] && !parent[newNode.name])
4107                              invalidChildren.push(newNode);
4108  
4109                          attrFiltersLen = attributeFilters.length;
4110                          while (attrFiltersLen--) {
4111                              attrName = attributeFilters[attrFiltersLen].name;
4112  
4113                              if (attrName in attrs.map) {
4114                                  list = matchedAttributes[attrName];
4115  
4116                                  if (list)
4117                                      list.push(newNode);
4118                                  else
4119                                      matchedAttributes[attrName] = [newNode];
4120                              }
4121                          }
4122  
4123                          // Trim whitespace before block
4124                          if (blockElements[name])
4125                              removeWhitespaceBefore(newNode);
4126  
4127                          // Change current node if the element wasn't empty i.e not <br /> or <img />
4128                          if (!empty)
4129                              node = newNode;
4130  
4131                          // Check if we are inside a whitespace preserved element
4132                          if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4133                              isInWhiteSpacePreservedElement = true;
4134                          }
4135                      }
4136                  },
4137  
4138                  end: function(name) {
4139                      var textNode, elementRule, text, sibling, tempNode;
4140  
4141                      elementRule = validate ? schema.getElementRule(name) : {};
4142                      if (elementRule) {
4143                          if (blockElements[name]) {
4144                              if (!isInWhiteSpacePreservedElement) {
4145                                  // Trim whitespace of the first node in a block
4146                                  textNode = node.firstChild;
4147                                  if (textNode && textNode.type === 3) {
4148                                      text = textNode.value.replace(startWhiteSpaceRegExp, '');
4149  
4150                                      // Any characters left after trim or should we remove it
4151                                      if (text.length > 0) {
4152                                          textNode.value = text;
4153                                          textNode = textNode.next;
4154                                      } else {
4155                                          sibling = textNode.next;
4156                                          textNode.remove();
4157                                          textNode = sibling;
4158                                      }
4159  
4160                                      // Remove any pure whitespace siblings
4161                                      while (textNode && textNode.type === 3) {
4162                                          text = textNode.value;
4163                                          sibling = textNode.next;
4164  
4165                                          if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4166                                              textNode.remove();
4167                                              textNode = sibling;
4168                                          }
4169  
4170                                          textNode = sibling;
4171                                      }
4172                                  }
4173  
4174                                  // Trim whitespace of the last node in a block
4175                                  textNode = node.lastChild;
4176                                  if (textNode && textNode.type === 3) {
4177                                      text = textNode.value.replace(endWhiteSpaceRegExp, '');
4178  
4179                                      // Any characters left after trim or should we remove it
4180                                      if (text.length > 0) {
4181                                          textNode.value = text;
4182                                          textNode = textNode.prev;
4183                                      } else {
4184                                          sibling = textNode.prev;
4185                                          textNode.remove();
4186                                          textNode = sibling;
4187                                      }
4188  
4189                                      // Remove any pure whitespace siblings
4190                                      while (textNode && textNode.type === 3) {
4191                                          text = textNode.value;
4192                                          sibling = textNode.prev;
4193  
4194                                          if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4195                                              textNode.remove();
4196                                              textNode = sibling;
4197                                          }
4198  
4199                                          textNode = sibling;
4200                                      }
4201                                  }
4202                              }
4203  
4204                              // Trim start white space
4205                              // Removed due to: #5424
4206                              /*textNode = node.prev;
4207                              if (textNode && textNode.type === 3) {
4208                                  text = textNode.value.replace(startWhiteSpaceRegExp, '');
4209  
4210                                  if (text.length > 0)
4211                                      textNode.value = text;
4212                                  else
4213                                      textNode.remove();
4214                              }*/
4215                          }
4216  
4217                          // Check if we exited a whitespace preserved element
4218                          if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4219                              isInWhiteSpacePreservedElement = false;
4220                          }
4221  
4222                          // Handle empty nodes
4223                          if (elementRule.removeEmpty || elementRule.paddEmpty) {
4224                              if (node.isEmpty(nonEmptyElements)) {
4225                                  if (elementRule.paddEmpty)
4226                                      node.empty().append(new Node('#text', '3')).value = '\u00a0';
4227                                  else {
4228                                      // Leave nodes that have a name like <a name="name">
4229                                      if (!node.attributes.map.name && !node.attributes.map.id) {
4230                                          tempNode = node.parent;
4231                                          node.empty().remove();
4232                                          node = tempNode;
4233                                          return;
4234                                      }
4235                                  }
4236                              }
4237                          }
4238  
4239                          node = node.parent;
4240                      }
4241                  }
4242              }, schema);
4243  
4244              rootNode = node = new Node(args.context || settings.root_name, 11);
4245  
4246              parser.parse(html);
4247  
4248              // Fix invalid children or report invalid children in a contextual parsing
4249              if (validate && invalidChildren.length) {
4250                  if (!args.context)
4251                      fixInvalidChildren(invalidChildren);
4252                  else
4253                      args.invalid = true;
4254              }
4255  
4256              // Wrap nodes in the root into block elements if the root is body
4257              if (rootBlockName && rootNode.name == 'body')
4258                  addRootBlocks();
4259  
4260              // Run filters only when the contents is valid
4261              if (!args.invalid) {
4262                  // Run node filters
4263                  for (name in matchedNodes) {
4264                      list = nodeFilters[name];
4265                      nodes = matchedNodes[name];
4266  
4267                      // Remove already removed children
4268                      fi = nodes.length;
4269                      while (fi--) {
4270                          if (!nodes[fi].parent)
4271                              nodes.splice(fi, 1);
4272                      }
4273  
4274                      for (i = 0, l = list.length; i < l; i++)
4275                          list[i](nodes, name, args);
4276                  }
4277  
4278                  // Run attribute filters
4279                  for (i = 0, l = attributeFilters.length; i < l; i++) {
4280                      list = attributeFilters[i];
4281  
4282                      if (list.name in matchedAttributes) {
4283                          nodes = matchedAttributes[list.name];
4284  
4285                          // Remove already removed children
4286                          fi = nodes.length;
4287                          while (fi--) {
4288                              if (!nodes[fi].parent)
4289                                  nodes.splice(fi, 1);
4290                          }
4291  
4292                          for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4293                              list.callbacks[fi](nodes, list.name, args);
4294                      }
4295                  }
4296              }
4297  
4298              return rootNode;
4299          };
4300  
4301          // Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4302          // make it possible to place the caret inside empty blocks. This logic tries to remove
4303          // these elements and keep br elements that where intended to be there intact
4304          if (settings.remove_trailing_brs) {
4305              self.addNodeFilter('br', function(nodes, name) {
4306                  var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4307                      nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4308  
4309                  // Remove brs from body element as well
4310                  blockElements.body = 1;
4311  
4312                  // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4313                  for (i = 0; i < l; i++) {
4314                      node = nodes[i];
4315                      parent = node.parent;
4316  
4317                      if (blockElements[node.parent.name] && node === parent.lastChild) {
4318                          // Loop all nodes to the left of the current node and check for other BR elements
4319                          // excluding bookmarks since they are invisible
4320                          prev = node.prev;
4321                          while (prev) {
4322                              prevName = prev.name;
4323  
4324                              // Ignore bookmarks
4325                              if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4326                                  // Found a non BR element
4327                                  if (prevName !== "br")
4328                                      break;
4329      
4330                                  // Found another br it's a <br><br> structure then don't remove anything
4331                                  if (prevName === 'br') {
4332                                      node = null;
4333                                      break;
4334                                  }
4335                              }
4336  
4337                              prev = prev.prev;
4338                          }
4339  
4340                          if (node) {
4341                              node.remove();
4342  
4343                              // Is the parent to be considered empty after we removed the BR
4344                              if (parent.isEmpty(nonEmptyElements)) {
4345                                  elementRule = schema.getElementRule(parent.name);
4346  
4347                                  // Remove or padd the element depending on schema rule
4348                                  if (elementRule) {
4349                                      if (elementRule.removeEmpty)
4350                                          parent.remove();
4351                                      else if (elementRule.paddEmpty)
4352                                          parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4353                                  }
4354                              }
4355                          }
4356                      } else {
4357                          // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i>&nbsp;</i></b></p> 
4358                          lastParent = node;
4359                          while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4360                              lastParent = parent;
4361  
4362                              if (blockElements[parent.name]) {
4363                                  break;
4364                              }
4365  
4366                              parent = parent.parent;
4367                          }
4368  
4369                          if (lastParent === parent) {
4370                              textNode = new tinymce.html.Node('#text', 3);
4371                              textNode.value = '\u00a0';
4372                              node.replace(textNode);
4373                          }
4374                      }
4375                  }
4376              });
4377          }
4378  
4379          // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4380          if (!settings.allow_html_in_named_anchor) {
4381              self.addAttributeFilter('id,name', function(nodes, name) {
4382                  var i = nodes.length, sibling, prevSibling, parent, node;
4383  
4384                  while (i--) {
4385                      node = nodes[i];
4386                      if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4387                          parent = node.parent;
4388  
4389                          // Move children after current node
4390                          sibling = node.lastChild;
4391                          do {
4392                              prevSibling = sibling.prev;
4393                              parent.insert(sibling, node);
4394                              sibling = prevSibling;
4395                          } while (sibling);
4396                      }
4397                  }
4398              });
4399          }
4400      }
4401  })(tinymce);
4402  tinymce.html.Writer = function(settings) {
4403      var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4404  
4405      settings = settings || {};
4406      indent = settings.indent;
4407      indentBefore = tinymce.makeMap(settings.indent_before || '');
4408      indentAfter = tinymce.makeMap(settings.indent_after || '');
4409      encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4410      htmlOutput = settings.element_format == "html";
4411  
4412      return {
4413          start: function(name, attrs, empty) {
4414              var i, l, attr, value;
4415  
4416              if (indent && indentBefore[name] && html.length > 0) {
4417                  value = html[html.length - 1];
4418  
4419                  if (value.length > 0 && value !== '\n')
4420                      html.push('\n');
4421              }
4422  
4423              html.push('<', name);
4424  
4425              if (attrs) {
4426                  for (i = 0, l = attrs.length; i < l; i++) {
4427                      attr = attrs[i];
4428                      html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4429                  }
4430              }
4431  
4432              if (!empty || htmlOutput)
4433                  html[html.length] = '>';
4434              else
4435                  html[html.length] = ' />';
4436  
4437              if (empty && indent && indentAfter[name] && html.length > 0) {
4438                  value = html[html.length - 1];
4439  
4440                  if (value.length > 0 && value !== '\n')
4441                      html.push('\n');
4442              }
4443          },
4444  
4445          end: function(name) {
4446              var value;
4447  
4448              /*if (indent && indentBefore[name] && html.length > 0) {
4449                  value = html[html.length - 1];
4450  
4451                  if (value.length > 0 && value !== '\n')
4452                      html.push('\n');
4453              }*/
4454  
4455              html.push('</', name, '>');
4456  
4457              if (indent && indentAfter[name] && html.length > 0) {
4458                  value = html[html.length - 1];
4459  
4460                  if (value.length > 0 && value !== '\n')
4461                      html.push('\n');
4462              }
4463          },
4464  
4465          text: function(text, raw) {
4466              if (text.length > 0)
4467                  html[html.length] = raw ? text : encode(text);
4468          },
4469  
4470          cdata: function(text) {
4471              html.push('<![CDATA[', text, ']]>');
4472          },
4473  
4474          comment: function(text) {
4475              html.push('<!--', text, '-->');
4476          },
4477  
4478          pi: function(name, text) {
4479              if (text)
4480                  html.push('<?', name, ' ', text, '?>');
4481              else
4482                  html.push('<?', name, '?>');
4483  
4484              if (indent)
4485                  html.push('\n');
4486          },
4487  
4488          doctype: function(text) {
4489              html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4490          },
4491  
4492          reset: function() {
4493              html.length = 0;
4494          },
4495  
4496          getContent: function() {
4497              return html.join('').replace(/\n$/, '');
4498          }
4499      };
4500  };
4501  (function(tinymce) {
4502      tinymce.html.Serializer = function(settings, schema) {
4503          var self = this, writer = new tinymce.html.Writer(settings);
4504  
4505          settings = settings || {};
4506          settings.validate = "validate" in settings ? settings.validate : true;
4507  
4508          self.schema = schema = schema || new tinymce.html.Schema();
4509          self.writer = writer;
4510  
4511          self.serialize = function(node) {
4512              var handlers, validate;
4513  
4514              validate = settings.validate;
4515  
4516              handlers = {
4517                  // #text
4518                  3: function(node, raw) {
4519                      writer.text(node.value, node.raw);
4520                  },
4521  
4522                  // #comment
4523                  8: function(node) {
4524                      writer.comment(node.value);
4525                  },
4526  
4527                  // Processing instruction
4528                  7: function(node) {
4529                      writer.pi(node.name, node.value);
4530                  },
4531  
4532                  // Doctype
4533                  10: function(node) {
4534                      writer.doctype(node.value);
4535                  },
4536  
4537                  // CDATA
4538                  4: function(node) {
4539                      writer.cdata(node.value);
4540                  },
4541  
4542                  // Document fragment
4543                  11: function(node) {
4544                      if ((node = node.firstChild)) {
4545                          do {
4546                              walk(node);
4547                          } while (node = node.next);
4548                      }
4549                  }
4550              };
4551  
4552              writer.reset();
4553  
4554  			function walk(node) {
4555                  var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4556  
4557                  if (!handler) {
4558                      name = node.name;
4559                      isEmpty = node.shortEnded;
4560                      attrs = node.attributes;
4561  
4562                      // Sort attributes
4563                      if (validate && attrs && attrs.length > 1) {
4564                          sortedAttrs = [];
4565                          sortedAttrs.map = {};
4566  
4567                          elementRule = schema.getElementRule(node.name);
4568                          for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4569                              attrName = elementRule.attributesOrder[i];
4570  
4571                              if (attrName in attrs.map) {
4572                                  attrValue = attrs.map[attrName];
4573                                  sortedAttrs.map[attrName] = attrValue;
4574                                  sortedAttrs.push({name: attrName, value: attrValue});
4575                              }
4576                          }
4577  
4578                          for (i = 0, l = attrs.length; i < l; i++) {
4579                              attrName = attrs[i].name;
4580  
4581                              if (!(attrName in sortedAttrs.map)) {
4582                                  attrValue = attrs.map[attrName];
4583                                  sortedAttrs.map[attrName] = attrValue;
4584                                  sortedAttrs.push({name: attrName, value: attrValue});
4585                              }
4586                          }
4587  
4588                          attrs = sortedAttrs;
4589                      }
4590  
4591                      writer.start(node.name, attrs, isEmpty);
4592  
4593                      if (!isEmpty) {
4594                          if ((node = node.firstChild)) {
4595                              do {
4596                                  walk(node);
4597                              } while (node = node.next);
4598                          }
4599  
4600                          writer.end(name);
4601                      }
4602                  } else
4603                      handler(node);
4604              }
4605  
4606              // Serialize element and treat all non elements as fragments
4607              if (node.type == 1 && !settings.inner)
4608                  walk(node);
4609              else
4610                  handlers[11](node);
4611  
4612              return writer.getContent();
4613          };
4614      }
4615  })(tinymce);
4616  // JSLint defined globals
4617  /*global tinymce:false, window:false */
4618  
4619  tinymce.dom = {};
4620  
4621  (function(namespace, expando) {
4622      var w3cEventModel = !!document.addEventListener;
4623  
4624  	function addEvent(target, name, callback, capture) {
4625          if (target.addEventListener) {
4626              target.addEventListener(name, callback, capture || false);
4627          } else if (target.attachEvent) {
4628              target.attachEvent('on' + name, callback);
4629          }
4630      }
4631  
4632  	function removeEvent(target, name, callback, capture) {
4633          if (target.removeEventListener) {
4634              target.removeEventListener(name, callback, capture || false);
4635          } else if (target.detachEvent) {
4636              target.detachEvent('on' + name, callback);
4637          }
4638      }
4639  
4640  	function fix(original_event, data) {
4641          var name, event = data || {};
4642  
4643          // Dummy function that gets replaced on the delegation state functions
4644  		function returnFalse() {
4645              return false;
4646          }
4647  
4648          // Dummy function that gets replaced on the delegation state functions
4649  		function returnTrue() {
4650              return true;
4651          }
4652  
4653          // Copy all properties from the original event
4654          for (name in original_event) {
4655              // layerX/layerY is deprecated in Chrome and produces a warning
4656              if (name !== "layerX" && name !== "layerY") {
4657                  event[name] = original_event[name];
4658              }
4659          }
4660  
4661          // Normalize target IE uses srcElement
4662          if (!event.target) {
4663              event.target = event.srcElement || document;
4664          }
4665  
4666          // Add preventDefault method
4667          event.preventDefault = function() {
4668              event.isDefaultPrevented = returnTrue;
4669  
4670              // Execute preventDefault on the original event object
4671              if (original_event) {
4672                  if (original_event.preventDefault) {
4673                      original_event.preventDefault();
4674                  } else {
4675                      original_event.returnValue = false; // IE
4676                  }
4677              }
4678          };
4679  
4680          // Add stopPropagation
4681          event.stopPropagation = function() {
4682              event.isPropagationStopped = returnTrue;
4683  
4684              // Execute stopPropagation on the original event object
4685              if (original_event) {
4686                  if (original_event.stopPropagation) {
4687                      original_event.stopPropagation();
4688                  } else {
4689                      original_event.cancelBubble = true; // IE
4690                  }
4691               }
4692          };
4693  
4694          // Add stopImmediatePropagation
4695          event.stopImmediatePropagation = function() {
4696              event.isImmediatePropagationStopped = returnTrue;
4697              event.stopPropagation();
4698          };
4699  
4700          // Add event delegation states
4701          if (!event.isDefaultPrevented) {
4702              event.isDefaultPrevented = returnFalse;
4703              event.isPropagationStopped = returnFalse;
4704              event.isImmediatePropagationStopped = returnFalse;
4705          }
4706  
4707          return event;
4708      }
4709  
4710  	function bindOnReady(win, callback, event_utils) {
4711          var doc = win.document, event = {type: 'ready'};
4712  
4713          // Gets called when the DOM is ready
4714  		function readyHandler() {
4715              if (!event_utils.domLoaded) {
4716                  event_utils.domLoaded = true;
4717                  callback(event);
4718              }
4719          }
4720  
4721          // Page already loaded then fire it directly
4722          if (doc.readyState == "complete") {
4723              readyHandler();
4724              return;
4725          }
4726  
4727          // Use W3C method
4728          if (w3cEventModel) {
4729              addEvent(win, 'DOMContentLoaded', readyHandler);
4730          } else {
4731              // Use IE method
4732              addEvent(doc, "readystatechange", function() {
4733                  if (doc.readyState === "complete") {
4734                      removeEvent(doc, "readystatechange", arguments.callee);
4735                      readyHandler();
4736                  }
4737              });
4738  
4739              // Wait until we can scroll, when we can the DOM is initialized
4740              if (doc.documentElement.doScroll && win === win.top) {
4741                  (function() {
4742                      try {
4743                          // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4744                          // http://javascript.nwbox.com/IEContentLoaded/
4745                          doc.documentElement.doScroll("left");
4746                      } catch (ex) {
4747                          setTimeout(arguments.callee, 0);
4748                          return;
4749                      }
4750  
4751                      readyHandler();
4752                  })();
4753              }
4754          }
4755  
4756          // Fallback if any of the above methods should fail for some odd reason
4757          addEvent(win, 'load', readyHandler);
4758      }
4759  
4760  	function EventUtils(proxy) {
4761          var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4762  
4763          hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4764          hasFocusIn = "onfocusin" in document.documentElement;
4765          mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4766          count = 1;
4767  
4768          // State if the DOMContentLoaded was executed or not
4769          self.domLoaded = false;
4770          self.events = events;
4771  
4772  		function executeHandlers(evt, id) {
4773              var callbackList, i, l, callback;
4774  
4775              callbackList = events[id][evt.type];
4776              if (callbackList) {
4777                  for (i = 0, l = callbackList.length; i < l; i++) {
4778                      callback = callbackList[i];
4779                      
4780                      // Check if callback exists might be removed if a unbind is called inside the callback
4781                      if (callback && callback.func.call(callback.scope, evt) === false) {
4782                          evt.preventDefault();
4783                      }
4784  
4785                      // Should we stop propagation to immediate listeners
4786                      if (evt.isImmediatePropagationStopped()) {
4787                          return;
4788                      }
4789                  }
4790              }
4791          }
4792  
4793          self.bind = function(target, names, callback, scope) {
4794              var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4795  
4796              // Native event handler function patches the event and executes the callbacks for the expando
4797  			function defaultNativeHandler(evt) {
4798                  executeHandlers(fix(evt || win.event), id);
4799              }
4800  
4801              // Don't bind to text nodes or comments
4802              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4803                  return;
4804              }
4805  
4806              // Create or get events id for the target
4807              if (!target[expando]) {
4808                  id = count++;
4809                  target[expando] = id;
4810                  events[id] = {};
4811              } else {
4812                  id = target[expando];
4813  
4814                  if (!events[id]) {
4815                      events[id] = {};
4816                  }
4817              }
4818  
4819              // Setup the specified scope or use the target as a default
4820              scope = scope || target;
4821  
4822              // Split names and bind each event, enables you to bind multiple events with one call
4823              names = names.split(' ');
4824              i = names.length;
4825              while (i--) {
4826                  name = names[i];
4827                  nativeHandler = defaultNativeHandler;
4828                  fakeName = capture = false;
4829  
4830                  // Use ready instead of DOMContentLoaded
4831                  if (name === "DOMContentLoaded") {
4832                      name = "ready";
4833                  }
4834  
4835                  // DOM is already ready
4836                  if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4837                      self.domLoaded = true;
4838                      callback.call(scope, fix({type: name}));
4839                      continue;
4840                  }
4841  
4842                  // Handle mouseenter/mouseleaver
4843                  if (!hasMouseEnterLeave) {
4844                      fakeName = mouseEnterLeave[name];
4845  
4846                      if (fakeName) {
4847                          nativeHandler = function(evt) {
4848                              var current, related;
4849  
4850                              current = evt.currentTarget;
4851                              related = evt.relatedTarget;
4852  
4853                              // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4854                              if (related && current.contains) {
4855                                  // Use contains for performance
4856                                  related = current.contains(related);
4857                              } else {
4858                                  while (related && related !== current) {
4859                                      related = related.parentNode;
4860                                  }
4861                              }
4862  
4863                              // Fire fake event
4864                              if (!related) {
4865                                  evt = fix(evt || win.event);
4866                                  evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4867                                  evt.target = current;
4868                                  executeHandlers(evt, id);
4869                              }
4870                          };
4871                      }
4872                  }
4873  
4874                  // Fake bubbeling of focusin/focusout
4875                  if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4876                      capture = true;
4877                      fakeName = name === "focusin" ? "focus" : "blur";
4878                      nativeHandler = function(evt) {
4879                          evt = fix(evt || win.event);
4880                          evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4881                          executeHandlers(evt, id);
4882                      };
4883                  }
4884  
4885                  // Setup callback list and bind native event
4886                  callbackList = events[id][name];
4887                  if (!callbackList) {
4888                      events[id][name] = callbackList = [{func: callback, scope: scope}];
4889                      callbackList.fakeName = fakeName;
4890                      callbackList.capture = capture;
4891  
4892                      // Add the nativeHandler to the callback list so that we can later unbind it
4893                      callbackList.nativeHandler = nativeHandler;
4894                      if (!w3cEventModel) {
4895                          callbackList.proxyHandler = proxy(id);
4896                      }
4897  
4898                      // Check if the target has native events support
4899                      if (name === "ready") {
4900                          bindOnReady(target, nativeHandler, self);
4901                      } else {
4902                          addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4903                      }
4904                  } else {
4905                      // If it already has an native handler then just push the callback
4906                      callbackList.push({func: callback, scope: scope});
4907                  }
4908              }
4909  
4910              target = callbackList = 0; // Clean memory for IE
4911  
4912              return callback;
4913          };
4914  
4915          self.unbind = function(target, names, callback) {
4916              var id, callbackList, i, ci, name, eventMap;
4917  
4918              // Don't bind to text nodes or comments
4919              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4920                  return self;
4921              }
4922  
4923              // Unbind event or events if the target has the expando
4924              id = target[expando];
4925              if (id) {
4926                  eventMap = events[id];
4927  
4928                  // Specific callback
4929                  if (names) {
4930                      names = names.split(' ');
4931                      i = names.length;
4932                      while (i--) {
4933                          name = names[i];
4934                          callbackList = eventMap[name];
4935  
4936                          // Unbind the event if it exists in the map
4937                          if (callbackList) {
4938                              // Remove specified callback
4939                              if (callback) {
4940                                  ci = callbackList.length;
4941                                  while (ci--) {
4942                                      if (callbackList[ci].func === callback) {
4943                                          callbackList.splice(ci, 1);
4944                                      }
4945                                  }
4946                              }
4947  
4948                              // Remove all callbacks if there isn't a specified callback or there is no callbacks left
4949                              if (!callback || callbackList.length === 0) {
4950                                  delete eventMap[name];
4951                                  removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4952                              }
4953                          }
4954                      }
4955                  } else {
4956                      // All events for a specific element
4957                      for (name in eventMap) {
4958                          callbackList = eventMap[name];
4959                          removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4960                      }
4961  
4962                      eventMap = {};
4963                  }
4964  
4965                  // Check if object is empty, if it isn't then we won't remove the expando map
4966                  for (name in eventMap) {
4967                      return self;
4968                  }
4969  
4970                  // Delete event object
4971                  delete events[id];
4972  
4973                  // Remove expando from target
4974                  try {
4975                      // IE will fail here since it can't delete properties from window
4976                      delete target[expando];
4977                  } catch (ex) {
4978                      // IE will set it to null
4979                      target[expando] = null;
4980                  }
4981              }
4982  
4983              return self;
4984          };
4985  
4986          self.fire = function(target, name, args) {
4987              var id, event;
4988  
4989              // Don't bind to text nodes or comments
4990              if (!target || target.nodeType === 3 || target.nodeType === 8) {
4991                  return self;
4992              }
4993  
4994              // Build event object by patching the args
4995              event = fix(null, args);
4996              event.type = name;
4997  
4998              do {
4999                  // Found an expando that means there is listeners to execute
5000                  id = target[expando];
5001                  if (id) {
5002                      executeHandlers(event, id);
5003                  }
5004  
5005                  // Walk up the DOM
5006                  target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
5007              } while (target && !event.isPropagationStopped());
5008  
5009              return self;
5010          };
5011  
5012          self.clean = function(target) {
5013              var i, children, unbind = self.unbind;
5014      
5015              // Don't bind to text nodes or comments
5016              if (!target || target.nodeType === 3 || target.nodeType === 8) {
5017                  return self;
5018              }
5019  
5020              // Unbind any element on the specificed target
5021              if (target[expando]) {
5022                  unbind(target);
5023              }
5024  
5025              // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
5026              if (!target.getElementsByTagName) {
5027                  target = target.document;
5028              }
5029  
5030              // Remove events from each child element
5031              if (target && target.getElementsByTagName) {
5032                  unbind(target);
5033  
5034                  children = target.getElementsByTagName('*');
5035                  i = children.length;
5036                  while (i--) {
5037                      target = children[i];
5038  
5039                      if (target[expando]) {
5040                          unbind(target);
5041                      }
5042                  }
5043              }
5044  
5045              return self;
5046          };
5047  
5048          self.callNativeHandler = function(id, evt) {
5049              if (events) {
5050                  events[id][evt.type].nativeHandler(evt);
5051              }
5052          };
5053  
5054          self.destory = function() {
5055              events = {};
5056          };
5057  
5058          // Legacy function calls
5059  
5060          self.add = function(target, events, func, scope) {
5061              // Old API supported direct ID assignment
5062              if (typeof(target) === "string") {
5063                  target = document.getElementById(target);
5064              }
5065  
5066              // Old API supported multiple targets
5067              if (target && target instanceof Array) {
5068                  var i = target.length;
5069  
5070                  while (i--) {
5071                      self.add(target[i], events, func, scope);
5072                  }
5073  
5074                  return;
5075              }
5076  
5077              // Old API called ready init
5078              if (events === "init") {
5079                  events = "ready";
5080              }
5081  
5082              return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5083          };
5084  
5085          self.remove = function(target, events, func, scope) {
5086              if (!target) {
5087                  return self;
5088              }
5089  
5090              // Old API supported direct ID assignment
5091              if (typeof(target) === "string") {
5092                  target = document.getElementById(target);
5093              }
5094  
5095              // Old API supported multiple targets
5096              if (target instanceof Array) {
5097                  var i = target.length;
5098  
5099                  while (i--) {
5100                      self.remove(target[i], events, func, scope);
5101                  }
5102  
5103                  return self;
5104              }
5105  
5106              return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5107          };
5108  
5109          self.clear = function(target) {
5110              // Old API supported direct ID assignment
5111              if (typeof(target) === "string") {
5112                  target = document.getElementById(target);
5113              }
5114  
5115              return self.clean(target);
5116          };
5117  
5118          self.cancel = function(e) {
5119              if (e) {
5120                  self.prevent(e);
5121                  self.stop(e);
5122              }
5123  
5124              return false;
5125          };
5126  
5127          self.prevent = function(e) {
5128              if (!e.preventDefault) {
5129                  e = fix(e);
5130              }
5131  
5132              e.preventDefault();
5133  
5134              return false;
5135          };
5136  
5137          self.stop = function(e) {
5138              if (!e.stopPropagation) {
5139                  e = fix(e);
5140              }
5141  
5142              e.stopPropagation();
5143  
5144              return false;
5145          };
5146      }
5147  
5148      namespace.EventUtils = EventUtils;
5149  
5150      namespace.Event = new EventUtils(function(id) {
5151          return function(evt) {
5152              tinymce.dom.Event.callNativeHandler(id, evt);
5153          };
5154      });
5155  
5156      // Bind ready event when tinymce script is loaded
5157      namespace.Event.bind(window, 'ready', function() {});
5158  
5159      namespace = 0;
5160  })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5161  tinymce.dom.TreeWalker = function(start_node, root_node) {
5162      var node = start_node;
5163  
5164  	function findSibling(node, start_name, sibling_name, shallow) {
5165          var sibling, parent;
5166  
5167          if (node) {
5168              // Walk into nodes if it has a start
5169              if (!shallow && node[start_name])
5170                  return node[start_name];
5171  
5172              // Return the sibling if it has one
5173              if (node != root_node) {
5174                  sibling = node[sibling_name];
5175                  if (sibling)
5176                      return sibling;
5177  
5178                  // Walk up the parents to look for siblings
5179                  for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5180                      sibling = parent[sibling_name];
5181                      if (sibling)
5182                          return sibling;
5183                  }
5184              }
5185          }
5186      };
5187  
5188      this.current = function() {
5189          return node;
5190      };
5191  
5192      this.next = function(shallow) {
5193          return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5194      };
5195  
5196      this.prev = function(shallow) {
5197          return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5198      };
5199  };
5200  (function(tinymce) {
5201      // Shorten names
5202      var each = tinymce.each,
5203          is = tinymce.is,
5204          isWebKit = tinymce.isWebKit,
5205          isIE = tinymce.isIE,
5206          Entities = tinymce.html.Entities,
5207          simpleSelectorRe = /^([a-z0-9],?)+$/i,
5208          whiteSpaceRegExp = /^[ \t\r\n]*$/;
5209  
5210      tinymce.create('tinymce.dom.DOMUtils', {
5211          doc : null,
5212          root : null,
5213          files : null,
5214          pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5215          props : {
5216              "for" : "htmlFor",
5217              "class" : "className",
5218              className : "className",
5219              checked : "checked",
5220              disabled : "disabled",
5221              maxlength : "maxLength",
5222              readonly : "readOnly",
5223              selected : "selected",
5224              value : "value",
5225              id : "id",
5226              name : "name",
5227              type : "type"
5228          },
5229  
5230          DOMUtils : function(d, s) {
5231              var t = this, globalStyle, name, blockElementsMap;
5232  
5233              t.doc = d;
5234              t.win = window;
5235              t.files = {};
5236              t.cssFlicker = false;
5237              t.counter = 0;
5238              t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5239              t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5240              t.hasOuterHTML = "outerHTML" in d.createElement("a");
5241  
5242              t.settings = s = tinymce.extend({
5243                  keep_values : false,
5244                  hex_colors : 1
5245              }, s);
5246              
5247              t.schema = s.schema;
5248              t.styles = new tinymce.html.Styles({
5249                  url_converter : s.url_converter,
5250                  url_converter_scope : s.url_converter_scope
5251              }, s.schema);
5252  
5253              // Fix IE6SP2 flicker and check it failed for pre SP2
5254              if (tinymce.isIE6) {
5255                  try {
5256                      d.execCommand('BackgroundImageCache', false, true);
5257                  } catch (e) {
5258                      t.cssFlicker = true;
5259                  }
5260              }
5261  
5262              t.fixDoc(d);
5263              t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5264              tinymce.addUnload(t.destroy, t);
5265              blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5266  
5267              t.isBlock = function(node) {
5268                  // Fix for #5446
5269                  if (!node) {
5270                      return false;
5271                  }
5272  
5273                  // This function is called in module pattern style since it might be executed with the wrong this scope
5274                  var type = node.nodeType;
5275  
5276                  // If it's a node then check the type and use the nodeName
5277                  if (type)
5278                      return !!(type === 1 && blockElementsMap[node.nodeName]);
5279  
5280                  return !!blockElementsMap[node];
5281              };
5282          },
5283  
5284          fixDoc: function(doc) {
5285              var settings = this.settings, name;
5286  
5287              if (isIE && !tinymce.isIE11 && settings.schema) {
5288                  // Add missing HTML 4/5 elements to IE
5289                  ('abbr article aside audio canvas ' +
5290                  'details figcaption figure footer ' +
5291                  'header hgroup mark menu meter nav ' +
5292                  'output progress section summary ' +
5293                  'time video').replace(/\w+/g, function(name) {
5294                      doc.createElement(name);
5295                  });
5296  
5297                  // Create all custom elements
5298                  for (name in settings.schema.getCustomElements()) {
5299                      doc.createElement(name);
5300                  }
5301              }
5302          },
5303  
5304          clone: function(node, deep) {
5305              var self = this, clone, doc;
5306  
5307              // TODO: Add feature detection here in the future
5308              if (!isIE || tinymce.isIE11 || node.nodeType !== 1 || deep) {
5309                  return node.cloneNode(deep);
5310              }
5311  
5312              doc = self.doc;
5313  
5314              // Make a HTML5 safe shallow copy
5315              if (!deep) {
5316                  clone = doc.createElement(node.nodeName);
5317  
5318                  // Copy attribs
5319                  each(self.getAttribs(node), function(attr) {
5320                      self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5321                  });
5322  
5323                  return clone;
5324              }
5325  /*
5326              // Setup HTML5 patched document fragment
5327              if (!self.frag) {
5328                  self.frag = doc.createDocumentFragment();
5329                  self.fixDoc(self.frag);
5330              }
5331  
5332              // Make a deep copy by adding it to the document fragment then removing it this removed the :section
5333              clone = doc.createElement('div');
5334              self.frag.appendChild(clone);
5335              clone.innerHTML = node.outerHTML;
5336              self.frag.removeChild(clone);
5337  */
5338              return clone.firstChild;
5339          },
5340  
5341          getRoot : function() {
5342              var t = this, s = t.settings;
5343  
5344              return (s && t.get(s.root_element)) || t.doc.body;
5345          },
5346  
5347          getViewPort : function(w) {
5348              var d, b;
5349  
5350              w = !w ? this.win : w;
5351              d = w.document;
5352              b = this.boxModel ? d.documentElement : d.body;
5353  
5354              // Returns viewport size excluding scrollbars
5355              return {
5356                  x : w.pageXOffset || b.scrollLeft,
5357                  y : w.pageYOffset || b.scrollTop,
5358                  w : w.innerWidth || b.clientWidth,
5359                  h : w.innerHeight || b.clientHeight
5360              };
5361          },
5362  
5363          getRect : function(e) {
5364              var p, t = this, sr;
5365  
5366              e = t.get(e);
5367              p = t.getPos(e);
5368              sr = t.getSize(e);
5369  
5370              return {
5371                  x : p.x,
5372                  y : p.y,
5373                  w : sr.w,
5374                  h : sr.h
5375              };
5376          },
5377  
5378          getSize : function(e) {
5379              var t = this, w, h;
5380  
5381              e = t.get(e);
5382              w = t.getStyle(e, 'width');
5383              h = t.getStyle(e, 'height');
5384  
5385              // Non pixel value, then force offset/clientWidth
5386              if (w.indexOf('px') === -1)
5387                  w = 0;
5388  
5389              // Non pixel value, then force offset/clientWidth
5390              if (h.indexOf('px') === -1)
5391                  h = 0;
5392  
5393              return {
5394                  w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5395                  h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5396              };
5397          },
5398  
5399          getParent : function(n, f, r) {
5400              return this.getParents(n, f, r, false);
5401          },
5402  
5403          getParents : function(n, f, r, c) {
5404              var t = this, na, se = t.settings, o = [];
5405  
5406              n = t.get(n);
5407              c = c === undefined;
5408  
5409              if (se.strict_root)
5410                  r = r || t.getRoot();
5411  
5412              // Wrap node name as func
5413              if (is(f, 'string')) {
5414                  na = f;
5415  
5416                  if (f === '*') {
5417                      f = function(n) {return n.nodeType == 1;};
5418                  } else {
5419                      f = function(n) {
5420                          return t.is(n, na);
5421                      };
5422                  }
5423              }
5424  
5425              while (n) {
5426                  if (n == r || !n.nodeType || n.nodeType === 9)
5427                      break;
5428  
5429                  if (!f || f(n)) {
5430                      if (c)
5431                          o.push(n);
5432                      else
5433                          return n;
5434                  }
5435  
5436                  n = n.parentNode;
5437              }
5438  
5439              return c ? o : null;
5440          },
5441  
5442          get : function(e) {
5443              var n;
5444  
5445              if (e && this.doc && typeof(e) == 'string') {
5446                  n = e;
5447                  e = this.doc.getElementById(e);
5448  
5449                  // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5450                  if (e && e.id !== n)
5451                      return this.doc.getElementsByName(n)[1];
5452              }
5453  
5454              return e;
5455          },
5456  
5457          getNext : function(node, selector) {
5458              return this._findSib(node, selector, 'nextSibling');
5459          },
5460  
5461          getPrev : function(node, selector) {
5462              return this._findSib(node, selector, 'previousSibling');
5463          },
5464  
5465  
5466          select : function(pa, s) {
5467              var t = this;
5468  
5469              return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
5470          },
5471  
5472          is : function(n, selector) {
5473              var i;
5474  
5475              // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
5476              if (n.length === undefined) {
5477                  // Simple all selector
5478                  if (selector === '*')
5479                      return n.nodeType == 1;
5480  
5481                  // Simple selector just elements
5482                  if (simpleSelectorRe.test(selector)) {
5483                      selector = selector.toLowerCase().split(/,/);
5484                      n = n.nodeName.toLowerCase();
5485  
5486                      for (i = selector.length - 1; i >= 0; i--) {
5487                          if (selector[i] == n)
5488                              return true;
5489                      }
5490  
5491                      return false;
5492                  }
5493              }
5494  
5495              return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
5496          },
5497  
5498  
5499          add : function(p, n, a, h, c) {
5500              var t = this;
5501  
5502              return this.run(p, function(p) {
5503                  var e, k;
5504  
5505                  e = is(n, 'string') ? t.doc.createElement(n) : n;
5506                  t.setAttribs(e, a);
5507  
5508                  if (h) {
5509                      if (h.nodeType)
5510                          e.appendChild(h);
5511                      else
5512                          t.setHTML(e, h);
5513                  }
5514  
5515                  return !c ? p.appendChild(e) : e;
5516              });
5517          },
5518  
5519          create : function(n, a, h) {
5520              return this.add(this.doc.createElement(n), n, a, h, 1);
5521          },
5522  
5523          createHTML : function(n, a, h) {
5524              var o = '', t = this, k;
5525  
5526              o += '<' + n;
5527  
5528              for (k in a) {
5529                  if (a.hasOwnProperty(k))
5530                      o += ' ' + k + '="' + t.encode(a[k]) + '"';
5531              }
5532  
5533              // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5534              if (typeof(h) != "undefined")
5535                  return o + '>' + h + '</' + n + '>';
5536  
5537              return o + ' />';
5538          },
5539  
5540          remove : function(node, keep_children) {
5541              return this.run(node, function(node) {
5542                  var child, parent = node.parentNode;
5543  
5544                  if (!parent)
5545                      return null;
5546  
5547                  if (keep_children) {
5548                      while (child = node.firstChild) {
5549                          // IE 8 will crash if you don't remove completely empty text nodes
5550                          if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5551                              parent.insertBefore(child, node);
5552                          else
5553                              node.removeChild(child);
5554                      }
5555                  }
5556  
5557                  return parent.removeChild(node);
5558              });
5559          },
5560  
5561          setStyle : function(n, na, v) {
5562              var t = this;
5563  
5564              return t.run(n, function(e) {
5565                  var s, i;
5566  
5567                  s = e.style;
5568  
5569                  // Camelcase it, if needed
5570                  na = na.replace(/-(\D)/g, function(a, b){
5571                      return b.toUpperCase();
5572                  });
5573  
5574                  // Default px suffix on these
5575                  if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5576                      v += 'px';
5577  
5578                  switch (na) {
5579                      case 'opacity':
5580                          // IE specific opacity
5581                          if (isIE && ! tinymce.isIE11) {
5582                              s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5583  
5584                              if (!n.currentStyle || !n.currentStyle.hasLayout)
5585                                  s.display = 'inline-block';
5586                          }
5587  
5588                          // Fix for older browsers
5589                          s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5590                          break;
5591  
5592                      case 'float':
5593                          (isIE && ! tinymce.isIE11) ? s.styleFloat = v : s.cssFloat = v;
5594                          break;
5595                      
5596                      default:
5597                          s[na] = v || '';
5598                  }
5599  
5600                  // Force update of the style data
5601                  if (t.settings.update_styles)
5602                      t.setAttrib(e, 'data-mce-style');
5603              });
5604          },
5605  
5606          getStyle : function(n, na, c) {
5607              n = this.get(n);
5608  
5609              if (!n)
5610                  return;
5611  
5612              // Gecko
5613              if (this.doc.defaultView && c) {
5614                  // Remove camelcase
5615                  na = na.replace(/[A-Z]/g, function(a){
5616                      return '-' + a;
5617                  });
5618  
5619                  try {
5620                      return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5621                  } catch (ex) {
5622                      // Old safari might fail
5623                      return null;
5624                  }
5625              }
5626  
5627              // Camelcase it, if needed
5628              na = na.replace(/-(\D)/g, function(a, b){
5629                  return b.toUpperCase();
5630              });
5631  
5632              if (na == 'float')
5633                  na = isIE ? 'styleFloat' : 'cssFloat';
5634  
5635              // IE & Opera
5636              if (n.currentStyle && c)
5637                  return n.currentStyle[na];
5638  
5639              return n.style ? n.style[na] : undefined;
5640          },
5641  
5642          setStyles : function(e, o) {
5643              var t = this, s = t.settings, ol;
5644  
5645              ol = s.update_styles;
5646              s.update_styles = 0;
5647  
5648              each(o, function(v, n) {
5649                  t.setStyle(e, n, v);
5650              });
5651  
5652              // Update style info
5653              s.update_styles = ol;
5654              if (s.update_styles)
5655                  t.setAttrib(e, s.cssText);
5656          },
5657  
5658          removeAllAttribs: function(e) {
5659              return this.run(e, function(e) {
5660                  var i, attrs = e.attributes;
5661                  for (i = attrs.length - 1; i >= 0; i--) {
5662                      e.removeAttributeNode(attrs.item(i));
5663                  }
5664              });
5665          },
5666  
5667          setAttrib : function(e, n, v) {
5668              var t = this;
5669  
5670              // Whats the point
5671              if (!e || !n)
5672                  return;
5673  
5674              // Strict XML mode
5675              if (t.settings.strict)
5676                  n = n.toLowerCase();
5677  
5678              return this.run(e, function(e) {
5679                  var s = t.settings;
5680                  var originalValue = e.getAttribute(n);
5681                  if (v !== null) {
5682                      switch (n) {
5683                          case "style":
5684                              if (!is(v, 'string')) {
5685                                  each(v, function(v, n) {
5686                                      t.setStyle(e, n, v);
5687                                  });
5688  
5689                                  return;
5690                              }
5691  
5692                              // No mce_style for elements with these since they might get resized by the user
5693                              if (s.keep_values) {
5694                                  if (v && !t._isRes(v))
5695                                      e.setAttribute('data-mce-style', v, 2);
5696                                  else
5697                                      e.removeAttribute('data-mce-style', 2);
5698                              }
5699  
5700                              e.style.cssText = v;
5701                              break;
5702  
5703                          case "class":
5704                              e.className = v || ''; // Fix IE null bug
5705                              break;
5706  
5707                          case "src":
5708                          case "href":
5709                              if (s.keep_values) {
5710                                  if (s.url_converter)
5711                                      v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5712  
5713                                  t.setAttrib(e, 'data-mce-' + n, v, 2);
5714                              }
5715  
5716                              break;
5717  
5718                          case "shape":
5719                              e.setAttribute('data-mce-style', v);
5720                              break;
5721                      }
5722                  }
5723                  if (is(v) && v !== null && v.length !== 0)
5724                      e.setAttribute(n, '' + v, 2);
5725                  else
5726                      e.removeAttribute(n, 2);
5727  
5728                  // fire onChangeAttrib event for attributes that have changed
5729                  if (tinyMCE.activeEditor && originalValue != v) {
5730                      var ed = tinyMCE.activeEditor;
5731                      ed.onSetAttrib.dispatch(ed, e, n, v);
5732                  }
5733              });
5734          },
5735  
5736          setAttribs : function(e, o) {
5737              var t = this;
5738  
5739              return this.run(e, function(e) {
5740                  each(o, function(v, n) {
5741                      t.setAttrib(e, n, v);
5742                  });
5743              });
5744          },
5745  
5746          getAttrib : function(e, n, dv) {
5747              var v, t = this, undef;
5748  
5749              e = t.get(e);
5750  
5751              if (!e || e.nodeType !== 1)
5752                  return dv === undef ? false : dv;
5753  
5754              if (!is(dv))
5755                  dv = '';
5756  
5757              // Try the mce variant for these
5758              if (/^(src|href|style|coords|shape)$/.test(n)) {
5759                  v = e.getAttribute("data-mce-" + n);
5760  
5761                  if (v)
5762                      return v;
5763              }
5764  
5765              if (isIE && t.props[n]) {
5766                  v = e[t.props[n]];
5767                  v = v && v.nodeValue ? v.nodeValue : v;
5768              }
5769  
5770              if (!v)
5771                  v = e.getAttribute(n, 2);
5772  
5773              // Check boolean attribs
5774              if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5775                  if (e[t.props[n]] === true && v === '')
5776                      return n;
5777  
5778                  return v ? n : '';
5779              }
5780  
5781              // Inner input elements will override attributes on form elements
5782              if (e.nodeName === "FORM" && e.getAttributeNode(n))
5783                  return e.getAttributeNode(n).nodeValue;
5784  
5785              if (n === 'style') {
5786                  v = v || e.style.cssText;
5787  
5788                  if (v) {
5789                      v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5790  
5791                      if (t.settings.keep_values && !t._isRes(v))
5792                          e.setAttribute('data-mce-style', v);
5793                  }
5794              }
5795  
5796              // Remove Apple and WebKit stuff
5797              if (isWebKit && n === "class" && v)
5798                  v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5799  
5800              // Handle IE issues
5801              if (isIE) {
5802                  switch (n) {
5803                      case 'rowspan':
5804                      case 'colspan':
5805                          // IE returns 1 as default value
5806                          if (v === 1)
5807                              v = '';
5808  
5809                          break;
5810  
5811                      case 'size':
5812                          // IE returns +0 as default value for size
5813                          if (v === '+0' || v === 20 || v === 0)
5814                              v = '';
5815  
5816                          break;
5817  
5818                      case 'width':
5819                      case 'height':
5820                      case 'vspace':
5821                      case 'checked':
5822                      case 'disabled':
5823                      case 'readonly':
5824                          if (v === 0)
5825                              v = '';
5826  
5827                          break;
5828  
5829                      case 'hspace':
5830                          // IE returns -1 as default value
5831                          if (v === -1)
5832                              v = '';
5833  
5834                          break;
5835  
5836                      case 'maxlength':
5837                      case 'tabindex':
5838                          // IE returns default value
5839                          if (v === 32768 || v === 2147483647 || v === '32768')
5840                              v = '';
5841  
5842                          break;
5843  
5844                      case 'multiple':
5845                      case 'compact':
5846                      case 'noshade':
5847                      case 'nowrap':
5848                          if (v === 65535)
5849                              return n;
5850  
5851                          return dv;
5852  
5853                      case 'shape':
5854                          v = v.toLowerCase();
5855                          break;
5856  
5857                      default:
5858                          // IE has odd anonymous function for event attributes
5859                          if (n.indexOf('on') === 0 && v)
5860                              v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5861                  }
5862              }
5863  
5864              return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5865          },
5866  
5867          getPos : function(n, ro) {
5868              var t = this, x = 0, y = 0, e, d = t.doc, r;
5869  
5870              n = t.get(n);
5871              ro = ro || d.body;
5872  
5873              if (n) {
5874                  // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5875                  if (n.getBoundingClientRect) {
5876                      n = n.getBoundingClientRect();
5877                      e = t.boxModel ? d.documentElement : d.body;
5878  
5879                      // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5880                      // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5881                      x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5882                      y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5883  
5884                      return {x : x, y : y};
5885                  }
5886  
5887                  r = n;
5888                  while (r && r != ro && r.nodeType) {
5889                      x += r.offsetLeft || 0;
5890                      y += r.offsetTop || 0;
5891                      r = r.offsetParent;
5892                  }
5893  
5894                  r = n.parentNode;
5895                  while (r && r != ro && r.nodeType) {
5896                      x -= r.scrollLeft || 0;
5897                      y -= r.scrollTop || 0;
5898                      r = r.parentNode;
5899                  }
5900              }
5901  
5902              return {x : x, y : y};
5903          },
5904  
5905          parseStyle : function(st) {
5906              return this.styles.parse(st);
5907          },
5908  
5909          serializeStyle : function(o, name) {
5910              return this.styles.serialize(o, name);
5911          },
5912  
5913          addStyle: function(cssText) {
5914              var doc = this.doc, head;
5915  
5916              // Create style element if needed
5917              styleElm = doc.getElementById('mceDefaultStyles');
5918              if (!styleElm) {
5919                  styleElm = doc.createElement('style'),
5920                  styleElm.id = 'mceDefaultStyles';
5921                  styleElm.type = 'text/css';
5922  
5923                  head = doc.getElementsByTagName('head')[0];
5924                  if (head.firstChild) {
5925                      head.insertBefore(styleElm, head.firstChild);
5926                  } else {
5927                      head.appendChild(styleElm);
5928                  }
5929              }
5930  
5931              // Append style data to old or new style element
5932              if (styleElm.styleSheet) {
5933                  styleElm.styleSheet.cssText += cssText;
5934              } else {
5935                  styleElm.appendChild(doc.createTextNode(cssText));
5936              }
5937          },
5938  
5939          loadCSS : function(u) {
5940              var t = this, d = t.doc, head;
5941  
5942              if (!u)
5943                  u = '';
5944  
5945              head = d.getElementsByTagName('head')[0];
5946  
5947              each(u.split(','), function(u) {
5948                  var link;
5949  
5950                  if (t.files[u])
5951                      return;
5952  
5953                  t.files[u] = true;
5954                  link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5955  
5956                  // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5957                  // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5958                  // It's ugly but it seems to work fine.
5959                  if (isIE && !tinymce.isIE11 && d.documentMode && d.recalc) {
5960                      link.onload = function() {
5961                          if (d.recalc)
5962                              d.recalc();
5963  
5964                          link.onload = null;
5965                      };
5966                  }
5967  
5968                  head.appendChild(link);
5969              });
5970          },
5971  
5972          addClass : function(e, c) {
5973              return this.run(e, function(e) {
5974                  var o;
5975  
5976                  if (!c)
5977                      return 0;
5978  
5979                  if (this.hasClass(e, c))
5980                      return e.className;
5981  
5982                  o = this.removeClass(e, c);
5983  
5984                  return e.className = (o != '' ? (o + ' ') : '') + c;
5985              });
5986          },
5987  
5988          removeClass : function(e, c) {
5989              var t = this, re;
5990  
5991              return t.run(e, function(e) {
5992                  var v;
5993  
5994                  if (t.hasClass(e, c)) {
5995                      if (!re)
5996                          re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5997  
5998                      v = e.className.replace(re, ' ');
5999                      v = tinymce.trim(v != ' ' ? v : '');
6000  
6001                      e.className = v;
6002  
6003                      // Empty class attr
6004                      if (!v) {
6005                          e.removeAttribute('class');
6006                          e.removeAttribute('className');
6007                      }
6008  
6009                      return v;
6010                  }
6011  
6012                  return e.className;
6013              });
6014          },
6015  
6016          hasClass : function(n, c) {
6017              n = this.get(n);
6018  
6019              if (!n || !c)
6020                  return false;
6021  
6022              return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
6023          },
6024  
6025          show : function(e) {
6026              return this.setStyle(e, 'display', 'block');
6027          },
6028  
6029          hide : function(e) {
6030              return this.setStyle(e, 'display', 'none');
6031          },
6032  
6033          isHidden : function(e) {
6034              e = this.get(e);
6035  
6036              return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
6037          },
6038  
6039          uniqueId : function(p) {
6040              return (!p ? 'mce_' : p) + (this.counter++);
6041          },
6042  
6043          setHTML : function(element, html) {
6044              var self = this;
6045  
6046              return self.run(element, function(element) {
6047                  if (isIE) {
6048                      // Remove all child nodes, IE keeps empty text nodes in DOM
6049                      while (element.firstChild)
6050                          element.removeChild(element.firstChild);
6051  
6052                      try {
6053                          // IE will remove comments from the beginning
6054                          // unless you padd the contents with something
6055                          element.innerHTML = '<br />' + html;
6056                          element.removeChild(element.firstChild);
6057                      } catch (ex) {
6058                          // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
6059                          // This seems to fix this problem
6060  
6061                          // Create new div with HTML contents and a BR infront to keep comments
6062                          var newElement = self.create('div');
6063                          newElement.innerHTML = '<br />' + html;
6064  
6065                          // Add all children from div to target
6066                          each (tinymce.grep(newElement.childNodes), function(node, i) {
6067                              // Skip br element
6068                              if (i && element.canHaveHTML)
6069                                  element.appendChild(node);
6070                          });
6071                      }
6072                  } else
6073                      element.innerHTML = html;
6074  
6075                  return html;
6076              });
6077          },
6078  
6079          getOuterHTML : function(elm) {
6080              var doc, self = this;
6081  
6082              elm = self.get(elm);
6083  
6084              if (!elm)
6085                  return null;
6086  
6087              if (elm.nodeType === 1 && self.hasOuterHTML)
6088                  return elm.outerHTML;
6089  
6090              doc = (elm.ownerDocument || self.doc).createElement("body");
6091              doc.appendChild(elm.cloneNode(true));
6092  
6093              return doc.innerHTML;
6094          },
6095  
6096          setOuterHTML : function(e, h, d) {
6097              var t = this;
6098  
6099  			function setHTML(e, h, d) {
6100                  var n, tp;
6101  
6102                  tp = d.createElement("body");
6103                  tp.innerHTML = h;
6104  
6105                  n = tp.lastChild;
6106                  while (n) {
6107                      t.insertAfter(n.cloneNode(true), e);
6108                      n = n.previousSibling;
6109                  }
6110  
6111                  t.remove(e);
6112              };
6113  
6114              return this.run(e, function(e) {
6115                  e = t.get(e);
6116  
6117                  // Only set HTML on elements
6118                  if (e.nodeType == 1) {
6119                      d = d || e.ownerDocument || t.doc;
6120  
6121                      if (isIE) {
6122                          try {
6123                              // Try outerHTML for IE it sometimes produces an unknown runtime error
6124                              if (isIE && e.nodeType == 1)
6125                                  e.outerHTML = h;
6126                              else
6127                                  setHTML(e, h, d);
6128                          } catch (ex) {
6129                              // Fix for unknown runtime error
6130                              setHTML(e, h, d);
6131                          }
6132                      } else
6133                          setHTML(e, h, d);
6134                  }
6135              });
6136          },
6137  
6138          decode : Entities.decode,
6139  
6140          encode : Entities.encodeAllRaw,
6141  
6142          insertAfter : function(node, reference_node) {
6143              reference_node = this.get(reference_node);
6144  
6145              return this.run(node, function(node) {
6146                  var parent, nextSibling;
6147  
6148                  parent = reference_node.parentNode;
6149                  nextSibling = reference_node.nextSibling;
6150  
6151                  if (nextSibling)
6152                      parent.insertBefore(node, nextSibling);
6153                  else
6154                      parent.appendChild(node);
6155  
6156                  return node;
6157              });
6158          },
6159  
6160          replace : function(n, o, k) {
6161              var t = this;
6162  
6163              if (is(o, 'array'))
6164                  n = n.cloneNode(true);
6165  
6166              return t.run(o, function(o) {
6167                  if (k) {
6168                      each(tinymce.grep(o.childNodes), function(c) {
6169                          n.appendChild(c);
6170                      });
6171                  }
6172  
6173                  return o.parentNode.replaceChild(n, o);
6174              });
6175          },
6176  
6177          rename : function(elm, name) {
6178              var t = this, newElm;
6179  
6180              if (elm.nodeName != name.toUpperCase()) {
6181                  // Rename block element
6182                  newElm = t.create(name);
6183  
6184                  // Copy attribs to new block
6185                  each(t.getAttribs(elm), function(attr_node) {
6186                      t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6187                  });
6188  
6189                  // Replace block
6190                  t.replace(newElm, elm, 1);
6191              }
6192  
6193              return newElm || elm;
6194          },
6195  
6196          findCommonAncestor : function(a, b) {
6197              var ps = a, pe;
6198  
6199              while (ps) {
6200                  pe = b;
6201  
6202                  while (pe && ps != pe)
6203                      pe = pe.parentNode;
6204  
6205                  if (ps == pe)
6206                      break;
6207  
6208                  ps = ps.parentNode;
6209              }
6210  
6211              if (!ps && a.ownerDocument)
6212                  return a.ownerDocument.documentElement;
6213  
6214              return ps;
6215          },
6216  
6217          toHex : function(s) {
6218              var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6219  
6220  			function hex(s) {
6221                  s = parseInt(s, 10).toString(16);
6222  
6223                  return s.length > 1 ? s : '0' + s; // 0 -> 00
6224              };
6225  
6226              if (c) {
6227                  s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6228  
6229                  return s;
6230              }
6231  
6232              return s;
6233          },
6234  
6235          getClasses : function() {
6236              var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6237  
6238              if (t.classes)
6239                  return t.classes;
6240  
6241  			function addClasses(s) {
6242                  // IE style imports
6243                  each(s.imports, function(r) {
6244                      addClasses(r);
6245                  });
6246  
6247                  each(s.cssRules || s.rules, function(r) {
6248                      // Real type or fake it on IE
6249                      switch (r.type || 1) {
6250                          // Rule
6251                          case 1:
6252                              if (r.selectorText) {
6253                                  each(r.selectorText.split(','), function(v) {
6254                                      v = v.replace(/^\s*|\s*$|^\s\./g, "");
6255  
6256                                      // Is internal or it doesn't contain a class
6257                                      if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6258                                          return;
6259  
6260                                      // Remove everything but class name
6261                                      ov = v;
6262                                      v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6263  
6264                                      // Filter classes
6265                                      if (f && !(v = f(v, ov)))
6266                                          return;
6267  
6268                                      if (!lo[v]) {
6269                                          cl.push({'class' : v});
6270                                          lo[v] = 1;
6271                                      }
6272                                  });
6273                              }
6274                              break;
6275  
6276                          // Import
6277                          case 3:
6278                              try {
6279                                  addClasses(r.styleSheet);
6280                              } catch (ex) {
6281                                  // Ignore
6282                              }
6283  
6284                              break;
6285                      }
6286                  });
6287              };
6288  
6289              try {
6290                  each(t.doc.styleSheets, addClasses);
6291              } catch (ex) {
6292                  // Ignore
6293              }
6294  
6295              if (cl.length > 0)
6296                  t.classes = cl;
6297  
6298              return cl;
6299          },
6300  
6301          run : function(e, f, s) {
6302              var t = this, o;
6303  
6304              if (t.doc && typeof(e) === 'string')
6305                  e = t.get(e);
6306  
6307              if (!e)
6308                  return false;
6309  
6310              s = s || this;
6311              if (!e.nodeType && (e.length || e.length === 0)) {
6312                  o = [];
6313  
6314                  each(e, function(e, i) {
6315                      if (e) {
6316                          if (typeof(e) == 'string')
6317                              e = t.doc.getElementById(e);
6318  
6319                          o.push(f.call(s, e, i));
6320                      }
6321                  });
6322  
6323                  return o;
6324              }
6325  
6326              return f.call(s, e);
6327          },
6328  
6329          getAttribs : function(n) {
6330              var o;
6331  
6332              n = this.get(n);
6333  
6334              if (!n)
6335                  return [];
6336  
6337              if (isIE) {
6338                  o = [];
6339  
6340                  // Object will throw exception in IE
6341                  if (n.nodeName == 'OBJECT')
6342                      return n.attributes;
6343  
6344                  // IE doesn't keep the selected attribute if you clone option elements
6345                  if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6346                      o.push({specified : 1, nodeName : 'selected'});
6347  
6348                  // It's crazy that this is faster in IE but it's because it returns all attributes all the time
6349                  n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6350                      o.push({specified : 1, nodeName : a});
6351                  });
6352  
6353                  return o;
6354              }
6355  
6356              return n.attributes;
6357          },
6358  
6359          isEmpty : function(node, elements) {
6360              var self = this, i, attributes, type, walker, name, brCount = 0;
6361  
6362              node = node.firstChild;
6363              if (node) {
6364                  walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6365                  elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6366  
6367                  do {
6368                      type = node.nodeType;
6369  
6370                      if (type === 1) {
6371                          // Ignore bogus elements
6372                          if (node.getAttribute('data-mce-bogus'))
6373                              continue;
6374  
6375                          // Keep empty elements like <img />
6376                          name = node.nodeName.toLowerCase();
6377                          if (elements && elements[name]) {
6378                              // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6379                              if (name === 'br') {
6380                                  brCount++;
6381                                  continue;
6382                              }
6383  
6384                              return false;
6385                          }
6386  
6387                          // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6388                          attributes = self.getAttribs(node);
6389                          i = node.attributes.length;
6390                          while (i--) {
6391                              name = node.attributes[i].nodeName;
6392                              if (name === "name" || name === 'data-mce-bookmark')
6393                                  return false;
6394                          }
6395                      }
6396  
6397                      // Keep comment nodes
6398                      if (type == 8)
6399                          return false;
6400  
6401                      // Keep non whitespace text nodes
6402                      if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6403                          return false;
6404                  } while (node = walker.next());
6405              }
6406  
6407              return brCount <= 1;
6408          },
6409  
6410          destroy : function(s) {
6411              var t = this;
6412  
6413              t.win = t.doc = t.root = t.events = t.frag = null;
6414  
6415              // Manual destroy then remove unload handler
6416              if (!s)
6417                  tinymce.removeUnload(t.destroy);
6418          },
6419  
6420          createRng : function() {
6421              var d = this.doc;
6422  
6423              return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6424          },
6425  
6426          nodeIndex : function(node, normalized) {
6427              var idx = 0, lastNodeType, lastNode, nodeType;
6428  
6429              if (node) {
6430                  for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6431                      nodeType = node.nodeType;
6432  
6433                      // Normalize text nodes
6434                      if (normalized && nodeType == 3) {
6435                          if (nodeType == lastNodeType || !node.nodeValue.length)
6436                              continue;
6437                      }
6438                      idx++;
6439                      lastNodeType = nodeType;
6440                  }
6441              }
6442  
6443              return idx;
6444          },
6445  
6446          split : function(pe, e, re) {
6447              var t = this, r = t.createRng(), bef, aft, pa;
6448  
6449              // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6450              // but we don't want that in our code since it serves no purpose for the end user
6451              // For example if this is chopped:
6452              //   <p>text 1<span><b>CHOP</b></span>text 2</p>
6453              // would produce:
6454              //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6455              // this function will then trim of empty edges and produce:
6456              //   <p>text 1</p><b>CHOP</b><p>text 2</p>
6457  			function trim(node) {
6458                  var i, children = node.childNodes, type = node.nodeType;
6459  
6460  				function surroundedBySpans(node) {
6461                      var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6462                      var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6463                      return previousIsSpan && nextIsSpan;
6464                  }
6465  
6466                  if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6467                      return;
6468  
6469                  for (i = children.length - 1; i >= 0; i--)
6470                      trim(children[i]);
6471  
6472                  if (type != 9) {
6473                      // Keep non whitespace text nodes
6474                      if (type == 3 && node.nodeValue.length > 0) {
6475                          // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6476                          // Also keep text nodes with only spaces if surrounded by spans.
6477                          // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6478                          var trimmedLength = tinymce.trim(node.nodeValue).length;
6479                          if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6480                              return;
6481                      } else if (type == 1) {
6482                          // If the only child is a bookmark then move it up
6483                          children = node.childNodes;
6484                          if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6485                              node.parentNode.insertBefore(children[0], node);
6486  
6487                          // Keep non empty elements or img, hr etc
6488                          if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6489                              return;
6490                      }
6491  
6492                      t.remove(node);
6493                  }
6494  
6495                  return node;
6496              };
6497  
6498              if (pe && e) {
6499                  // Get before chunk
6500                  r.setStart(pe.parentNode, t.nodeIndex(pe));
6501                  r.setEnd(e.parentNode, t.nodeIndex(e));
6502                  bef = r.extractContents();
6503  
6504                  // Get after chunk
6505                  r = t.createRng();
6506                  r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6507                  r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6508                  aft = r.extractContents();
6509  
6510                  // Insert before chunk
6511                  pa = pe.parentNode;
6512                  pa.insertBefore(trim(bef), pe);
6513  
6514                  // Insert middle chunk
6515                  if (re)
6516                  pa.replaceChild(re, e);
6517              else
6518                  pa.insertBefore(e, pe);
6519  
6520                  // Insert after chunk
6521                  pa.insertBefore(trim(aft), pe);
6522                  t.remove(pe);
6523  
6524                  return re || e;
6525              }
6526          },
6527  
6528          bind : function(target, name, func, scope) {
6529              return this.events.add(target, name, func, scope || this);
6530          },
6531  
6532          unbind : function(target, name, func) {
6533              return this.events.remove(target, name, func);
6534          },
6535  
6536          fire : function(target, name, evt) {
6537              return this.events.fire(target, name, evt);
6538          },
6539  
6540          // Returns the content editable state of a node
6541          getContentEditable: function(node) {
6542              var contentEditable;
6543  
6544              // Check type
6545              if (node.nodeType != 1) {
6546                  return null;
6547              }
6548  
6549              // Check for fake content editable
6550              contentEditable = node.getAttribute("data-mce-contenteditable");
6551              if (contentEditable && contentEditable !== "inherit") {
6552                  return contentEditable;
6553              }
6554  
6555              // Check for real content editable
6556              return node.contentEditable !== "inherit" ? node.contentEditable : null;
6557          },
6558  
6559  
6560          _findSib : function(node, selector, name) {
6561              var t = this, f = selector;
6562  
6563              if (node) {
6564                  // If expression make a function of it using is
6565                  if (is(f, 'string')) {
6566                      f = function(node) {
6567                          return t.is(node, selector);
6568                      };
6569                  }
6570  
6571                  // Loop all siblings
6572                  for (node = node[name]; node; node = node[name]) {
6573                      if (f(node))
6574                          return node;
6575                  }
6576              }
6577  
6578              return null;
6579          },
6580  
6581          _isRes : function(c) {
6582              // Is live resizble element
6583              return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6584          }
6585  
6586          /*
6587          walk : function(n, f, s) {
6588              var d = this.doc, w;
6589  
6590              if (d.createTreeWalker) {
6591                  w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6592  
6593                  while ((n = w.nextNode()) != null)
6594                      f.call(s || this, n);
6595              } else
6596                  tinymce.walk(n, f, 'childNodes', s);
6597          }
6598          */
6599  
6600          /*
6601          toRGB : function(s) {
6602              var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6603  
6604              if (c) {
6605                  // #FFF -> #FFFFFF
6606                  if (!is(c[3]))
6607                      c[3] = c[2] = c[1];
6608  
6609                  return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6610              }
6611  
6612              return s;
6613          }
6614          */
6615      });
6616  
6617      tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6618  })(tinymce);
6619  (function(ns) {
6620      // Range constructor
6621  	function Range(dom) {
6622          var t = this,
6623              doc = dom.doc,
6624              EXTRACT = 0,
6625              CLONE = 1,
6626              DELETE = 2,
6627              TRUE = true,
6628              FALSE = false,
6629              START_OFFSET = 'startOffset',
6630              START_CONTAINER = 'startContainer',
6631              END_CONTAINER = 'endContainer',
6632              END_OFFSET = 'endOffset',
6633              extend = tinymce.extend,
6634              nodeIndex = dom.nodeIndex;
6635  
6636          extend(t, {
6637              // Inital states
6638              startContainer : doc,
6639              startOffset : 0,
6640              endContainer : doc,
6641              endOffset : 0,
6642              collapsed : TRUE,
6643              commonAncestorContainer : doc,
6644  
6645              // Range constants
6646              START_TO_START : 0,
6647              START_TO_END : 1,
6648              END_TO_END : 2,
6649              END_TO_START : 3,
6650  
6651              // Public methods
6652              setStart : setStart,
6653              setEnd : setEnd,
6654              setStartBefore : setStartBefore,
6655              setStartAfter : setStartAfter,
6656              setEndBefore : setEndBefore,
6657              setEndAfter : setEndAfter,
6658              collapse : collapse,
6659              selectNode : selectNode,
6660              selectNodeContents : selectNodeContents,
6661              compareBoundaryPoints : compareBoundaryPoints,
6662              deleteContents : deleteContents,
6663              extractContents : extractContents,
6664              cloneContents : cloneContents,
6665              insertNode : insertNode,
6666              surroundContents : surroundContents,
6667              cloneRange : cloneRange,
6668              toStringIE : toStringIE
6669          });
6670  
6671  		function createDocumentFragment() {
6672              return doc.createDocumentFragment();
6673          };
6674  
6675  		function setStart(n, o) {
6676              _setEndPoint(TRUE, n, o);
6677          };
6678  
6679  		function setEnd(n, o) {
6680              _setEndPoint(FALSE, n, o);
6681          };
6682  
6683  		function setStartBefore(n) {
6684              setStart(n.parentNode, nodeIndex(n));
6685          };
6686  
6687  		function setStartAfter(n) {
6688              setStart(n.parentNode, nodeIndex(n) + 1);
6689          };
6690  
6691  		function setEndBefore(n) {
6692              setEnd(n.parentNode, nodeIndex(n));
6693          };
6694  
6695  		function setEndAfter(n) {
6696              setEnd(n.parentNode, nodeIndex(n) + 1);
6697          };
6698  
6699  		function collapse(ts) {
6700              if (ts) {
6701                  t[END_CONTAINER] = t[START_CONTAINER];
6702                  t[END_OFFSET] = t[START_OFFSET];
6703              } else {
6704                  t[START_CONTAINER] = t[END_CONTAINER];
6705                  t[START_OFFSET] = t[END_OFFSET];
6706              }
6707  
6708              t.collapsed = TRUE;
6709          };
6710  
6711  		function selectNode(n) {
6712              setStartBefore(n);
6713              setEndAfter(n);
6714          };
6715  
6716  		function selectNodeContents(n) {
6717              setStart(n, 0);
6718              setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6719          };
6720  
6721  		function compareBoundaryPoints(h, r) {
6722              var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6723              rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6724  
6725              // Check START_TO_START
6726              if (h === 0)
6727                  return _compareBoundaryPoints(sc, so, rsc, rso);
6728      
6729              // Check START_TO_END
6730              if (h === 1)
6731                  return _compareBoundaryPoints(ec, eo, rsc, rso);
6732      
6733              // Check END_TO_END
6734              if (h === 2)
6735                  return _compareBoundaryPoints(ec, eo, rec, reo);
6736      
6737              // Check END_TO_START
6738              if (h === 3) 
6739                  return _compareBoundaryPoints(sc, so, rec, reo);
6740          };
6741  
6742  		function deleteContents() {
6743              _traverse(DELETE);
6744          };
6745  
6746  		function extractContents() {
6747              return _traverse(EXTRACT);
6748          };
6749  
6750  		function cloneContents() {
6751              return _traverse(CLONE);
6752          };
6753  
6754  		function insertNode(n) {
6755              var startContainer = this[START_CONTAINER],
6756                  startOffset = this[START_OFFSET], nn, o;
6757  
6758              // Node is TEXT_NODE or CDATA
6759              if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6760                  if (!startOffset) {
6761                      // At the start of text
6762                      startContainer.parentNode.insertBefore(n, startContainer);
6763                  } else if (startOffset >= startContainer.nodeValue.length) {
6764                      // At the end of text
6765                      dom.insertAfter(n, startContainer);
6766                  } else {
6767                      // Middle, need to split
6768                      nn = startContainer.splitText(startOffset);
6769                      startContainer.parentNode.insertBefore(n, nn);
6770                  }
6771              } else {
6772                  // Insert element node
6773                  if (startContainer.childNodes.length > 0)
6774                      o = startContainer.childNodes[startOffset];
6775  
6776                  if (o)
6777                      startContainer.insertBefore(n, o);
6778                  else
6779                      startContainer.appendChild(n);
6780              }
6781          };
6782  
6783  		function surroundContents(n) {
6784              var f = t.extractContents();
6785  
6786              t.insertNode(n);
6787              n.appendChild(f);
6788              t.selectNode(n);
6789          };
6790  
6791  		function cloneRange() {
6792              return extend(new Range(dom), {
6793                  startContainer : t[START_CONTAINER],
6794                  startOffset : t[START_OFFSET],
6795                  endContainer : t[END_CONTAINER],
6796                  endOffset : t[END_OFFSET],
6797                  collapsed : t.collapsed,
6798                  commonAncestorContainer : t.commonAncestorContainer
6799              });
6800          };
6801  
6802          // Private methods
6803  
6804  		function _getSelectedNode(container, offset) {
6805              var child;
6806  
6807              if (container.nodeType == 3 /* TEXT_NODE */)
6808                  return container;
6809  
6810              if (offset < 0)
6811                  return container;
6812  
6813              child = container.firstChild;
6814              while (child && offset > 0) {
6815                  --offset;
6816                  child = child.nextSibling;
6817              }
6818  
6819              if (child)
6820                  return child;
6821  
6822              return container;
6823          };
6824  
6825  		function _isCollapsed() {
6826              return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6827          };
6828  
6829  		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6830              var c, offsetC, n, cmnRoot, childA, childB;
6831              
6832              // In the first case the boundary-points have the same container. A is before B
6833              // if its offset is less than the offset of B, A is equal to B if its offset is
6834              // equal to the offset of B, and A is after B if its offset is greater than the
6835              // offset of B.
6836              if (containerA == containerB) {
6837                  if (offsetA == offsetB)
6838                      return 0; // equal
6839  
6840                  if (offsetA < offsetB)
6841                      return -1; // before
6842  
6843                  return 1; // after
6844              }
6845  
6846              // In the second case a child node C of the container of A is an ancestor
6847              // container of B. In this case, A is before B if the offset of A is less than or
6848              // equal to the index of the child node C and A is after B otherwise.
6849              c = containerB;
6850              while (c && c.parentNode != containerA)
6851                  c = c.parentNode;
6852  
6853              if (c) {
6854                  offsetC = 0;
6855                  n = containerA.firstChild;
6856  
6857                  while (n != c && offsetC < offsetA) {
6858                      offsetC++;
6859                      n = n.nextSibling;
6860                  }
6861  
6862                  if (offsetA <= offsetC)
6863                      return -1; // before
6864  
6865                  return 1; // after
6866              }
6867  
6868              // In the third case a child node C of the container of B is an ancestor container
6869              // of A. In this case, A is before B if the index of the child node C is less than
6870              // the offset of B and A is after B otherwise.
6871              c = containerA;
6872              while (c && c.parentNode != containerB) {
6873                  c = c.parentNode;
6874              }
6875  
6876              if (c) {
6877                  offsetC = 0;
6878                  n = containerB.firstChild;
6879  
6880                  while (n != c && offsetC < offsetB) {
6881                      offsetC++;
6882                      n = n.nextSibling;
6883                  }
6884  
6885                  if (offsetC < offsetB)
6886                      return -1; // before
6887  
6888                  return 1; // after
6889              }
6890  
6891              // In the fourth case, none of three other cases hold: the containers of A and B
6892              // are siblings or descendants of sibling nodes. In this case, A is before B if
6893              // the container of A is before the container of B in a pre-order traversal of the
6894              // Ranges' context tree and A is after B otherwise.
6895              cmnRoot = dom.findCommonAncestor(containerA, containerB);
6896              childA = containerA;
6897  
6898              while (childA && childA.parentNode != cmnRoot)
6899                  childA = childA.parentNode;
6900  
6901              if (!childA)
6902                  childA = cmnRoot;
6903  
6904              childB = containerB;
6905              while (childB && childB.parentNode != cmnRoot)
6906                  childB = childB.parentNode;
6907  
6908              if (!childB)
6909                  childB = cmnRoot;
6910  
6911              if (childA == childB)
6912                  return 0; // equal
6913  
6914              n = cmnRoot.firstChild;
6915              while (n) {
6916                  if (n == childA)
6917                      return -1; // before
6918  
6919                  if (n == childB)
6920                      return 1; // after
6921  
6922                  n = n.nextSibling;
6923              }
6924          };
6925  
6926  		function _setEndPoint(st, n, o) {
6927              var ec, sc;
6928  
6929              if (st) {
6930                  t[START_CONTAINER] = n;
6931                  t[START_OFFSET] = o;
6932              } else {
6933                  t[END_CONTAINER] = n;
6934                  t[END_OFFSET] = o;
6935              }
6936  
6937              // If one boundary-point of a Range is set to have a root container
6938              // other than the current one for the Range, the Range is collapsed to
6939              // the new position. This enforces the restriction that both boundary-
6940              // points of a Range must have the same root container.
6941              ec = t[END_CONTAINER];
6942              while (ec.parentNode)
6943                  ec = ec.parentNode;
6944  
6945              sc = t[START_CONTAINER];
6946              while (sc.parentNode)
6947                  sc = sc.parentNode;
6948  
6949              if (sc == ec) {
6950                  // The start position of a Range is guaranteed to never be after the
6951                  // end position. To enforce this restriction, if the start is set to
6952                  // be at a position after the end, the Range is collapsed to that
6953                  // position.
6954                  if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6955                      t.collapse(st);
6956              } else
6957                  t.collapse(st);
6958  
6959              t.collapsed = _isCollapsed();
6960              t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6961          };
6962  
6963  		function _traverse(how) {
6964              var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6965  
6966              if (t[START_CONTAINER] == t[END_CONTAINER])
6967                  return _traverseSameContainer(how);
6968  
6969              for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6970                  if (p == t[START_CONTAINER])
6971                      return _traverseCommonStartContainer(c, how);
6972  
6973                  ++endContainerDepth;
6974              }
6975  
6976              for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6977                  if (p == t[END_CONTAINER])
6978                      return _traverseCommonEndContainer(c, how);
6979  
6980                  ++startContainerDepth;
6981              }
6982  
6983              depthDiff = startContainerDepth - endContainerDepth;
6984  
6985              startNode = t[START_CONTAINER];
6986              while (depthDiff > 0) {
6987                  startNode = startNode.parentNode;
6988                  depthDiff--;
6989              }
6990  
6991              endNode = t[END_CONTAINER];
6992              while (depthDiff < 0) {
6993                  endNode = endNode.parentNode;
6994                  depthDiff++;
6995              }
6996  
6997              // ascend the ancestor hierarchy until we have a common parent.
6998              for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6999                  startNode = sp;
7000                  endNode = ep;
7001              }
7002  
7003              return _traverseCommonAncestors(startNode, endNode, how);
7004          };
7005  
7006  		 function _traverseSameContainer(how) {
7007              var frag, s, sub, n, cnt, sibling, xferNode, start, len;
7008  
7009              if (how != DELETE)
7010                  frag = createDocumentFragment();
7011  
7012              // If selection is empty, just return the fragment
7013              if (t[START_OFFSET] == t[END_OFFSET])
7014                  return frag;
7015  
7016              // Text node needs special case handling
7017              if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
7018                  // get the substring
7019                  s = t[START_CONTAINER].nodeValue;
7020                  sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
7021  
7022                  // set the original text node to its new value
7023                  if (how != CLONE) {
7024                      n = t[START_CONTAINER];
7025                      start = t[START_OFFSET];
7026                      len = t[END_OFFSET] - t[START_OFFSET];
7027  
7028                      if (start === 0 && len >= n.nodeValue.length - 1) {
7029                          n.parentNode.removeChild(n);
7030                      } else {
7031                          n.deleteData(start, len);
7032                      }
7033  
7034                      // Nothing is partially selected, so collapse to start point
7035                      t.collapse(TRUE);
7036                  }
7037  
7038                  if (how == DELETE)
7039                      return;
7040  
7041                  if (sub.length > 0) {
7042                      frag.appendChild(doc.createTextNode(sub));
7043                  }
7044  
7045                  return frag;
7046              }
7047  
7048              // Copy nodes between the start/end offsets.
7049              n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
7050              cnt = t[END_OFFSET] - t[START_OFFSET];
7051  
7052              while (n && cnt > 0) {
7053                  sibling = n.nextSibling;
7054                  xferNode = _traverseFullySelected(n, how);
7055  
7056                  if (frag)
7057                      frag.appendChild( xferNode );
7058  
7059                  --cnt;
7060                  n = sibling;
7061              }
7062  
7063              // Nothing is partially selected, so collapse to start point
7064              if (how != CLONE)
7065                  t.collapse(TRUE);
7066  
7067              return frag;
7068          };
7069  
7070  		function _traverseCommonStartContainer(endAncestor, how) {
7071              var frag, n, endIdx, cnt, sibling, xferNode;
7072  
7073              if (how != DELETE)
7074                  frag = createDocumentFragment();
7075  
7076              n = _traverseRightBoundary(endAncestor, how);
7077  
7078              if (frag)
7079                  frag.appendChild(n);
7080  
7081              endIdx = nodeIndex(endAncestor);
7082              cnt = endIdx - t[START_OFFSET];
7083  
7084              if (cnt <= 0) {
7085                  // Collapse to just before the endAncestor, which
7086                  // is partially selected.
7087                  if (how != CLONE) {
7088                      t.setEndBefore(endAncestor);
7089                      t.collapse(FALSE);
7090                  }
7091  
7092                  return frag;
7093              }
7094  
7095              n = endAncestor.previousSibling;
7096              while (cnt > 0) {
7097                  sibling = n.previousSibling;
7098                  xferNode = _traverseFullySelected(n, how);
7099  
7100                  if (frag)
7101                      frag.insertBefore(xferNode, frag.firstChild);
7102  
7103                  --cnt;
7104                  n = sibling;
7105              }
7106  
7107              // Collapse to just before the endAncestor, which
7108              // is partially selected.
7109              if (how != CLONE) {
7110                  t.setEndBefore(endAncestor);
7111                  t.collapse(FALSE);
7112              }
7113  
7114              return frag;
7115          };
7116  
7117  		function _traverseCommonEndContainer(startAncestor, how) {
7118              var frag, startIdx, n, cnt, sibling, xferNode;
7119  
7120              if (how != DELETE)
7121                  frag = createDocumentFragment();
7122  
7123              n = _traverseLeftBoundary(startAncestor, how);
7124              if (frag)
7125                  frag.appendChild(n);
7126  
7127              startIdx = nodeIndex(startAncestor);
7128              ++startIdx; // Because we already traversed it
7129  
7130              cnt = t[END_OFFSET] - startIdx;
7131              n = startAncestor.nextSibling;
7132              while (n && cnt > 0) {
7133                  sibling = n.nextSibling;
7134                  xferNode = _traverseFullySelected(n, how);
7135  
7136                  if (frag)
7137                      frag.appendChild(xferNode);
7138  
7139                  --cnt;
7140                  n = sibling;
7141              }
7142  
7143              if (how != CLONE) {
7144                  t.setStartAfter(startAncestor);
7145                  t.collapse(TRUE);
7146              }
7147  
7148              return frag;
7149          };
7150  
7151  		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7152              var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7153  
7154              if (how != DELETE)
7155                  frag = createDocumentFragment();
7156  
7157              n = _traverseLeftBoundary(startAncestor, how);
7158              if (frag)
7159                  frag.appendChild(n);
7160  
7161              commonParent = startAncestor.parentNode;
7162              startOffset = nodeIndex(startAncestor);
7163              endOffset = nodeIndex(endAncestor);
7164              ++startOffset;
7165  
7166              cnt = endOffset - startOffset;
7167              sibling = startAncestor.nextSibling;
7168  
7169              while (cnt > 0) {
7170                  nextSibling = sibling.nextSibling;
7171                  n = _traverseFullySelected(sibling, how);
7172  
7173                  if (frag)
7174                      frag.appendChild(n);
7175  
7176                  sibling = nextSibling;
7177                  --cnt;
7178              }
7179  
7180              n = _traverseRightBoundary(endAncestor, how);
7181  
7182              if (frag)
7183                  frag.appendChild(n);
7184  
7185              if (how != CLONE) {
7186                  t.setStartAfter(startAncestor);
7187                  t.collapse(TRUE);
7188              }
7189  
7190              return frag;
7191          };
7192  
7193  		function _traverseRightBoundary(root, how) {
7194              var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7195  
7196              if (next == root)
7197                  return _traverseNode(next, isFullySelected, FALSE, how);
7198  
7199              parent = next.parentNode;
7200              clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7201  
7202              while (parent) {
7203                  while (next) {
7204                      prevSibling = next.previousSibling;
7205                      clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7206  
7207                      if (how != DELETE)
7208                          clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7209  
7210                      isFullySelected = TRUE;
7211                      next = prevSibling;
7212                  }
7213  
7214                  if (parent == root)
7215                      return clonedParent;
7216  
7217                  next = parent.previousSibling;
7218                  parent = parent.parentNode;
7219  
7220                  clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7221  
7222                  if (how != DELETE)
7223                      clonedGrandParent.appendChild(clonedParent);
7224  
7225                  clonedParent = clonedGrandParent;
7226              }
7227          };
7228  
7229  		function _traverseLeftBoundary(root, how) {
7230              var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7231  
7232              if (next == root)
7233                  return _traverseNode(next, isFullySelected, TRUE, how);
7234  
7235              parent = next.parentNode;
7236              clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7237  
7238              while (parent) {
7239                  while (next) {
7240                      nextSibling = next.nextSibling;
7241                      clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7242  
7243                      if (how != DELETE)
7244                          clonedParent.appendChild(clonedChild);
7245  
7246                      isFullySelected = TRUE;
7247                      next = nextSibling;
7248                  }
7249  
7250                  if (parent == root)
7251                      return clonedParent;
7252  
7253                  next = parent.nextSibling;
7254                  parent = parent.parentNode;
7255  
7256                  clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7257  
7258                  if (how != DELETE)
7259                      clonedGrandParent.appendChild(clonedParent);
7260  
7261                  clonedParent = clonedGrandParent;
7262              }
7263          };
7264  
7265  		function _traverseNode(n, isFullySelected, isLeft, how) {
7266              var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7267  
7268              if (isFullySelected)
7269                  return _traverseFullySelected(n, how);
7270  
7271              if (n.nodeType == 3 /* TEXT_NODE */) {
7272                  txtValue = n.nodeValue;
7273  
7274                  if (isLeft) {
7275                      offset = t[START_OFFSET];
7276                      newNodeValue = txtValue.substring(offset);
7277                      oldNodeValue = txtValue.substring(0, offset);
7278                  } else {
7279                      offset = t[END_OFFSET];
7280                      newNodeValue = txtValue.substring(0, offset);
7281                      oldNodeValue = txtValue.substring(offset);
7282                  }
7283  
7284                  if (how != CLONE)
7285                      n.nodeValue = oldNodeValue;
7286  
7287                  if (how == DELETE)
7288                      return;
7289  
7290                  newNode = dom.clone(n, FALSE);
7291                  newNode.nodeValue = newNodeValue;
7292  
7293                  return newNode;
7294              }
7295  
7296              if (how == DELETE)
7297                  return;
7298  
7299              return dom.clone(n, FALSE);
7300          };
7301  
7302  		function _traverseFullySelected(n, how) {
7303              if (how != DELETE)
7304                  return how == CLONE ? dom.clone(n, TRUE) : n;
7305  
7306              n.parentNode.removeChild(n);
7307          };
7308  
7309  		function toStringIE() {
7310              return dom.create('body', null, cloneContents()).outerText;
7311          }
7312          
7313          return t;
7314      };
7315  
7316      ns.Range = Range;
7317  
7318      // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7319      Range.prototype.toString = function() {
7320          return this.toStringIE();
7321      };
7322  })(tinymce.dom);
7323  (function() {
7324  	function Selection(selection) {
7325          var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7326  
7327  		function getPosition(rng, start) {
7328              var checkRng, startIndex = 0, endIndex, inside,
7329                  children, child, offset, index, position = -1, parent;
7330  
7331              // Setup test range, collapse it and get the parent
7332              checkRng = rng.duplicate();
7333              checkRng.collapse(start);
7334              parent = checkRng.parentElement();
7335  
7336              // Check if the selection is within the right document
7337              if (parent.ownerDocument !== selection.dom.doc)
7338                  return;
7339  
7340              // IE will report non editable elements as it's parent so look for an editable one
7341              while (parent.contentEditable === "false") {
7342                  parent = parent.parentNode;
7343              }
7344  
7345              // If parent doesn't have any children then return that we are inside the element
7346              if (!parent.hasChildNodes()) {
7347                  return {node : parent, inside : 1};
7348              }
7349  
7350              // Setup node list and endIndex
7351              children = parent.children;
7352              endIndex = children.length - 1;
7353  
7354              // Perform a binary search for the position
7355              while (startIndex <= endIndex) {
7356                  index = Math.floor((startIndex + endIndex) / 2);
7357  
7358                  // Move selection to node and compare the ranges
7359                  child = children[index];
7360                  checkRng.moveToElementText(child);
7361                  position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7362  
7363                  // Before/after or an exact match
7364                  if (position > 0) {
7365                      endIndex = index - 1;
7366                  } else if (position < 0) {
7367                      startIndex = index + 1;
7368                  } else {
7369                      return {node : child};
7370                  }
7371              }
7372  
7373              // Check if child position is before or we didn't find a position
7374              if (position < 0) {
7375                  // No element child was found use the parent element and the offset inside that
7376                  if (!child) {
7377                      checkRng.moveToElementText(parent);
7378                      checkRng.collapse(true);
7379                      child = parent;
7380                      inside = true;
7381                  } else
7382                      checkRng.collapse(false);
7383  
7384                  // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7385                  // We need to walk char by char since rng.text or rng.htmlText will trim line endings
7386                  offset = 0;
7387                  while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7388                      if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7389                          break;
7390                      }
7391  
7392                      offset++;
7393                  }
7394              } else {
7395                  // Child position is after the selection endpoint
7396                  checkRng.collapse(true);
7397  
7398                  // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7399                  offset = 0;
7400                  while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7401                      if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7402                          break;
7403                      }
7404  
7405                      offset++;
7406                  }
7407              }
7408  
7409              return {node : child, position : position, offset : offset, inside : inside};
7410          };
7411  
7412          // Returns a W3C DOM compatible range object by using the IE Range API
7413  		function getRange() {
7414              var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7415  
7416              // If selection is outside the current document just return an empty range
7417              element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7418              if (element.ownerDocument != dom.doc)
7419                  return domRange;
7420  
7421              collapsed = selection.isCollapsed();
7422  
7423              // Handle control selection
7424              if (ieRange.item) {
7425                  domRange.setStart(element.parentNode, dom.nodeIndex(element));
7426                  domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7427  
7428                  return domRange;
7429              }
7430  
7431  			function findEndPoint(start) {
7432                  var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7433  
7434                  container = endPoint.node;
7435                  offset = endPoint.offset;
7436  
7437                  if (endPoint.inside && !container.hasChildNodes()) {
7438                      domRange[start ? 'setStart' : 'setEnd'](container, 0);
7439                      return;
7440                  }
7441  
7442                  if (offset === undef) {
7443                      domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7444                      return;
7445                  }
7446  
7447                  if (endPoint.position < 0) {
7448                      sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7449  
7450                      if (!sibling) {
7451                          domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7452                          return;
7453                      }
7454  
7455                      if (!offset) {
7456                          if (sibling.nodeType == 3)
7457                              domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7458                          else
7459                              domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7460  
7461                          return;
7462                      }
7463  
7464                      // Find the text node and offset
7465                      while (sibling) {
7466                          nodeValue = sibling.nodeValue;
7467                          textNodeOffset += nodeValue.length;
7468  
7469                          // We are at or passed the position we where looking for
7470                          if (textNodeOffset >= offset) {
7471                              container = sibling;
7472                              textNodeOffset -= offset;
7473                              textNodeOffset = nodeValue.length - textNodeOffset;
7474                              break;
7475                          }
7476  
7477                          sibling = sibling.nextSibling;
7478                      }
7479                  } else {
7480                      // Find the text node and offset
7481                      sibling = container.previousSibling;
7482  
7483                      if (!sibling)
7484                          return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7485  
7486                      // If there isn't any text to loop then use the first position
7487                      if (!offset) {
7488                          if (container.nodeType == 3)
7489                              domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7490                          else
7491                              domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7492  
7493                          return;
7494                      }
7495  
7496                      while (sibling) {
7497                          textNodeOffset += sibling.nodeValue.length;
7498  
7499                          // We are at or passed the position we where looking for
7500                          if (textNodeOffset >= offset) {
7501                              container = sibling;
7502                              textNodeOffset -= offset;
7503                              break;
7504                          }
7505  
7506                          sibling = sibling.previousSibling;
7507                      }
7508                  }
7509  
7510                  domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7511              };
7512  
7513              try {
7514                  // Find start point
7515                  findEndPoint(true);
7516  
7517                  // Find end point if needed
7518                  if (!collapsed)
7519                      findEndPoint();
7520              } catch (ex) {
7521                  // IE has a nasty bug where text nodes might throw "invalid argument" when you
7522                  // access the nodeValue or other properties of text nodes. This seems to happend when
7523                  // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7524                  if (ex.number == -2147024809) {
7525                      // Get the current selection
7526                      bookmark = self.getBookmark(2);
7527  
7528                      // Get start element
7529                      tmpRange = ieRange.duplicate();
7530                      tmpRange.collapse(true);
7531                      element = tmpRange.parentElement();
7532  
7533                      // Get end element
7534                      if (!collapsed) {
7535                          tmpRange = ieRange.duplicate();
7536                          tmpRange.collapse(false);
7537                          element2 = tmpRange.parentElement();
7538                          element2.innerHTML = element2.innerHTML;
7539                      }
7540  
7541                      // Remove the broken elements
7542                      element.innerHTML = element.innerHTML;
7543  
7544                      // Restore the selection
7545                      self.moveToBookmark(bookmark);
7546  
7547                      // Since the range has moved we need to re-get it
7548                      ieRange = selection.getRng();
7549  
7550                      // Find start point
7551                      findEndPoint(true);
7552  
7553                      // Find end point if needed
7554                      if (!collapsed)
7555                          findEndPoint();
7556                  } else
7557                      throw ex; // Throw other errors
7558              }
7559  
7560              return domRange;
7561          };
7562  
7563          this.getBookmark = function(type) {
7564              var rng = selection.getRng(), start, end, bookmark = {};
7565  
7566  			function getIndexes(node) {
7567                  var parent, root, children, i, indexes = [];
7568  
7569                  parent = node.parentNode;
7570                  root = dom.getRoot().parentNode;
7571  
7572                  while (parent != root && parent.nodeType !== 9) {
7573                      children = parent.children;
7574  
7575                      i = children.length;
7576                      while (i--) {
7577                          if (node === children[i]) {
7578                              indexes.push(i);
7579                              break;
7580                          }
7581                      }
7582  
7583                      node = parent;
7584                      parent = parent.parentNode;
7585                  }
7586  
7587                  return indexes;
7588              };
7589  
7590  			function getBookmarkEndPoint(start) {
7591                  var position;
7592  
7593                  position = getPosition(rng, start);
7594                  if (position) {
7595                      return {
7596                          position : position.position,
7597                          offset : position.offset,
7598                          indexes : getIndexes(position.node),
7599                          inside : position.inside
7600                      };
7601                  }
7602              };
7603  
7604              // Non ubstructive bookmark
7605              if (type === 2) {
7606                  // Handle text selection
7607                  if (!rng.item) {
7608                      bookmark.start = getBookmarkEndPoint(true);
7609  
7610                      if (!selection.isCollapsed())
7611                          bookmark.end = getBookmarkEndPoint();
7612                  } else
7613                      bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7614              }
7615  
7616              return bookmark;
7617          };
7618  
7619          this.moveToBookmark = function(bookmark) {
7620              var rng, body = dom.doc.body;
7621  
7622  			function resolveIndexes(indexes) {
7623                  var node, i, idx, children;
7624  
7625                  node = dom.getRoot();
7626                  for (i = indexes.length - 1; i >= 0; i--) {
7627                      children = node.children;
7628                      idx = indexes[i];
7629  
7630                      if (idx <= children.length - 1) {
7631                          node = children[idx];
7632                      }
7633                  }
7634  
7635                  return node;
7636              };
7637              
7638  			function setBookmarkEndPoint(start) {
7639                  var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7640  
7641                  if (endPoint) {
7642                      moveLeft = endPoint.position > 0;
7643  
7644                      moveRng = body.createTextRange();
7645                      moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7646  
7647                      offset = endPoint.offset;
7648                      if (offset !== undef) {
7649                          moveRng.collapse(endPoint.inside || moveLeft);
7650                          moveRng.moveStart('character', moveLeft ? -offset : offset);
7651                      } else
7652                          moveRng.collapse(start);
7653  
7654                      rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7655  
7656                      if (start)
7657                          rng.collapse(true);
7658                  }
7659              };
7660  
7661              if (bookmark.start) {
7662                  if (bookmark.start.ctrl) {
7663                      rng = body.createControlRange();
7664                      rng.addElement(resolveIndexes(bookmark.start.indexes));
7665                      rng.select();
7666                  } else {
7667                      rng = body.createTextRange();
7668                      setBookmarkEndPoint(true);
7669                      setBookmarkEndPoint();
7670                      rng.select();
7671                  }
7672              }
7673          };
7674  
7675          this.addRange = function(rng) {
7676              var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,
7677                  doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;
7678  
7679  			function setEndPoint(start) {
7680                  var container, offset, marker, tmpRng, nodes;
7681  
7682                  marker = dom.create('a');
7683                  container = start ? startContainer : endContainer;
7684                  offset = start ? startOffset : endOffset;
7685                  tmpRng = ieRng.duplicate();
7686  
7687                  if (container == doc || container == doc.documentElement) {
7688                      container = body;
7689                      offset = 0;
7690                  }
7691  
7692                  if (container.nodeType == 3) {
7693                      container.parentNode.insertBefore(marker, container);
7694                      tmpRng.moveToElementText(marker);
7695                      tmpRng.moveStart('character', offset);
7696                      dom.remove(marker);
7697                      ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7698                  } else {
7699                      nodes = container.childNodes;
7700  
7701                      if (nodes.length) {
7702                          if (offset >= nodes.length) {
7703                              dom.insertAfter(marker, nodes[nodes.length - 1]);
7704                          } else {
7705                              container.insertBefore(marker, nodes[offset]);
7706                          }
7707  
7708                          tmpRng.moveToElementText(marker);
7709                      } else if (container.canHaveHTML) {
7710                          // Empty node selection for example <div>|</div>
7711                          // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7712                          container.innerHTML = '<span>\uFEFF</span>';
7713                          marker = container.firstChild;
7714                          tmpRng.moveToElementText(marker);
7715                          tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7716                      }
7717  
7718                      ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7719                      dom.remove(marker);
7720                  }
7721              }
7722  
7723              // Setup some shorter versions
7724              startContainer = rng.startContainer;
7725              startOffset = rng.startOffset;
7726              endContainer = rng.endContainer;
7727              endOffset = rng.endOffset;
7728              ieRng = body.createTextRange();
7729  
7730              // If single element selection then try making a control selection out of it
7731              if (startContainer == endContainer && startContainer.nodeType == 1) {
7732                  // Trick to place the caret inside an empty block element like <p></p>
7733                  if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7734                      if (startContainer.canHaveHTML) {
7735                          // Check if previous sibling is an empty block if it is then we need to render it
7736                          // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7737                          // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7738                          sibling = startContainer.previousSibling;
7739                          if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7740                              sibling.innerHTML = '\uFEFF';
7741                          } else {
7742                              sibling = null;
7743                          }
7744  
7745                          startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7746                          ieRng.moveToElementText(startContainer.lastChild);
7747                          ieRng.select();
7748                          dom.doc.selection.clear();
7749                          startContainer.innerHTML = '';
7750  
7751                          if (sibling) {
7752                              sibling.innerHTML = '';
7753                          }
7754                          return;
7755                      } else {
7756                          startOffset = dom.nodeIndex(startContainer);
7757                          startContainer = startContainer.parentNode;
7758                      }
7759                  }
7760  
7761                  if (startOffset == endOffset - 1) {
7762                      try {
7763                          ctrlElm = startContainer.childNodes[startOffset];
7764                          ctrlRng = body.createControlRange();
7765                          ctrlRng.addElement(ctrlElm);
7766                          ctrlRng.select();
7767  
7768                          // Check if the range produced is on the correct element and is a control range
7769                          // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398
7770                          nativeRng = selection.getRng();
7771                          if (nativeRng.item && ctrlElm === nativeRng.item(0)) {
7772                              return;
7773                          }
7774                      } catch (ex) {
7775                          // Ignore
7776                      }
7777                  }
7778              }
7779  
7780              // Set start/end point of selection
7781              setEndPoint(true);
7782              setEndPoint();
7783  
7784              // Select the new range and scroll it into view
7785              ieRng.select();
7786          };
7787  
7788          // Expose range method
7789          this.getRangeAt = getRange;
7790      };
7791  
7792      // Expose the selection object
7793      tinymce.dom.TridentSelection = Selection;
7794  })();
7795  
7796  /*
7797   * Sizzle CSS Selector Engine
7798   *  Copyright, The Dojo Foundation
7799   *  Released under the MIT, BSD, and GPL Licenses.
7800   *  More information: http://sizzlejs.com/
7801   */
7802  (function(){
7803  
7804  var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
7805      expando = "sizcache",
7806      done = 0,
7807      toString = Object.prototype.toString,
7808      hasDuplicate = false,
7809      baseHasDuplicate = true,
7810      rBackslash = /\\/g,
7811      rReturn = /\r\n/g,
7812      rNonWord = /\W/;
7813  
7814  // Here we check if the JavaScript engine is using some sort of
7815  // optimization where it does not always call our comparision
7816  // function. If that is the case, discard the hasDuplicate value.
7817  //   Thus far that includes Google Chrome.
7818  [0, 0].sort(function() {
7819      baseHasDuplicate = false;
7820      return 0;
7821  });
7822  
7823  var Sizzle = function( selector, context, results, seed ) {
7824      results = results || [];
7825      context = context || document;
7826  
7827      var origContext = context;
7828  
7829      if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
7830          return [];
7831      }
7832  
7833      if ( !selector || typeof selector !== "string" ) {
7834          return results;
7835      }
7836  
7837      var m, set, checkSet, extra, ret, cur, pop, i,
7838          prune = true,
7839          contextXML = Sizzle.isXML( context ),
7840          parts = [],
7841          soFar = selector;
7842  
7843      // Reset the position of the chunker regexp (start from head)
7844      do {
7845          chunker.exec( "" );
7846          m = chunker.exec( soFar );
7847  
7848          if ( m ) {
7849              soFar = m[3];
7850  
7851              parts.push( m[1] );
7852  
7853              if ( m[2] ) {
7854                  extra = m[3];
7855                  break;
7856              }
7857          }
7858      } while ( m );
7859  
7860      if ( parts.length > 1 && origPOS.exec( selector ) ) {
7861  
7862          if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
7863              set = posProcess( parts[0] + parts[1], context, seed );
7864  
7865          } else {
7866              set = Expr.relative[ parts[0] ] ?
7867                  [ context ] :
7868                  Sizzle( parts.shift(), context );
7869  
7870              while ( parts.length ) {
7871                  selector = parts.shift();
7872  
7873                  if ( Expr.relative[ selector ] ) {
7874                      selector += parts.shift();
7875                  }
7876  
7877                  set = posProcess( selector, set, seed );
7878              }
7879          }
7880  
7881      } else {
7882          // Take a shortcut and set the context if the root selector is an ID
7883          // (but not if it'll be faster if the inner selector is an ID)
7884          if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
7885                  Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
7886  
7887              ret = Sizzle.find( parts.shift(), context, contextXML );
7888              context = ret.expr ?
7889                  Sizzle.filter( ret.expr, ret.set )[0] :
7890                  ret.set[0];
7891          }
7892  
7893          if ( context ) {
7894              ret = seed ?
7895                  { expr: parts.pop(), set: makeArray(seed) } :
7896                  Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
7897  
7898              set = ret.expr ?
7899                  Sizzle.filter( ret.expr, ret.set ) :
7900                  ret.set;
7901  
7902              if ( parts.length > 0 ) {
7903                  checkSet = makeArray( set );
7904  
7905              } else {
7906                  prune = false;
7907              }
7908  
7909              while ( parts.length ) {
7910                  cur = parts.pop();
7911                  pop = cur;
7912  
7913                  if ( !Expr.relative[ cur ] ) {
7914                      cur = "";
7915                  } else {
7916                      pop = parts.pop();
7917                  }
7918  
7919                  if ( pop == null ) {
7920                      pop = context;
7921                  }
7922  
7923                  Expr.relative[ cur ]( checkSet, pop, contextXML );
7924              }
7925  
7926          } else {
7927              checkSet = parts = [];
7928          }
7929      }
7930  
7931      if ( !checkSet ) {
7932          checkSet = set;
7933      }
7934  
7935      if ( !checkSet ) {
7936          Sizzle.error( cur || selector );
7937      }
7938  
7939      if ( toString.call(checkSet) === "[object Array]" ) {
7940          if ( !prune ) {
7941              results.push.apply( results, checkSet );
7942  
7943          } else if ( context && context.nodeType === 1 ) {
7944              for ( i = 0; checkSet[i] != null; i++ ) {
7945                  if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
7946                      results.push( set[i] );
7947                  }
7948              }
7949  
7950          } else {
7951              for ( i = 0; checkSet[i] != null; i++ ) {
7952                  if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
7953                      results.push( set[i] );
7954                  }
7955              }
7956          }
7957  
7958      } else {
7959          makeArray( checkSet, results );
7960      }
7961  
7962      if ( extra ) {
7963          Sizzle( extra, origContext, results, seed );
7964          Sizzle.uniqueSort( results );
7965      }
7966  
7967      return results;
7968  };
7969  
7970  Sizzle.uniqueSort = function( results ) {
7971      if ( sortOrder ) {
7972          hasDuplicate = baseHasDuplicate;
7973          results.sort( sortOrder );
7974  
7975          if ( hasDuplicate ) {
7976              for ( var i = 1; i < results.length; i++ ) {
7977                  if ( results[i] === results[ i - 1 ] ) {
7978                      results.splice( i--, 1 );
7979                  }
7980              }
7981          }
7982      }
7983  
7984      return results;
7985  };
7986  
7987  Sizzle.matches = function( expr, set ) {
7988      return Sizzle( expr, null, null, set );
7989  };
7990  
7991  Sizzle.matchesSelector = function( node, expr ) {
7992      return Sizzle( expr, null, null, [node] ).length > 0;
7993  };
7994  
7995  Sizzle.find = function( expr, context, isXML ) {
7996      var set, i, len, match, type, left;
7997  
7998      if ( !expr ) {
7999          return [];
8000      }
8001  
8002      for ( i = 0, len = Expr.order.length; i < len; i++ ) {
8003          type = Expr.order[i];
8004  
8005          if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
8006              left = match[1];
8007              match.splice( 1, 1 );
8008  
8009              if ( left.substr( left.length - 1 ) !== "\\" ) {
8010                  match[1] = (match[1] || "").replace( rBackslash, "" );
8011                  set = Expr.find[ type ]( match, context, isXML );
8012  
8013                  if ( set != null ) {
8014                      expr = expr.replace( Expr.match[ type ], "" );
8015                      break;
8016                  }
8017              }
8018          }
8019      }
8020  
8021      if ( !set ) {
8022          set = typeof context.getElementsByTagName !== "undefined" ?
8023              context.getElementsByTagName( "*" ) :
8024              [];
8025      }
8026  
8027      return { set: set, expr: expr };
8028  };
8029  
8030  Sizzle.filter = function( expr, set, inplace, not ) {
8031      var match, anyFound,
8032          type, found, item, filter, left,
8033          i, pass,
8034          old = expr,
8035          result = [],
8036          curLoop = set,
8037          isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
8038  
8039      while ( expr && set.length ) {
8040          for ( type in Expr.filter ) {
8041              if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
8042                  filter = Expr.filter[ type ];
8043                  left = match[1];
8044  
8045                  anyFound = false;
8046  
8047                  match.splice(1,1);
8048  
8049                  if ( left.substr( left.length - 1 ) === "\\" ) {
8050                      continue;
8051                  }
8052  
8053                  if ( curLoop === result ) {
8054                      result = [];
8055                  }
8056  
8057                  if ( Expr.preFilter[ type ] ) {
8058                      match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
8059  
8060                      if ( !match ) {
8061                          anyFound = found = true;
8062  
8063                      } else if ( match === true ) {
8064                          continue;
8065                      }
8066                  }
8067  
8068                  if ( match ) {
8069                      for ( i = 0; (item = curLoop[i]) != null; i++ ) {
8070                          if ( item ) {
8071                              found = filter( item, match, i, curLoop );
8072                              pass = not ^ found;
8073  
8074                              if ( inplace && found != null ) {
8075                                  if ( pass ) {
8076                                      anyFound = true;
8077  
8078                                  } else {
8079                                      curLoop[i] = false;
8080                                  }
8081  
8082                              } else if ( pass ) {
8083                                  result.push( item );
8084                                  anyFound = true;
8085                              }
8086                          }
8087                      }
8088                  }
8089  
8090                  if ( found !== undefined ) {
8091                      if ( !inplace ) {
8092                          curLoop = result;
8093                      }
8094  
8095                      expr = expr.replace( Expr.match[ type ], "" );
8096  
8097                      if ( !anyFound ) {
8098                          return [];
8099                      }
8100  
8101                      break;
8102                  }
8103              }
8104          }
8105  
8106          // Improper expression
8107          if ( expr === old ) {
8108              if ( anyFound == null ) {
8109                  Sizzle.error( expr );
8110  
8111              } else {
8112                  break;
8113              }
8114          }
8115  
8116          old = expr;
8117      }
8118  
8119      return curLoop;
8120  };
8121  
8122  Sizzle.error = function( msg ) {
8123      throw new Error( "Syntax error, unrecognized expression: " + msg );
8124  };
8125  
8126  var getText = Sizzle.getText = function( elem ) {
8127      var i, node,
8128          nodeType = elem.nodeType,
8129          ret = "";
8130  
8131      if ( nodeType ) {
8132          if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
8133              // Use textContent || innerText for elements
8134              if ( typeof elem.textContent === 'string' ) {
8135                  return elem.textContent;
8136              } else if ( typeof elem.innerText === 'string' ) {
8137                  // Replace IE's carriage returns
8138                  return elem.innerText.replace( rReturn, '' );
8139              } else {
8140                  // Traverse it's children
8141                  for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
8142                      ret += getText( elem );
8143                  }
8144              }
8145          } else if ( nodeType === 3 || nodeType === 4 ) {
8146              return elem.nodeValue;
8147          }
8148      } else {
8149  
8150          // If no nodeType, this is expected to be an array
8151          for ( i = 0; (node = elem[i]); i++ ) {
8152              // Do not traverse comment nodes
8153              if ( node.nodeType !== 8 ) {
8154                  ret += getText( node );
8155              }
8156          }
8157      }
8158      return ret;
8159  };
8160  
8161  var Expr = Sizzle.selectors = {
8162      order: [ "ID", "NAME", "TAG" ],
8163  
8164      match: {
8165          ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8166          CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
8167          NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
8168          ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
8169          TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
8170          CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
8171          POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
8172          PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
8173      },
8174  
8175      leftMatch: {},
8176  
8177      attrMap: {
8178          "class": "className",
8179          "for": "htmlFor"
8180      },
8181  
8182      attrHandle: {
8183          href: function( elem ) {
8184              return elem.getAttribute( "href" );
8185          },
8186          type: function( elem ) {
8187              return elem.getAttribute( "type" );
8188          }
8189      },
8190  
8191      relative: {
8192          "+": function(checkSet, part){
8193              var isPartStr = typeof part === "string",
8194                  isTag = isPartStr && !rNonWord.test( part ),
8195                  isPartStrNotTag = isPartStr && !isTag;
8196  
8197              if ( isTag ) {
8198                  part = part.toLowerCase();
8199              }
8200  
8201              for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
8202                  if ( (elem = checkSet[i]) ) {
8203                      while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
8204  
8205                      checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
8206                          elem || false :
8207                          elem === part;
8208                  }
8209              }
8210  
8211              if ( isPartStrNotTag ) {
8212                  Sizzle.filter( part, checkSet, true );
8213              }
8214          },
8215  
8216          ">": function( checkSet, part ) {
8217              var elem,
8218                  isPartStr = typeof part === "string",
8219                  i = 0,
8220                  l = checkSet.length;
8221  
8222              if ( isPartStr && !rNonWord.test( part ) ) {
8223                  part = part.toLowerCase();
8224  
8225                  for ( ; i < l; i++ ) {
8226                      elem = checkSet[i];
8227  
8228                      if ( elem ) {
8229                          var parent = elem.parentNode;
8230                          checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
8231                      }
8232                  }
8233  
8234              } else {
8235                  for ( ; i < l; i++ ) {
8236                      elem = checkSet[i];
8237  
8238                      if ( elem ) {
8239                          checkSet[i] = isPartStr ?
8240                              elem.parentNode :
8241                              elem.parentNode === part;
8242                      }
8243                  }
8244  
8245                  if ( isPartStr ) {
8246                      Sizzle.filter( part, checkSet, true );
8247                  }
8248              }
8249          },
8250  
8251          "": function(checkSet, part, isXML){
8252              var nodeCheck,
8253                  doneName = done++,
8254                  checkFn = dirCheck;
8255  
8256              if ( typeof part === "string" && !rNonWord.test( part ) ) {
8257                  part = part.toLowerCase();
8258                  nodeCheck = part;
8259                  checkFn = dirNodeCheck;
8260              }
8261  
8262              checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
8263          },
8264  
8265          "~": function( checkSet, part, isXML ) {
8266              var nodeCheck,
8267                  doneName = done++,
8268                  checkFn = dirCheck;
8269  
8270              if ( typeof part === "string" && !rNonWord.test( part ) ) {
8271                  part = part.toLowerCase();
8272                  nodeCheck = part;
8273                  checkFn = dirNodeCheck;
8274              }
8275  
8276              checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
8277          }
8278      },
8279  
8280      find: {
8281          ID: function( match, context, isXML ) {
8282              if ( typeof context.getElementById !== "undefined" && !isXML ) {
8283                  var m = context.getElementById(match[1]);
8284                  // Check parentNode to catch when Blackberry 4.6 returns
8285                  // nodes that are no longer in the document #6963
8286                  return m && m.parentNode ? [m] : [];
8287              }
8288          },
8289  
8290          NAME: function( match, context ) {
8291              if ( typeof context.getElementsByName !== "undefined" ) {
8292                  var ret = [],
8293                      results = context.getElementsByName( match[1] );
8294  
8295                  for ( var i = 0, l = results.length; i < l; i++ ) {
8296                      if ( results[i].getAttribute("name") === match[1] ) {
8297                          ret.push( results[i] );
8298                      }
8299                  }
8300  
8301                  return ret.length === 0 ? null : ret;
8302              }
8303          },
8304  
8305          TAG: function( match, context ) {
8306              if ( typeof context.getElementsByTagName !== "undefined" ) {
8307                  return context.getElementsByTagName( match[1] );
8308              }
8309          }
8310      },
8311      preFilter: {
8312          CLASS: function( match, curLoop, inplace, result, not, isXML ) {
8313              match = " " + match[1].replace( rBackslash, "" ) + " ";
8314  
8315              if ( isXML ) {
8316                  return match;
8317              }
8318  
8319              for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
8320                  if ( elem ) {
8321                      if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
8322                          if ( !inplace ) {
8323                              result.push( elem );
8324                          }
8325  
8326                      } else if ( inplace ) {
8327                          curLoop[i] = false;
8328                      }
8329                  }
8330              }
8331  
8332              return false;
8333          },
8334  
8335          ID: function( match ) {
8336              return match[1].replace( rBackslash, "" );
8337          },
8338  
8339          TAG: function( match, curLoop ) {
8340              return match[1].replace( rBackslash, "" ).toLowerCase();
8341          },
8342  
8343          CHILD: function( match ) {
8344              if ( match[1] === "nth" ) {
8345                  if ( !match[2] ) {
8346                      Sizzle.error( match[0] );
8347                  }
8348  
8349                  match[2] = match[2].replace(/^\+|\s*/g, '');
8350  
8351                  // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
8352                  var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
8353                      match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
8354                      !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
8355  
8356                  // calculate the numbers (first)n+(last) including if they are negative
8357                  match[2] = (test[1] + (test[2] || 1)) - 0;
8358                  match[3] = test[3] - 0;
8359              }
8360              else if ( match[2] ) {
8361                  Sizzle.error( match[0] );
8362              }
8363  
8364              // TODO: Move to normal caching system
8365              match[0] = done++;
8366  
8367              return match;
8368          },
8369  
8370          ATTR: function( match, curLoop, inplace, result, not, isXML ) {
8371              var name = match[1] = match[1].replace( rBackslash, "" );
8372  
8373              if ( !isXML && Expr.attrMap[name] ) {
8374                  match[1] = Expr.attrMap[name];
8375              }
8376  
8377              // Handle if an un-quoted value was used
8378              match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
8379  
8380              if ( match[2] === "~=" ) {
8381                  match[4] = " " + match[4] + " ";
8382              }
8383  
8384              return match;
8385          },
8386  
8387          PSEUDO: function( match, curLoop, inplace, result, not ) {
8388              if ( match[1] === "not" ) {
8389                  // If we're dealing with a complex expression, or a simple one
8390                  if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
8391                      match[3] = Sizzle(match[3], null, null, curLoop);
8392  
8393                  } else {
8394                      var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
8395  
8396                      if ( !inplace ) {
8397                          result.push.apply( result, ret );
8398                      }
8399  
8400                      return false;
8401                  }
8402  
8403              } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
8404                  return true;
8405              }
8406  
8407              return match;
8408          },
8409  
8410          POS: function( match ) {
8411              match.unshift( true );
8412  
8413              return match;
8414          }
8415      },
8416  
8417      filters: {
8418          enabled: function( elem ) {
8419              return elem.disabled === false && elem.type !== "hidden";
8420          },
8421  
8422          disabled: function( elem ) {
8423              return elem.disabled === true;
8424          },
8425  
8426          checked: function( elem ) {
8427              return elem.checked === true;
8428          },
8429  
8430          selected: function( elem ) {
8431              // Accessing this property makes selected-by-default
8432              // options in Safari work properly
8433              if ( elem.parentNode ) {
8434                  elem.parentNode.selectedIndex;
8435              }
8436  
8437              return elem.selected === true;
8438          },
8439  
8440          parent: function( elem ) {
8441              return !!elem.firstChild;
8442          },
8443  
8444          empty: function( elem ) {
8445              return !elem.firstChild;
8446          },
8447  
8448          has: function( elem, i, match ) {
8449              return !!Sizzle( match[3], elem ).length;
8450          },
8451  
8452          header: function( elem ) {
8453              return (/h\d/i).test( elem.nodeName );
8454          },
8455  
8456          text: function( elem ) {
8457              var attr = elem.getAttribute( "type" ), type = elem.type;
8458              // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
8459              // use getAttribute instead to test this case
8460              return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
8461          },
8462  
8463          radio: function( elem ) {
8464              return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
8465          },
8466  
8467          checkbox: function( elem ) {
8468              return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
8469          },
8470  
8471          file: function( elem ) {
8472              return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
8473          },
8474  
8475          password: function( elem ) {
8476              return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
8477          },
8478  
8479          submit: function( elem ) {
8480              var name = elem.nodeName.toLowerCase();
8481              return (name === "input" || name === "button") && "submit" === elem.type;
8482          },
8483  
8484          image: function( elem ) {
8485              return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
8486          },
8487  
8488          reset: function( elem ) {
8489              var name = elem.nodeName.toLowerCase();
8490              return (name === "input" || name === "button") && "reset" === elem.type;
8491          },
8492  
8493          button: function( elem ) {
8494              var name = elem.nodeName.toLowerCase();
8495              return name === "input" && "button" === elem.type || name === "button";
8496          },
8497  
8498          input: function( elem ) {
8499              return (/input|select|textarea|button/i).test( elem.nodeName );
8500          },
8501  
8502          focus: function( elem ) {
8503              return elem === elem.ownerDocument.activeElement;
8504          }
8505      },
8506      setFilters: {
8507          first: function( elem, i ) {
8508              return i === 0;
8509          },
8510  
8511          last: function( elem, i, match, array ) {
8512              return i === array.length - 1;
8513          },
8514  
8515          even: function( elem, i ) {
8516              return i % 2 === 0;
8517          },
8518  
8519          odd: function( elem, i ) {
8520              return i % 2 === 1;
8521          },
8522  
8523          lt: function( elem, i, match ) {
8524              return i < match[3] - 0;
8525          },
8526  
8527          gt: function( elem, i, match ) {
8528              return i > match[3] - 0;
8529          },
8530  
8531          nth: function( elem, i, match ) {
8532              return match[3] - 0 === i;
8533          },
8534  
8535          eq: function( elem, i, match ) {
8536              return match[3] - 0 === i;
8537          }
8538      },
8539      filter: {
8540          PSEUDO: function( elem, match, i, array ) {
8541              var name = match[1],
8542                  filter = Expr.filters[ name ];
8543  
8544              if ( filter ) {
8545                  return filter( elem, i, match, array );
8546  
8547              } else if ( name === "contains" ) {
8548                  return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
8549  
8550              } else if ( name === "not" ) {
8551                  var not = match[3];
8552  
8553                  for ( var j = 0, l = not.length; j < l; j++ ) {
8554                      if ( not[j] === elem ) {
8555                          return false;
8556                      }
8557                  }
8558  
8559                  return true;
8560  
8561              } else {
8562                  Sizzle.error( name );
8563              }
8564          },
8565  
8566          CHILD: function( elem, match ) {
8567              var first, last,
8568                  doneName, parent, cache,
8569                  count, diff,
8570                  type = match[1],
8571                  node = elem;
8572  
8573              switch ( type ) {
8574                  case "only":
8575                  case "first":
8576                      while ( (node = node.previousSibling) ) {
8577                          if ( node.nodeType === 1 ) {
8578                              return false;
8579                          }
8580                      }
8581  
8582                      if ( type === "first" ) {
8583                          return true;
8584                      }
8585  
8586                      node = elem;
8587  
8588                      /* falls through */
8589                  case "last":
8590                      while ( (node = node.nextSibling) ) {
8591                          if ( node.nodeType === 1 ) {
8592                              return false;
8593                          }
8594                      }
8595  
8596                      return true;
8597  
8598                  case "nth":
8599                      first = match[2];
8600                      last = match[3];
8601  
8602                      if ( first === 1 && last === 0 ) {
8603                          return true;
8604                      }
8605  
8606                      doneName = match[0];
8607                      parent = elem.parentNode;
8608  
8609                      if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
8610                          count = 0;
8611  
8612                          for ( node = parent.firstChild; node; node = node.nextSibling ) {
8613                              if ( node.nodeType === 1 ) {
8614                                  node.nodeIndex = ++count;
8615                              }
8616                          }
8617  
8618                          parent[ expando ] = doneName;
8619                      }
8620  
8621                      diff = elem.nodeIndex - last;
8622  
8623                      if ( first === 0 ) {
8624                          return diff === 0;
8625  
8626                      } else {
8627                          return ( diff % first === 0 && diff / first >= 0 );
8628                      }
8629              }
8630          },
8631  
8632          ID: function( elem, match ) {
8633              return elem.nodeType === 1 && elem.getAttribute("id") === match;
8634          },
8635  
8636          TAG: function( elem, match ) {
8637              return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
8638          },
8639  
8640          CLASS: function( elem, match ) {
8641              return (" " + (elem.className || elem.getAttribute("class")) + " ")
8642                  .indexOf( match ) > -1;
8643          },
8644  
8645          ATTR: function( elem, match ) {
8646              var name = match[1],
8647                  result = Sizzle.attr ?
8648                      Sizzle.attr( elem, name ) :
8649                      Expr.attrHandle[ name ] ?
8650                      Expr.attrHandle[ name ]( elem ) :
8651                      elem[ name ] != null ?
8652                          elem[ name ] :
8653                          elem.getAttribute( name ),
8654                  value = result + "",
8655                  type = match[2],
8656                  check = match[4];
8657  
8658              return result == null ?
8659                  type === "!=" :
8660                  !type && Sizzle.attr ?
8661                  result != null :
8662                  type === "=" ?
8663                  value === check :
8664                  type === "*=" ?
8665                  value.indexOf(check) >= 0 :
8666                  type === "~=" ?
8667                  (" " + value + " ").indexOf(check) >= 0 :
8668                  !check ?
8669                  value && result !== false :
8670                  type === "!=" ?
8671                  value !== check :
8672                  type === "^=" ?
8673                  value.indexOf(check) === 0 :
8674                  type === "$=" ?
8675                  value.substr(value.length - check.length) === check :
8676                  type === "|=" ?
8677                  value === check || value.substr(0, check.length + 1) === check + "-" :
8678                  false;
8679          },
8680  
8681          POS: function( elem, match, i, array ) {
8682              var name = match[2],
8683                  filter = Expr.setFilters[ name ];
8684  
8685              if ( filter ) {
8686                  return filter( elem, i, match, array );
8687              }
8688          }
8689      }
8690  };
8691  
8692  var origPOS = Expr.match.POS,
8693      fescape = function(all, num){
8694          return "\\" + (num - 0 + 1);
8695      };
8696  
8697  for ( var type in Expr.match ) {
8698      Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
8699      Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
8700  }
8701  // Expose origPOS
8702  // "global" as in regardless of relation to brackets/parens
8703  Expr.match.globalPOS = origPOS;
8704  
8705  var makeArray = function( array, results ) {
8706      array = Array.prototype.slice.call( array, 0 );
8707  
8708      if ( results ) {
8709          results.push.apply( results, array );
8710          return results;
8711      }
8712  
8713      return array;
8714  };
8715  
8716  // Perform a simple check to determine if the browser is capable of
8717  // converting a NodeList to an array using builtin methods.
8718  // Also verifies that the returned array holds DOM nodes
8719  // (which is not the case in the Blackberry browser)
8720  try {
8721      Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
8722  
8723  // Provide a fallback method if it does not work
8724  } catch( e ) {
8725      makeArray = function( array, results ) {
8726          var i = 0,
8727              ret = results || [];
8728  
8729          if ( toString.call(array) === "[object Array]" ) {
8730              Array.prototype.push.apply( ret, array );
8731  
8732          } else {
8733              if ( typeof array.length === "number" ) {
8734                  for ( var l = array.length; i < l; i++ ) {
8735                      ret.push( array[i] );
8736                  }
8737  
8738              } else {
8739                  for ( ; array[i]; i++ ) {
8740                      ret.push( array[i] );
8741                  }
8742              }
8743          }
8744  
8745          return ret;
8746      };
8747  }
8748  
8749  var sortOrder, siblingCheck;
8750  
8751  if ( document.documentElement.compareDocumentPosition ) {
8752      sortOrder = function( a, b ) {
8753          if ( a === b ) {
8754              hasDuplicate = true;
8755              return 0;
8756          }
8757  
8758          if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
8759              return a.compareDocumentPosition ? -1 : 1;
8760          }
8761  
8762          return a.compareDocumentPosition(b) & 4 ? -1 : 1;
8763      };
8764  
8765  } else {
8766      sortOrder = function( a, b ) {
8767          // The nodes are identical, we can exit early
8768          if ( a === b ) {
8769              hasDuplicate = true;
8770              return 0;
8771  
8772          // Fallback to using sourceIndex (in IE) if it's available on both nodes
8773          } else if ( a.sourceIndex && b.sourceIndex ) {
8774              return a.sourceIndex - b.sourceIndex;
8775          }
8776  
8777          var al, bl,
8778              ap = [],
8779              bp = [],
8780              aup = a.parentNode,
8781              bup = b.parentNode,
8782              cur = aup;
8783  
8784          // If the nodes are siblings (or identical) we can do a quick check
8785          if ( aup === bup ) {
8786              return siblingCheck( a, b );
8787  
8788          // If no parents were found then the nodes are disconnected
8789          } else if ( !aup ) {
8790              return -1;
8791  
8792          } else if ( !bup ) {
8793              return 1;
8794          }
8795  
8796          // Otherwise they're somewhere else in the tree so we need
8797          // to build up a full list of the parentNodes for comparison
8798          while ( cur ) {
8799              ap.unshift( cur );
8800              cur = cur.parentNode;
8801          }
8802  
8803          cur = bup;
8804  
8805          while ( cur ) {
8806              bp.unshift( cur );
8807              cur = cur.parentNode;
8808          }
8809  
8810          al = ap.length;
8811          bl = bp.length;
8812  
8813          // Start walking down the tree looking for a discrepancy
8814          for ( var i = 0; i < al && i < bl; i++ ) {
8815              if ( ap[i] !== bp[i] ) {
8816                  return siblingCheck( ap[i], bp[i] );
8817              }
8818          }
8819  
8820          // We ended someplace up the tree so do a sibling check
8821          return i === al ?
8822              siblingCheck( a, bp[i], -1 ) :
8823              siblingCheck( ap[i], b, 1 );
8824      };
8825  
8826      siblingCheck = function( a, b, ret ) {
8827          if ( a === b ) {
8828              return ret;
8829          }
8830  
8831          var cur = a.nextSibling;
8832  
8833          while ( cur ) {
8834              if ( cur === b ) {
8835                  return -1;
8836              }
8837  
8838              cur = cur.nextSibling;
8839          }
8840  
8841          return 1;
8842      };
8843  }
8844  
8845  // Check to see if the browser returns elements by name when
8846  // querying by getElementById (and provide a workaround)
8847  (function(){
8848      // We're going to inject a fake input element with a specified name
8849      var form = document.createElement("div"),
8850          id = "script" + (new Date()).getTime(),
8851          root = document.documentElement;
8852  
8853      form.innerHTML = "<a name='" + id + "'/>";
8854  
8855      // Inject it into the root element, check its status, and remove it quickly
8856      root.insertBefore( form, root.firstChild );
8857  
8858      // The workaround has to do additional checks after a getElementById
8859      // Which slows things down for other browsers (hence the branching)
8860      if ( document.getElementById( id ) ) {
8861          Expr.find.ID = function( match, context, isXML ) {
8862              if ( typeof context.getElementById !== "undefined" && !isXML ) {
8863                  var m = context.getElementById(match[1]);
8864  
8865                  return m ?
8866                      m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
8867                          [m] :
8868                          undefined :
8869                      [];
8870              }
8871          };
8872  
8873          Expr.filter.ID = function( elem, match ) {
8874              var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
8875  
8876              return elem.nodeType === 1 && node && node.nodeValue === match;
8877          };
8878      }
8879  
8880      root.removeChild( form );
8881  
8882      // release memory in IE
8883      root = form = null;
8884  })();
8885  
8886  (function(){
8887      // Check to see if the browser returns only elements
8888      // when doing getElementsByTagName("*")
8889  
8890      // Create a fake element
8891      var div = document.createElement("div");
8892      div.appendChild( document.createComment("") );
8893  
8894      // Make sure no comments are found
8895      if ( div.getElementsByTagName("*").length > 0 ) {
8896          Expr.find.TAG = function( match, context ) {
8897              var results = context.getElementsByTagName( match[1] );
8898  
8899              // Filter out possible comments
8900              if ( match[1] === "*" ) {
8901                  var tmp = [];
8902  
8903                  for ( var i = 0; results[i]; i++ ) {
8904                      if ( results[i].nodeType === 1 ) {
8905                          tmp.push( results[i] );
8906                      }
8907                  }
8908  
8909                  results = tmp;
8910              }
8911  
8912              return results;
8913          };
8914      }
8915  
8916      // Check to see if an attribute returns normalized href attributes
8917      div.innerHTML = "<a href='#'></a>";
8918  
8919      if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
8920              div.firstChild.getAttribute("href") !== "#" ) {
8921  
8922          Expr.attrHandle.href = function( elem ) {
8923              return elem.getAttribute( "href", 2 );
8924          };
8925      }
8926  
8927      // release memory in IE
8928      div = null;
8929  })();
8930  
8931  if ( document.querySelectorAll ) {
8932      (function(){
8933          var oldSizzle = Sizzle,
8934              div = document.createElement("div"),
8935              id = "__sizzle__";
8936  
8937          div.innerHTML = "<p class='TEST'></p>";
8938  
8939          // Safari can't handle uppercase or unicode characters when
8940          // in quirks mode.
8941          if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
8942              return;
8943          }
8944  
8945          Sizzle = function( query, context, extra, seed ) {
8946              context = context || document;
8947  
8948              // Only use querySelectorAll on non-XML documents
8949              // (ID selectors don't work in non-HTML documents)
8950              if ( !seed && !Sizzle.isXML(context) ) {
8951                  // See if we find a selector to speed up
8952                  var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
8953  
8954                  if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
8955                      // Speed-up: Sizzle("TAG")
8956                      if ( match[1] ) {
8957                          return makeArray( context.getElementsByTagName( query ), extra );
8958  
8959                      // Speed-up: Sizzle(".CLASS")
8960                      } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
8961                          return makeArray( context.getElementsByClassName( match[2] ), extra );
8962                      }
8963                  }
8964  
8965                  if ( context.nodeType === 9 ) {
8966                      // Speed-up: Sizzle("body")
8967                      // The body element only exists once, optimize finding it
8968                      if ( query === "body" && context.body ) {
8969                          return makeArray( [ context.body ], extra );
8970  
8971                      // Speed-up: Sizzle("#ID")
8972                      } else if ( match && match[3] ) {
8973                          var elem = context.getElementById( match[3] );
8974  
8975                          // Check parentNode to catch when Blackberry 4.6 returns
8976                          // nodes that are no longer in the document #6963
8977                          if ( elem && elem.parentNode ) {
8978                              // Handle the case where IE and Opera return items
8979                              // by name instead of ID
8980                              if ( elem.id === match[3] ) {
8981                                  return makeArray( [ elem ], extra );
8982                              }
8983  
8984                          } else {
8985                              return makeArray( [], extra );
8986                          }
8987                      }
8988  
8989                      try {
8990                          return makeArray( context.querySelectorAll(query), extra );
8991                      } catch(qsaError) {}
8992  
8993                  // qSA works strangely on Element-rooted queries
8994                  // We can work around this by specifying an extra ID on the root
8995                  // and working up from there (Thanks to Andrew Dupont for the technique)
8996                  // IE 8 doesn't work on object elements
8997                  } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
8998                      var oldContext = context,
8999                          old = context.getAttribute( "id" ),
9000                          nid = old || id,
9001                          hasParent = context.parentNode,
9002                          relativeHierarchySelector = /^\s*[+~]/.test( query );
9003  
9004                      if ( !old ) {
9005                          context.setAttribute( "id", nid );
9006                      } else {
9007                          nid = nid.replace( /'/g, "\\$&" );
9008                      }
9009                      if ( relativeHierarchySelector && hasParent ) {
9010                          context = context.parentNode;
9011                      }
9012  
9013                      try {
9014                          if ( !relativeHierarchySelector || hasParent ) {
9015                              return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
9016                          }
9017  
9018                      } catch(pseudoError) {
9019                      } finally {
9020                          if ( !old ) {
9021                              oldContext.removeAttribute( "id" );
9022                          }
9023                      }
9024                  }
9025              }
9026  
9027              return oldSizzle(query, context, extra, seed);
9028          };
9029  
9030          for ( var prop in oldSizzle ) {
9031              Sizzle[ prop ] = oldSizzle[ prop ];
9032          }
9033  
9034          // release memory in IE
9035          div = null;
9036      })();
9037  }
9038  
9039  (function(){
9040      var html = document.documentElement,
9041          matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
9042  
9043      if ( matches ) {
9044          // Check to see if it's possible to do matchesSelector
9045          // on a disconnected node (IE 9 fails this)
9046          var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
9047              pseudoWorks = false;
9048  
9049          try {
9050              // This should fail with an exception
9051              // Gecko does not error, returns false instead
9052              matches.call( document.documentElement, "[test!='']:sizzle" );
9053  
9054          } catch( pseudoError ) {
9055              pseudoWorks = true;
9056          }
9057  
9058          Sizzle.matchesSelector = function( node, expr ) {
9059              // Make sure that attribute selectors are quoted
9060              expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
9061  
9062              if ( !Sizzle.isXML( node ) ) {
9063                  try {
9064                      if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
9065                          var ret = matches.call( node, expr );
9066  
9067                          // IE 9's matchesSelector returns false on disconnected nodes
9068                          if ( ret || !disconnectedMatch ||
9069                                  // As well, disconnected nodes are said to be in a document
9070                                  // fragment in IE 9, so check for that
9071                                  node.document && node.document.nodeType !== 11 ) {
9072                              return ret;
9073                          }
9074                      }
9075                  } catch(e) {}
9076              }
9077  
9078              return Sizzle(expr, null, null, [node]).length > 0;
9079          };
9080      }
9081  })();
9082  
9083  (function(){
9084      var div = document.createElement("div");
9085  
9086      div.innerHTML = "<div class='test e'></div><div class='test'></div>";
9087  
9088      // Opera can't find a second classname (in 9.6)
9089      // Also, make sure that getElementsByClassName actually exists
9090      if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
9091          return;
9092      }
9093  
9094      // Safari caches class attributes, doesn't catch changes (in 3.2)
9095      div.lastChild.className = "e";
9096  
9097      if ( div.getElementsByClassName("e").length === 1 ) {
9098          return;
9099      }
9100  
9101      Expr.order.splice(1, 0, "CLASS");
9102      Expr.find.CLASS = function( match, context, isXML ) {
9103          if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
9104              return context.getElementsByClassName(match[1]);
9105          }
9106      };
9107  
9108      // release memory in IE
9109      div = null;
9110  })();
9111  
9112  function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9113      for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9114          var elem = checkSet[i];
9115  
9116          if ( elem ) {
9117              var match = false;
9118  
9119              elem = elem[dir];
9120  
9121              while ( elem ) {
9122                  if ( elem[ expando ] === doneName ) {
9123                      match = checkSet[elem.sizset];
9124                      break;
9125                  }
9126  
9127                  if ( elem.nodeType === 1 && !isXML ){
9128                      elem[ expando ] = doneName;
9129                      elem.sizset = i;
9130                  }
9131  
9132                  if ( elem.nodeName.toLowerCase() === cur ) {
9133                      match = elem;
9134                      break;
9135                  }
9136  
9137                  elem = elem[dir];
9138              }
9139  
9140              checkSet[i] = match;
9141          }
9142      }
9143  }
9144  
9145  function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
9146      for ( var i = 0, l = checkSet.length; i < l; i++ ) {
9147          var elem = checkSet[i];
9148  
9149          if ( elem ) {
9150              var match = false;
9151  
9152              elem = elem[dir];
9153  
9154              while ( elem ) {
9155                  if ( elem[ expando ] === doneName ) {
9156                      match = checkSet[elem.sizset];
9157                      break;
9158                  }
9159  
9160                  if ( elem.nodeType === 1 ) {
9161                      if ( !isXML ) {
9162                          elem[ expando ] = doneName;
9163                          elem.sizset = i;
9164                      }
9165  
9166                      if ( typeof cur !== "string" ) {
9167                          if ( elem === cur ) {
9168                              match = true;
9169                              break;
9170                          }
9171  
9172                      } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
9173                          match = elem;
9174                          break;
9175                      }
9176                  }
9177  
9178                  elem = elem[dir];
9179              }
9180  
9181              checkSet[i] = match;
9182          }
9183      }
9184  }
9185  
9186  if ( document.documentElement.contains ) {
9187      Sizzle.contains = function( a, b ) {
9188          return a !== b && (a.contains ? a.contains(b) : true);
9189      };
9190  
9191  } else if ( document.documentElement.compareDocumentPosition ) {
9192      Sizzle.contains = function( a, b ) {
9193          return !!(a.compareDocumentPosition(b) & 16);
9194      };
9195  
9196  } else {
9197      Sizzle.contains = function() {
9198          return false;
9199      };
9200  }
9201  
9202  Sizzle.isXML = function( elem ) {
9203      // documentElement is verified for cases where it doesn't yet exist
9204      // (such as loading iframes in IE - #4833)
9205      var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
9206  
9207      return documentElement ? documentElement.nodeName !== "HTML" : false;
9208  };
9209  
9210  var posProcess = function( selector, context, seed ) {
9211      var match,
9212          tmpSet = [],
9213          later = "",
9214          root = context.nodeType ? [context] : context;
9215  
9216      // Position selectors must be done after the filter
9217      // And so must :not(positional) so we move all PSEUDOs to the end
9218      while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
9219          later += match[0];
9220          selector = selector.replace( Expr.match.PSEUDO, "" );
9221      }
9222  
9223      selector = Expr.relative[selector] ? selector + "*" : selector;
9224  
9225      for ( var i = 0, l = root.length; i < l; i++ ) {
9226          Sizzle( selector, root[i], tmpSet, seed );
9227      }
9228  
9229      return Sizzle.filter( later, tmpSet );
9230  };
9231  
9232  // EXPOSE
9233  
9234  window.tinymce.dom.Sizzle = Sizzle;
9235  
9236  })();
9237  
9238  (function(tinymce) {
9239      tinymce.dom.Element = function(id, settings) {
9240          var t = this, dom, el;
9241  
9242          t.settings = settings = settings || {};
9243          t.id = id;
9244          t.dom = dom = settings.dom || tinymce.DOM;
9245  
9246          // Only IE leaks DOM references, this is a lot faster
9247          if (!tinymce.isIE)
9248              el = dom.get(t.id);
9249  
9250          tinymce.each(
9251                  ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
9252                  'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
9253                  'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
9254                  'isHidden,setHTML,get').split(/,/), function(k) {
9255                      t[k] = function() {
9256                          var a = [id], i;
9257  
9258                          for (i = 0; i < arguments.length; i++)
9259                              a.push(arguments[i]);
9260  
9261                          a = dom[k].apply(dom, a);
9262                          t.update(k);
9263  
9264                          return a;
9265                      };
9266              }
9267          );
9268  
9269          tinymce.extend(t, {
9270              on : function(n, f, s) {
9271                  return tinymce.dom.Event.add(t.id, n, f, s);
9272              },
9273  
9274              getXY : function() {
9275                  return {
9276                      x : parseInt(t.getStyle('left')),
9277                      y : parseInt(t.getStyle('top'))
9278                  };
9279              },
9280  
9281              getSize : function() {
9282                  var n = dom.get(t.id);
9283  
9284                  return {
9285                      w : parseInt(t.getStyle('width') || n.clientWidth),
9286                      h : parseInt(t.getStyle('height') || n.clientHeight)
9287                  };
9288              },
9289  
9290              moveTo : function(x, y) {
9291                  t.setStyles({left : x, top : y});
9292              },
9293  
9294              moveBy : function(x, y) {
9295                  var p = t.getXY();
9296  
9297                  t.moveTo(p.x + x, p.y + y);
9298              },
9299  
9300              resizeTo : function(w, h) {
9301                  t.setStyles({width : w, height : h});
9302              },
9303  
9304              resizeBy : function(w, h) {
9305                  var s = t.getSize();
9306  
9307                  t.resizeTo(s.w + w, s.h + h);
9308              },
9309  
9310              update : function(k) {
9311                  var b;
9312  
9313                  if (tinymce.isIE6 && settings.blocker) {
9314                      k = k || '';
9315  
9316                      // Ignore getters
9317                      if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
9318                          return;
9319  
9320                      // Remove blocker on remove
9321                      if (k == 'remove') {
9322                          dom.remove(t.blocker);
9323                          return;
9324                      }
9325  
9326                      if (!t.blocker) {
9327                          t.blocker = dom.uniqueId();
9328                          b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
9329                          dom.setStyle(b, 'opacity', 0);
9330                      } else
9331                          b = dom.get(t.blocker);
9332  
9333                      dom.setStyles(b, {
9334                          left : t.getStyle('left', 1),
9335                          top : t.getStyle('top', 1),
9336                          width : t.getStyle('width', 1),
9337                          height : t.getStyle('height', 1),
9338                          display : t.getStyle('display', 1),
9339                          zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
9340                      });
9341                  }
9342              }
9343          });
9344      };
9345  })(tinymce);
9346  (function(tinymce) {
9347  	function trimNl(s) {
9348          return s.replace(/[\n\r]+/g, '');
9349      };
9350  
9351      // Shorten names
9352      var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
9353  
9354      tinymce.create('tinymce.dom.Selection', {
9355          Selection : function(dom, win, serializer, editor) {
9356              var t = this;
9357  
9358              t.dom = dom;
9359              t.win = win;
9360              t.serializer = serializer;
9361              t.editor = editor;
9362  
9363              // Add events
9364              each([
9365                  'onBeforeSetContent',
9366  
9367                  'onBeforeGetContent',
9368  
9369                  'onSetContent',
9370  
9371                  'onGetContent'
9372              ], function(e) {
9373                  t[e] = new tinymce.util.Dispatcher(t);
9374              });
9375  
9376              // No W3C Range support
9377              if (!t.win.getSelection)
9378                  t.tridentSel = new tinymce.dom.TridentSelection(t);
9379  
9380              if (tinymce.isIE && ! tinymce.isIE11 && dom.boxModel)
9381                  this._fixIESelection();
9382  
9383              // Prevent leaks
9384              tinymce.addUnload(t.destroy, t);
9385          },
9386  
9387          setCursorLocation: function(node, offset) {
9388              var t = this; var r = t.dom.createRng();
9389              r.setStart(node, offset);
9390              r.setEnd(node, offset);
9391              t.setRng(r);
9392              t.collapse(false);
9393          },
9394          getContent : function(s) {
9395              var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
9396  
9397              s = s || {};
9398              wb = wa = '';
9399              s.get = true;
9400              s.format = s.format || 'html';
9401              s.forced_root_block = '';
9402              t.onBeforeGetContent.dispatch(t, s);
9403  
9404              if (s.format == 'text')
9405                  return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
9406  
9407              if (r.cloneContents) {
9408                  n = r.cloneContents();
9409  
9410                  if (n)
9411                      e.appendChild(n);
9412              } else if (is(r.item) || is(r.htmlText)) {
9413                  // IE will produce invalid markup if elements are present that
9414                  // it doesn't understand like custom elements or HTML5 elements.
9415                  // Adding a BR in front of the contents and then remoiving it seems to fix it though.
9416                  e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
9417                  e.removeChild(e.firstChild);
9418              } else
9419                  e.innerHTML = r.toString();
9420  
9421              // Keep whitespace before and after
9422              if (/^\s/.test(e.innerHTML))
9423                  wb = ' ';
9424  
9425              if (/\s+$/.test(e.innerHTML))
9426                  wa = ' ';
9427  
9428              s.getInner = true;
9429  
9430              s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
9431              t.onGetContent.dispatch(t, s);
9432  
9433              return s.content;
9434          },
9435  
9436          setContent : function(content, args) {
9437              var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
9438  
9439              args = args || {format : 'html'};
9440              args.set = true;
9441              content = args.content = content;
9442  
9443              // Dispatch before set content event
9444              if (!args.no_events)
9445                  self.onBeforeSetContent.dispatch(self, args);
9446  
9447              content = args.content;
9448  
9449              if (rng.insertNode) {
9450                  // Make caret marker since insertNode places the caret in the beginning of text after insert
9451                  content += '<span id="__caret">_</span>';
9452  
9453                  // Delete and insert new node
9454                  if (rng.startContainer == doc && rng.endContainer == doc) {
9455                      // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
9456                      doc.body.innerHTML = content;
9457                  } else {
9458                      rng.deleteContents();
9459  
9460                      if (doc.body.childNodes.length === 0) {
9461                          doc.body.innerHTML = content;
9462                      } else {
9463                          // createContextualFragment doesn't exists in IE 9 DOMRanges
9464                          if (rng.createContextualFragment) {
9465                              rng.insertNode(rng.createContextualFragment(content));
9466                          } else {
9467                              // Fake createContextualFragment call in IE 9
9468                              frag = doc.createDocumentFragment();
9469                              temp = doc.createElement('div');
9470  
9471                              frag.appendChild(temp);
9472                              temp.outerHTML = content;
9473  
9474                              rng.insertNode(frag);
9475                          }
9476                      }
9477                  }
9478  
9479                  // Move to caret marker
9480                  caretNode = self.dom.get('__caret');
9481  
9482                  // Make sure we wrap it compleatly, Opera fails with a simple select call
9483                  rng = doc.createRange();
9484                  rng.setStartBefore(caretNode);
9485                  rng.setEndBefore(caretNode);
9486                  self.setRng(rng);
9487  
9488                  // Remove the caret position
9489                  self.dom.remove('__caret');
9490  
9491                  try {
9492                      self.setRng(rng);
9493                  } catch (ex) {
9494                      // Might fail on Opera for some odd reason
9495                  }
9496              } else {
9497                  if (rng.item) {
9498                      // Delete content and get caret text selection
9499                      doc.execCommand('Delete', false, null);
9500                      rng = self.getRng();
9501                  }
9502  
9503                  // Explorer removes spaces from the beginning of pasted contents
9504                  if (/^\s+/.test(content)) {
9505                      rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
9506                      self.dom.remove('__mce_tmp');
9507                  } else
9508                      rng.pasteHTML(content);
9509              }
9510  
9511              // Dispatch set content event
9512              if (!args.no_events)
9513                  self.onSetContent.dispatch(self, args);
9514          },
9515  
9516          getStart : function() {
9517              var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
9518  
9519              if (rng.duplicate || rng.item) {
9520                  // Control selection, return first item
9521                  if (rng.item)
9522                      return rng.item(0);
9523  
9524                  // Get start element
9525                  checkRng = rng.duplicate();
9526                  checkRng.collapse(1);
9527                  startElement = checkRng.parentElement();
9528                  if (startElement.ownerDocument !== self.dom.doc) {
9529                      startElement = self.dom.getRoot();
9530                  }
9531  
9532                  // Check if range parent is inside the start element, then return the inner parent element
9533                  // This will fix issues when a single element is selected, IE would otherwise return the wrong start element
9534                  parentElement = node = rng.parentElement();
9535                  while (node = node.parentNode) {
9536                      if (node == startElement) {
9537                          startElement = parentElement;
9538                          break;
9539                      }
9540                  }
9541  
9542                  return startElement;
9543              } else {
9544                  startElement = rng.startContainer;
9545  
9546                  if (startElement.nodeType == 1 && startElement.hasChildNodes())
9547                      startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
9548  
9549                  if (startElement && startElement.nodeType == 3)
9550                      return startElement.parentNode;
9551  
9552                  return startElement;
9553              }
9554          },
9555  
9556          getEnd : function() {
9557              var self = this, rng = self.getRng(), endElement, endOffset;
9558  
9559              if (rng.duplicate || rng.item) {
9560                  if (rng.item)
9561                      return rng.item(0);
9562  
9563                  rng = rng.duplicate();
9564                  rng.collapse(0);
9565                  endElement = rng.parentElement();
9566                  if (endElement.ownerDocument !== self.dom.doc) {
9567                      endElement = self.dom.getRoot();
9568                  }
9569  
9570                  if (endElement && endElement.nodeName == 'BODY')
9571                      return endElement.lastChild || endElement;
9572  
9573                  return endElement;
9574              } else {
9575                  endElement = rng.endContainer;
9576                  endOffset = rng.endOffset;
9577  
9578                  if (endElement.nodeType == 1 && endElement.hasChildNodes())
9579                      endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
9580  
9581                  if (endElement && endElement.nodeType == 3)
9582                      return endElement.parentNode;
9583  
9584                  return endElement;
9585              }
9586          },
9587  
9588          getBookmark : function(type, normalized) {
9589              var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
9590  
9591  			function findIndex(name, element) {
9592                  var index = 0;
9593  
9594                  each(dom.select(name), function(node, i) {
9595                      if (node == element)
9596                          index = i;
9597                  });
9598  
9599                  return index;
9600              };
9601  
9602  			function normalizeTableCellSelection(rng) {
9603  				function moveEndPoint(start) {
9604                      var container, offset, childNodes, prefix = start ? 'start' : 'end';
9605  
9606                      container = rng[prefix + 'Container'];
9607                      offset = rng[prefix + 'Offset'];
9608  
9609                      if (container.nodeType == 1 && container.nodeName == "TR") {
9610                          childNodes = container.childNodes;
9611                          container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
9612                          if (container) {
9613                              offset = start ? 0 : container.childNodes.length;
9614                              rng['set' + (start ? 'Start' : 'End')](container, offset);
9615                          }
9616                      }
9617                  };
9618  
9619                  moveEndPoint(true);
9620                  moveEndPoint();
9621  
9622                  return rng;
9623              };
9624  
9625  			function getLocation() {
9626                  var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
9627  
9628  				function getPoint(rng, start) {
9629                      var container = rng[start ? 'startContainer' : 'endContainer'],
9630                          offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
9631  
9632                      if (container.nodeType == 3) {
9633                          if (normalized) {
9634                              for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
9635                                  offset += node.nodeValue.length;
9636                          }
9637  
9638                          point.push(offset);
9639                      } else {
9640                          childNodes = container.childNodes;
9641  
9642                          if (offset >= childNodes.length && childNodes.length) {
9643                              after = 1;
9644                              offset = Math.max(0, childNodes.length - 1);
9645                          }
9646  
9647                          point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
9648                      }
9649  
9650                      for (; container && container != root; container = container.parentNode)
9651                          point.push(t.dom.nodeIndex(container, normalized));
9652  
9653                      return point;
9654                  };
9655  
9656                  bookmark.start = getPoint(rng, true);
9657  
9658                  if (!t.isCollapsed())
9659                      bookmark.end = getPoint(rng);
9660  
9661                  return bookmark;
9662              };
9663  
9664              if (type == 2) {
9665                  if (t.tridentSel)
9666                      return t.tridentSel.getBookmark(type);
9667  
9668                  return getLocation();
9669              }
9670  
9671              // Handle simple range
9672              if (type) {
9673                  rng = t.getRng();
9674  
9675                  if (rng.setStart) {
9676                      rng = {
9677                          startContainer: rng.startContainer,
9678                          startOffset: rng.startOffset,
9679                          endContainer: rng.endContainer,
9680                          endOffset: rng.endOffset
9681                      };
9682                  }
9683  
9684                  return {rng : rng};
9685              }
9686  
9687              rng = t.getRng();
9688              id = dom.uniqueId();
9689              collapsed = tinyMCE.activeEditor.selection.isCollapsed();
9690              styles = 'overflow:hidden;line-height:0px';
9691  
9692              // Explorer method
9693              if (rng.duplicate || rng.item) {
9694                  // Text selection
9695                  if (!rng.item) {
9696                      rng2 = rng.duplicate();
9697  
9698                      try {
9699                          // Insert start marker
9700                          rng.collapse();
9701                          rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
9702  
9703                          // Insert end marker
9704                          if (!collapsed) {
9705                              rng2.collapse(false);
9706  
9707                              // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
9708                              rng.moveToElementText(rng2.parentElement());
9709                              if (rng.compareEndPoints('StartToEnd', rng2) === 0)
9710                                  rng2.move('character', -1);
9711  
9712                              rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
9713                          }
9714                      } catch (ex) {
9715                          // IE might throw unspecified error so lets ignore it
9716                          return null;
9717                      }
9718                  } else {
9719                      // Control selection
9720                      element = rng.item(0);
9721                      name = element.nodeName;
9722  
9723                      return {name : name, index : findIndex(name, element)};
9724                  }
9725              } else {
9726                  element = t.getNode();
9727                  name = element.nodeName;
9728                  if (name == 'IMG')
9729                      return {name : name, index : findIndex(name, element)};
9730  
9731                  // W3C method
9732                  rng2 = normalizeTableCellSelection(rng.cloneRange());
9733  
9734                  // Insert end marker
9735                  if (!collapsed) {
9736                      rng2.collapse(false);
9737                      rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
9738                  }
9739  
9740                  rng = normalizeTableCellSelection(rng);
9741                  rng.collapse(true);
9742                  rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
9743              }
9744  
9745              t.moveToBookmark({id : id, keep : 1});
9746  
9747              return {id : id};
9748          },
9749  
9750          moveToBookmark : function(bookmark) {
9751              var t = this, dom = t.dom, marker1, marker2, rng, rng2, root, startContainer, endContainer, startOffset, endOffset;
9752  
9753  			function setEndPoint(start) {
9754                  var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
9755  
9756                  if (point) {
9757                      offset = point[0];
9758  
9759                      // Find container node
9760                      for (node = root, i = point.length - 1; i >= 1; i--) {
9761                          children = node.childNodes;
9762  
9763                          if (point[i] > children.length - 1)
9764                              return;
9765  
9766                          node = children[point[i]];
9767                      }
9768  
9769                      // Move text offset to best suitable location
9770                      if (node.nodeType === 3)
9771                          offset = Math.min(point[0], node.nodeValue.length);
9772  
9773                      // Move element offset to best suitable location
9774                      if (node.nodeType === 1)
9775                          offset = Math.min(point[0], node.childNodes.length);
9776  
9777                      // Set offset within container node
9778                      if (start)
9779                          rng.setStart(node, offset);
9780                      else
9781                          rng.setEnd(node, offset);
9782                  }
9783  
9784                  return true;
9785              };
9786  
9787  			function restoreEndPoint(suffix) {
9788                  var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
9789  
9790                  if (marker) {
9791                      node = marker.parentNode;
9792  
9793                      if (suffix == 'start') {
9794                          if (!keep) {
9795                              idx = dom.nodeIndex(marker);
9796                          } else {
9797                              node = marker.firstChild;
9798                              idx = 1;
9799                          }
9800  
9801                          startContainer = endContainer = node;
9802                          startOffset = endOffset = idx;
9803                      } else {
9804                          if (!keep) {
9805                              idx = dom.nodeIndex(marker);
9806                          } else {
9807                              node = marker.firstChild;
9808                              idx = 1;
9809                          }
9810  
9811                          endContainer = node;
9812                          endOffset = idx;
9813                      }
9814  
9815                      if (!keep) {
9816                          prev = marker.previousSibling;
9817                          next = marker.nextSibling;
9818  
9819                          // Remove all marker text nodes
9820                          each(tinymce.grep(marker.childNodes), function(node) {
9821                              if (node.nodeType == 3)
9822                                  node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
9823                          });
9824  
9825                          // Remove marker but keep children if for example contents where inserted into the marker
9826                          // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
9827                          while (marker = dom.get(bookmark.id + '_' + suffix))
9828                              dom.remove(marker, 1);
9829  
9830                          // If siblings are text nodes then merge them unless it's Opera since it some how removes the node
9831                          // and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
9832                          if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
9833                              idx = prev.nodeValue.length;
9834                              prev.appendData(next.nodeValue);
9835                              dom.remove(next);
9836  
9837                              if (suffix == 'start') {
9838                                  startContainer = endContainer = prev;
9839                                  startOffset = endOffset = idx;
9840                              } else {
9841                                  endContainer = prev;
9842                                  endOffset = idx;
9843                              }
9844                          }
9845                      }
9846                  }
9847              };
9848  
9849  			function addBogus(node) {
9850                  // Adds a bogus BR element for empty block elements
9851                  if (dom.isBlock(node) && !node.innerHTML && !isIE)
9852                      node.innerHTML = '<br data-mce-bogus="1" />';
9853  
9854                  return node;
9855              };
9856  
9857              if (bookmark) {
9858                  if (bookmark.start) {
9859                      rng = dom.createRng();
9860                      root = dom.getRoot();
9861  
9862                      if (t.tridentSel)
9863                          return t.tridentSel.moveToBookmark(bookmark);
9864  
9865                      if (setEndPoint(true) && setEndPoint()) {
9866                          t.setRng(rng);
9867                      }
9868                  } else if (bookmark.id) {
9869                      // Restore start/end points
9870                      restoreEndPoint('start');
9871                      restoreEndPoint('end');
9872  
9873                      if (startContainer) {
9874                          rng = dom.createRng();
9875                          rng.setStart(addBogus(startContainer), startOffset);
9876                          rng.setEnd(addBogus(endContainer), endOffset);
9877                          t.setRng(rng);
9878                      }
9879                  } else if (bookmark.name) {
9880                      t.select(dom.select(bookmark.name)[bookmark.index]);
9881                  } else if (bookmark.rng) {
9882                      rng = bookmark.rng;
9883  
9884                      if (rng.startContainer) {
9885                          rng2 = t.dom.createRng();
9886  
9887                          try {
9888                              rng2.setStart(rng.startContainer, rng.startOffset);
9889                              rng2.setEnd(rng.endContainer, rng.endOffset);
9890                          } catch (e) {
9891                              // Might fail with index error
9892                          }
9893  
9894                          rng = rng2;
9895                      }
9896  
9897                      t.setRng(rng);
9898                  }
9899              }
9900          },
9901  
9902          select : function(node, content) {
9903              var t = this, dom = t.dom, rng = dom.createRng(), idx;
9904  
9905  			function setPoint(node, start) {
9906                  var walker = new TreeWalker(node, node);
9907  
9908                  do {
9909                      // Text node
9910                      if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
9911                          if (start)
9912                              rng.setStart(node, 0);
9913                          else
9914                              rng.setEnd(node, node.nodeValue.length);
9915  
9916                          return;
9917                      }
9918  
9919                      // BR element
9920                      if (node.nodeName == 'BR') {
9921                          if (start)
9922                              rng.setStartBefore(node);
9923                          else
9924                              rng.setEndBefore(node);
9925  
9926                          return;
9927                      }
9928                  } while (node = (start ? walker.next() : walker.prev()));
9929              };
9930  
9931              if (node) {
9932                  idx = dom.nodeIndex(node);
9933                  rng.setStart(node.parentNode, idx);
9934                  rng.setEnd(node.parentNode, idx + 1);
9935  
9936                  // Find first/last text node or BR element
9937                  if (content) {
9938                      setPoint(node, 1);
9939                      setPoint(node);
9940                  }
9941  
9942                  t.setRng(rng);
9943              }
9944  
9945              return node;
9946          },
9947  
9948          isCollapsed : function() {
9949              var t = this, r = t.getRng(), s = t.getSel();
9950  
9951              if (!r || r.item)
9952                  return false;
9953  
9954              if (r.compareEndPoints)
9955                  return r.compareEndPoints('StartToEnd', r) === 0;
9956  
9957              return !s || r.collapsed;
9958          },
9959  
9960          collapse : function(to_start) {
9961              var self = this, rng = self.getRng(), node;
9962  
9963              // Control range on IE
9964              if (rng.item) {
9965                  node = rng.item(0);
9966                  rng = self.win.document.body.createTextRange();
9967                  rng.moveToElementText(node);
9968              }
9969  
9970              rng.collapse(!!to_start);
9971              self.setRng(rng);
9972          },
9973  
9974          getSel : function() {
9975              var t = this, w = this.win;
9976  
9977              return w.getSelection ? w.getSelection() : w.document.selection;
9978          },
9979  
9980          getRng : function(w3c) {
9981              var self = this, selection, rng, elm, doc = self.win.document;
9982  
9983              // Workaround for IE 11 not being able to select images properly see #6613 see quirk fix
9984              if (self.fakeRng) {
9985                  return self.fakeRng;
9986              }
9987  
9988              // Found tridentSel object then we need to use that one
9989              if (w3c && self.tridentSel) {
9990                  return self.tridentSel.getRangeAt(0);
9991              }
9992  
9993              try {
9994                  if (selection = self.getSel()) {
9995                      rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
9996                  }
9997              } catch (ex) {
9998                  // IE throws unspecified error here if TinyMCE is placed in a frame/iframe
9999              }
10000  
10001              // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
10002              if (tinymce.isIE && ! tinymce.isIE11 && rng && rng.setStart && doc.selection.createRange().item) {
10003                  elm = doc.selection.createRange().item(0);
10004                  rng = doc.createRange();
10005                  rng.setStartBefore(elm);
10006                  rng.setEndAfter(elm);
10007              }
10008  
10009              // No range found then create an empty one
10010              // This can occur when the editor is placed in a hidden container element on Gecko
10011              // Or on IE when there was an exception
10012              if (!rng) {
10013                  rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
10014              }
10015  
10016              // If range is at start of document then move it to start of body
10017              if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
10018                  elm = self.dom.getRoot();
10019                  rng.setStart(elm, 0);
10020                  rng.setEnd(elm, 0);
10021              }
10022  
10023              if (self.selectedRange && self.explicitRange) {
10024                  if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
10025                      // Safari, Opera and Chrome only ever select text which causes the range to change.
10026                      // This lets us use the originally set range if the selection hasn't been changed by the user.
10027                      rng = self.explicitRange;
10028                  } else {
10029                      self.selectedRange = null;
10030                      self.explicitRange = null;
10031                  }
10032              }
10033  
10034              return rng;
10035          },
10036  
10037          setRng : function(r, forward) {
10038              var s, t = this;
10039  
10040              if (!t.tridentSel) {
10041                  s = t.getSel();
10042  
10043                  if (s) {
10044                      t.explicitRange = r;
10045  
10046                      try {
10047                          s.removeAllRanges();
10048                      } catch (ex) {
10049                          // IE9 might throw errors here don't know why
10050                      }
10051  
10052                      s.addRange(r);
10053  
10054                      // Forward is set to false and we have an extend function
10055                      if (forward === false && s.extend) {
10056                          s.collapse(r.endContainer, r.endOffset);
10057                          s.extend(r.startContainer, r.startOffset);
10058                      }
10059  
10060                      // adding range isn't always successful so we need to check range count otherwise an exception can occur
10061                      t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
10062                  }
10063              } else {
10064                  // Is W3C Range
10065                  if (r.cloneRange) {
10066                      try {
10067                          t.tridentSel.addRange(r);
10068                          return;
10069                      } catch (ex) {
10070                          //IE9 throws an error here if called before selection is placed in the editor
10071                      }
10072                  }
10073  
10074                  // Is IE specific range
10075                  try {
10076                      r.select();
10077                  } catch (ex) {
10078                      // Needed for some odd IE bug #1843306
10079                  }
10080              }
10081          },
10082  
10083          setNode : function(n) {
10084              var t = this;
10085  
10086              t.setContent(t.dom.getOuterHTML(n));
10087  
10088              return n;
10089          },
10090  
10091          getNode : function() {
10092              var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
10093  
10094  			function skipEmptyTextNodes(n, forwards) {
10095                  var orig = n;
10096                  while (n && n.nodeType === 3 && n.length === 0) {
10097                      n = forwards ? n.nextSibling : n.previousSibling;
10098                  }
10099                  return n || orig;
10100              };
10101  
10102              // Range maybe lost after the editor is made visible again
10103              if (!rng)
10104                  return t.dom.getRoot();
10105  
10106              if (rng.setStart) {
10107                  elm = rng.commonAncestorContainer;
10108  
10109                  // Handle selection a image or other control like element such as anchors
10110                  if (!rng.collapsed) {
10111                      if (rng.startContainer == rng.endContainer) {
10112                          if (rng.endOffset - rng.startOffset < 2) {
10113                              if (rng.startContainer.hasChildNodes())
10114                                  elm = rng.startContainer.childNodes[rng.startOffset];
10115                          }
10116                      }
10117  
10118                      // If the anchor node is a element instead of a text node then return this element
10119                      //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
10120                      //    return sel.anchorNode.childNodes[sel.anchorOffset];
10121  
10122                      // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
10123                      // This happens when you double click an underlined word in FireFox.
10124                      if (start.nodeType === 3 && end.nodeType === 3) {
10125                          if (start.length === rng.startOffset) {
10126                              start = skipEmptyTextNodes(start.nextSibling, true);
10127                          } else {
10128                              start = start.parentNode;
10129                          }
10130                          if (rng.endOffset === 0) {
10131                              end = skipEmptyTextNodes(end.previousSibling, false);
10132                          } else {
10133                              end = end.parentNode;
10134                          }
10135  
10136                          if (start && start === end)
10137                              return start;
10138                      }
10139                  }
10140  
10141                  if (elm && elm.nodeType == 3)
10142                      return elm.parentNode;
10143  
10144                  return elm;
10145              }
10146  
10147              return rng.item ? rng.item(0) : rng.parentElement();
10148          },
10149  
10150          getSelectedBlocks : function(st, en) {
10151              var t = this, dom = t.dom, sb, eb, n, bl = [];
10152  
10153              sb = dom.getParent(st || t.getStart(), dom.isBlock);
10154              eb = dom.getParent(en || t.getEnd(), dom.isBlock);
10155  
10156              if (sb)
10157                  bl.push(sb);
10158  
10159              if (sb && eb && sb != eb) {
10160                  n = sb;
10161  
10162                  var walker = new TreeWalker(sb, dom.getRoot());
10163                  while ((n = walker.next()) && n != eb) {
10164                      if (dom.isBlock(n))
10165                          bl.push(n);
10166                  }
10167              }
10168  
10169              if (eb && sb != eb)
10170                  bl.push(eb);
10171  
10172              return bl;
10173          },
10174  
10175          isForward: function(){
10176              var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
10177  
10178              // No support for selection direction then always return true
10179              if (!sel || sel.anchorNode == null || sel.focusNode == null) {
10180                  return true;
10181              }
10182  
10183              anchorRange = dom.createRng();
10184              anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
10185              anchorRange.collapse(true);
10186  
10187              focusRange = dom.createRng();
10188              focusRange.setStart(sel.focusNode, sel.focusOffset);
10189              focusRange.collapse(true);
10190  
10191              return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
10192          },
10193  
10194          normalize : function() {
10195              var self = this, rng, normalized, collapsed, node, sibling;
10196  
10197  			function normalizeEndPoint(start) {
10198                  var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
10199  
10200  				function hasBrBeforeAfter(node, left) {
10201                      var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
10202  
10203                      while (node = walker[left ? 'prev' : 'next']()) {
10204                          if (node.nodeName === "BR") {
10205                              return true;
10206                          }
10207                      }
10208                  };
10209  
10210                  // Walks the dom left/right to find a suitable text node to move the endpoint into
10211                  // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
10212  				function findTextNodeRelative(left, startNode) {
10213                      var walker, lastInlineElement;
10214  
10215                      startNode = startNode || container;
10216                      walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
10217  
10218                      // Walk left until we hit a text node we can move to or a block/br/img
10219                      while (node = walker[left ? 'prev' : 'next']()) {
10220                          // Found text node that has a length
10221                          if (node.nodeType === 3 && node.nodeValue.length > 0) {
10222                              container = node;
10223                              offset = left ? node.nodeValue.length : 0;
10224                              normalized = true;
10225                              return;
10226                          }
10227  
10228                          // Break if we find a block or a BR/IMG/INPUT etc
10229                          if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10230                              return;
10231                          }
10232  
10233                          lastInlineElement = node;
10234                      }
10235  
10236                      // Only fetch the last inline element when in caret mode for now
10237                      if (collapsed && lastInlineElement) {
10238                          container = lastInlineElement;
10239                          normalized = true;
10240                          offset = 0;
10241                      }
10242                  };
10243  
10244                  container = rng[(start ? 'start' : 'end') + 'Container'];
10245                  offset = rng[(start ? 'start' : 'end') + 'Offset'];
10246                  nonEmptyElementsMap = dom.schema.getNonEmptyElements();
10247  
10248                  // If the container is a document move it to the body element
10249                  if (container.nodeType === 9) {
10250                      container = dom.getRoot();
10251                      offset = 0;
10252                  }
10253  
10254                  // If the container is body try move it into the closest text node or position
10255                  if (container === body) {
10256                      // If start is before/after a image, table etc
10257                      if (start) {
10258                          node = container.childNodes[offset > 0 ? offset - 1 : 0];
10259                          if (node) {
10260                              nodeName = node.nodeName.toLowerCase();
10261                              if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
10262                                  return;
10263                              }
10264                          }
10265                      }
10266  
10267                      // Resolve the index
10268                      if (container.hasChildNodes()) {
10269                          container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
10270                          offset = 0;
10271  
10272                          // Don't walk into elements that doesn't have any child nodes like a IMG
10273                          if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
10274                              // Walk the DOM to find a text node to place the caret at or a BR
10275                              node = container;
10276                              walker = new TreeWalker(container, body);
10277  
10278                              do {
10279                                  // Found a text node use that position
10280                                  if (node.nodeType === 3 && node.nodeValue.length > 0) {
10281                                      offset = start ? 0 : node.nodeValue.length;
10282                                      container = node;
10283                                      normalized = true;
10284                                      break;
10285                                  }
10286  
10287                                  // Found a BR/IMG element that we can place the caret before
10288                                  if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
10289                                      offset = dom.nodeIndex(node);
10290                                      container = node.parentNode;
10291  
10292                                      // Put caret after image when moving the end point
10293                                      if (node.nodeName ==  "IMG" && !start) {
10294                                          offset++;
10295                                      }
10296  
10297                                      normalized = true;
10298                                      break;
10299                                  }
10300                              } while (node = (start ? walker.next() : walker.prev()));
10301                          }
10302                      }
10303                  }
10304  
10305                  // Lean the caret to the left if possible
10306                  if (collapsed) {
10307                      // So this: <b>x</b><i>|x</i>
10308                      // Becomes: <b>x|</b><i>x</i>
10309                      // Seems that only gecko has issues with this
10310                      if (container.nodeType === 3 && offset === 0) {
10311                          findTextNodeRelative(true);
10312                      }
10313  
10314                      // Lean left into empty inline elements when the caret is before a BR
10315                      // So this: <i><b></b><i>|<br></i>
10316                      // Becomes: <i><b>|</b><i><br></i>
10317                      // Seems that only gecko has issues with this
10318                      if (container.nodeType === 1) {
10319                          node = container.childNodes[offset];
10320                          if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
10321                              findTextNodeRelative(true, container.childNodes[offset]);
10322                          }
10323                      }
10324                  }
10325  
10326                  // Lean the start of the selection right if possible
10327                  // So this: x[<b>x]</b>
10328                  // Becomes: x<b>[x]</b>
10329                  if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
10330                      findTextNodeRelative(false);
10331                  }
10332  
10333                  // Set endpoint if it was normalized
10334                  if (normalized)
10335                      rng['set' + (start ? 'Start' : 'End')](container, offset);
10336              };
10337  
10338              // Normalize only on non IE browsers for now
10339              if (tinymce.isIE)
10340                  return;
10341              
10342              rng = self.getRng();
10343              collapsed = rng.collapsed;
10344  
10345              // Normalize the end points
10346              normalizeEndPoint(true);
10347  
10348              if (!collapsed)
10349                  normalizeEndPoint();
10350  
10351              // Set the selection if it was normalized
10352              if (normalized) {
10353                  // If it was collapsed then make sure it still is
10354                  if (collapsed) {
10355                      rng.collapse(true);
10356                  }
10357  
10358                  //console.log(self.dom.dumpRng(rng));
10359                  self.setRng(rng, self.isForward());
10360              }
10361          },
10362  
10363          selectorChanged: function(selector, callback) {
10364              var self = this, currentSelectors;
10365  
10366              if (!self.selectorChangedData) {
10367                  self.selectorChangedData = {};
10368                  currentSelectors = {};
10369  
10370                  self.editor.onNodeChange.addToTop(function(ed, cm, node) {
10371                      var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
10372  
10373                      // Check for new matching selectors
10374                      each(self.selectorChangedData, function(callbacks, selector) {
10375                          each(parents, function(node) {
10376                              if (dom.is(node, selector)) {
10377                                  if (!currentSelectors[selector]) {
10378                                      // Execute callbacks
10379                                      each(callbacks, function(callback) {
10380                                          callback(true, {node: node, selector: selector, parents: parents});
10381                                      });
10382  
10383                                      currentSelectors[selector] = callbacks;
10384                                  }
10385  
10386                                  matchedSelectors[selector] = callbacks;
10387                                  return false;
10388                              }
10389                          });
10390                      });
10391  
10392                      // Check if current selectors still match
10393                      each(currentSelectors, function(callbacks, selector) {
10394                          if (!matchedSelectors[selector]) {
10395                              delete currentSelectors[selector];
10396  
10397                              each(callbacks, function(callback) {
10398                                  callback(false, {node: node, selector: selector, parents: parents});
10399                              });
10400                          }
10401                      });
10402                  });
10403              }
10404  
10405              // Add selector listeners
10406              if (!self.selectorChangedData[selector]) {
10407                  self.selectorChangedData[selector] = [];
10408              }
10409  
10410              self.selectorChangedData[selector].push(callback);
10411  
10412              return self;
10413          },
10414  
10415          scrollIntoView: function(elm) {
10416              var y, viewPort, self = this, dom = self.dom;
10417  
10418              viewPort = dom.getViewPort(self.editor.getWin());
10419              y = dom.getPos(elm).y;
10420              if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
10421                  self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);
10422              }
10423          },
10424  
10425          destroy : function(manual) {
10426              var self = this;
10427  
10428              self.win = null;
10429  
10430              // Manual destroy then remove unload handler
10431              if (!manual)
10432                  tinymce.removeUnload(self.destroy);
10433          },
10434  
10435          // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
10436          _fixIESelection : function() {
10437              var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
10438  
10439              // Return range from point or null if it failed
10440  			function rngFromPoint(x, y) {
10441                  var rng = body.createTextRange();
10442  
10443                  try {
10444                      rng.moveToPoint(x, y);
10445                  } catch (ex) {
10446                      // IE sometimes throws and exception, so lets just ignore it
10447                      rng = null;
10448                  }
10449  
10450                  return rng;
10451              };
10452  
10453              // Fires while the selection is changing
10454  			function selectionChange(e) {
10455                  var pointRng;
10456  
10457                  // Check if the button is down or not
10458                  if (e.button) {
10459                      // Create range from mouse position
10460                      pointRng = rngFromPoint(e.x, e.y);
10461  
10462                      if (pointRng) {
10463                          // Check if pointRange is before/after selection then change the endPoint
10464                          if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
10465                              pointRng.setEndPoint('StartToStart', startRng);
10466                          else
10467                              pointRng.setEndPoint('EndToEnd', startRng);
10468  
10469                          pointRng.select();
10470                      }
10471                  } else
10472                      endSelection();
10473              }
10474  
10475              // Removes listeners
10476  			function endSelection() {
10477                  var rng = doc.selection.createRange();
10478  
10479                  // If the range is collapsed then use the last start range
10480                  if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
10481                      startRng.select();
10482  
10483                  dom.unbind(doc, 'mouseup', endSelection);
10484                  dom.unbind(doc, 'mousemove', selectionChange);
10485                  startRng = started = 0;
10486              };
10487  
10488              // Make HTML element unselectable since we are going to handle selection by hand
10489              doc.documentElement.unselectable = true;
10490              
10491              // Detect when user selects outside BODY
10492              dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
10493                  if (e.target.nodeName === 'HTML') {
10494                      if (started)
10495                          endSelection();
10496  
10497                      // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
10498                      htmlElm = doc.documentElement;
10499                      if (htmlElm.scrollHeight > htmlElm.clientHeight)
10500                          return;
10501  
10502                      started = 1;
10503                      // Setup start position
10504                      startRng = rngFromPoint(e.x, e.y);
10505                      if (startRng) {
10506                          // Listen for selection change events
10507                          dom.bind(doc, 'mouseup', endSelection);
10508                          dom.bind(doc, 'mousemove', selectionChange);
10509  
10510                          dom.win.focus();
10511                          startRng.select();
10512                      }
10513                  }
10514              });
10515          }
10516      });
10517  })(tinymce);
10518  (function(tinymce) {
10519      tinymce.dom.Serializer = function(settings, dom, schema) {
10520          var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
10521  
10522          // Support the old apply_source_formatting option
10523          if (!settings.apply_source_formatting)
10524              settings.indent = false;
10525  
10526          // Default DOM and Schema if they are undefined
10527          dom = dom || tinymce.DOM;
10528          schema = schema || new tinymce.html.Schema(settings);
10529          settings.entity_encoding = settings.entity_encoding || 'named';
10530          settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
10531  
10532          onPreProcess = new tinymce.util.Dispatcher(self);
10533  
10534          onPostProcess = new tinymce.util.Dispatcher(self);
10535  
10536          htmlParser = new tinymce.html.DomParser(settings, schema);
10537  
10538          // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
10539          htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
10540              var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
10541  
10542              while (i--) {
10543                  node = nodes[i];
10544  
10545                  value = node.attributes.map[internalName];
10546                  if (value !== undef) {
10547                      // Set external name to internal value and remove internal
10548                      node.attr(name, value.length > 0 ? value : null);
10549                      node.attr(internalName, null);
10550                  } else {
10551                      // No internal attribute found then convert the value we have in the DOM
10552                      value = node.attributes.map[name];
10553  
10554                      if (name === "style")
10555                          value = dom.serializeStyle(dom.parseStyle(value), node.name);
10556                      else if (urlConverter)
10557                          value = urlConverter.call(urlConverterScope, value, name, node.name);
10558  
10559                      node.attr(name, value.length > 0 ? value : null);
10560                  }
10561              }
10562          });
10563  
10564          // Remove internal classes mceItem<..> or mceSelected
10565          htmlParser.addAttributeFilter('class', function(nodes, name) {
10566              var i = nodes.length, node, value;
10567  
10568              while (i--) {
10569                  node = nodes[i];
10570                  value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
10571                  node.attr('class', value.length > 0 ? value : null);
10572              }
10573          });
10574  
10575          // Remove bookmark elements
10576          htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
10577              var i = nodes.length, node;
10578  
10579              while (i--) {
10580                  node = nodes[i];
10581  
10582                  if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
10583                      node.remove();
10584              }
10585          });
10586  
10587          // Remove expando attributes
10588          htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
10589              var i = nodes.length;
10590  
10591              while (i--) {
10592                  nodes[i].attr(name, null);
10593              }
10594          });
10595  
10596          htmlParser.addNodeFilter('noscript', function(nodes) {
10597              var i = nodes.length, node;
10598  
10599              while (i--) {
10600                  node = nodes[i].firstChild;
10601  
10602                  if (node) {
10603                      node.value = tinymce.html.Entities.decode(node.value);
10604                  }
10605              }
10606          });
10607  
10608          // Force script into CDATA sections and remove the mce- prefix also add comments around styles
10609          htmlParser.addNodeFilter('script,style', function(nodes, name) {
10610              var i = nodes.length, node, value;
10611  
10612  			function trim(value) {
10613                  return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
10614                          .replace(/^[\r\n]*|[\r\n]*$/g, '')
10615                          .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
10616                          .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
10617              };
10618  
10619              while (i--) {
10620                  node = nodes[i];
10621                  value = node.firstChild ? node.firstChild.value : '';
10622  
10623                  if (name === "script") {
10624                      // Remove mce- prefix from script elements
10625                      node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
10626  
10627                      if (value.length > 0)
10628                          node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
10629                  } else {
10630                      if (value.length > 0)
10631                          node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
10632                  }
10633              }
10634          });
10635  
10636          // Convert comments to cdata and handle protected comments
10637          htmlParser.addNodeFilter('#comment', function(nodes, name) {
10638              var i = nodes.length, node;
10639  
10640              while (i--) {
10641                  node = nodes[i];
10642  
10643                  if (node.value.indexOf('[CDATA[') === 0) {
10644                      node.name = '#cdata';
10645                      node.type = 4;
10646                      node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
10647                  } else if (node.value.indexOf('mce:protected ') === 0) {
10648                      node.name = "#text";
10649                      node.type = 3;
10650                      node.raw = true;
10651                      node.value = unescape(node.value).substr(14);
10652                  }
10653              }
10654          });
10655  
10656          htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
10657              var i = nodes.length, node;
10658  
10659              while (i--) {
10660                  node = nodes[i];
10661                  if (node.type === 7)
10662                      node.remove();
10663                  else if (node.type === 1) {
10664                      if (name === "input" && !("type" in node.attributes.map))
10665                          node.attr('type', 'text');
10666                  }
10667              }
10668          });
10669  
10670          // Fix list elements, TODO: Replace this later
10671          if (settings.fix_list_elements) {
10672              htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
10673                  var i = nodes.length, node, parentNode;
10674  
10675                  while (i--) {
10676                      node = nodes[i];
10677                      parentNode = node.parent;
10678  
10679                      if (parentNode.name === 'ul' || parentNode.name === 'ol') {
10680                          if (node.prev && node.prev.name === 'li') {
10681                              node.prev.append(node);
10682                          }
10683                      }
10684                  }
10685              });
10686          }
10687  
10688          // Remove internal data attributes
10689          htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
10690              var i = nodes.length;
10691  
10692              while (i--) {
10693                  nodes[i].attr(name, null);
10694              }
10695          });
10696  
10697          // Return public methods
10698          return {
10699              schema : schema,
10700  
10701              addNodeFilter : htmlParser.addNodeFilter,
10702  
10703              addAttributeFilter : htmlParser.addAttributeFilter,
10704  
10705              onPreProcess : onPreProcess,
10706  
10707              onPostProcess : onPostProcess,
10708  
10709              serialize : function(node, args) {
10710                  var impl, doc, oldDoc, htmlSerializer, content;
10711  
10712                  // Explorer won't clone contents of script and style and the
10713                  // selected index of select elements are cleared on a clone operation.
10714                  if (isIE && dom.select('script,style,select,map').length > 0) {
10715                      content = node.innerHTML;
10716                      node = node.cloneNode(false);
10717                      dom.setHTML(node, content);
10718                  } else
10719                      node = node.cloneNode(true);
10720  
10721                  // Nodes needs to be attached to something in WebKit/Opera
10722                  // Older builds of Opera crashes if you attach the node to an document created dynamically
10723                  // and since we can't feature detect a crash we need to sniff the acutal build number
10724                  // This fix will make DOM ranges and make Sizzle happy!
10725                  impl = node.ownerDocument.implementation;
10726                  if (impl.createHTMLDocument) {
10727                      // Create an empty HTML document
10728                      doc = impl.createHTMLDocument("");
10729  
10730                      // Add the element or it's children if it's a body element to the new document
10731                      each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
10732                          doc.body.appendChild(doc.importNode(node, true));
10733                      });
10734  
10735                      // Grab first child or body element for serialization
10736                      if (node.nodeName != 'BODY')
10737                          node = doc.body.firstChild;
10738                      else
10739                          node = doc.body;
10740  
10741                      // set the new document in DOMUtils so createElement etc works
10742                      oldDoc = dom.doc;
10743                      dom.doc = doc;
10744                  }
10745  
10746                  args = args || {};
10747                  args.format = args.format || 'html';
10748  
10749                  // Pre process
10750                  if (!args.no_events) {
10751                      args.node = node;
10752                      onPreProcess.dispatch(self, args);
10753                  }
10754  
10755                  // Setup serializer
10756                  htmlSerializer = new tinymce.html.Serializer(settings, schema);
10757  
10758                  // Parse and serialize HTML
10759                  args.content = htmlSerializer.serialize(
10760                      htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
10761                  );
10762  
10763                  // Replace all BOM characters for now until we can find a better solution
10764                  if (!args.cleanup)
10765                      args.content = args.content.replace(/\uFEFF/g, '');
10766  
10767                  // Post process
10768                  if (!args.no_events)
10769                      onPostProcess.dispatch(self, args);
10770  
10771                  // Restore the old document if it was changed
10772                  if (oldDoc)
10773                      dom.doc = oldDoc;
10774  
10775                  args.node = null;
10776  
10777                  return args.content;
10778              },
10779  
10780              addRules : function(rules) {
10781                  schema.addValidElements(rules);
10782              },
10783  
10784              setRules : function(rules) {
10785                  schema.setValidElements(rules);
10786              }
10787          };
10788      };
10789  })(tinymce);
10790  (function(tinymce) {
10791      tinymce.dom.ScriptLoader = function(settings) {
10792          var QUEUED = 0,
10793              LOADING = 1,
10794              LOADED = 2,
10795              states = {},
10796              queue = [],
10797              scriptLoadedCallbacks = {},
10798              queueLoadedCallbacks = [],
10799              loading = 0,
10800              undef;
10801  
10802  		function loadScript(url, callback) {
10803              var t = this, dom = tinymce.DOM, elm, uri, loc, id;
10804  
10805              // Execute callback when script is loaded
10806  			function done() {
10807                  dom.remove(id);
10808  
10809                  if (elm)
10810                      elm.onreadystatechange = elm.onload = elm = null;
10811  
10812                  callback();
10813              };
10814              
10815  			function error() {
10816                  // Report the error so it's easier for people to spot loading errors
10817                  if (typeof(console) !== "undefined" && console.log)
10818                      console.log("Failed to load: " + url);
10819  
10820                  // We can't mark it as done if there is a load error since
10821                  // A) We don't want to produce 404 errors on the server and
10822                  // B) the onerror event won't fire on all browsers.
10823                  // done();
10824              };
10825  
10826              id = dom.uniqueId();
10827  
10828              if (tinymce.isIE6) {
10829                  uri = new tinymce.util.URI(url);
10830                  loc = location;
10831  
10832                  // If script is from same domain and we
10833                  // use IE 6 then use XHR since it's more reliable
10834                  if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
10835                      tinymce.util.XHR.send({
10836                          url : tinymce._addVer(uri.getURI()),
10837                          success : function(content) {
10838                              // Create new temp script element
10839                              var script = dom.create('script', {
10840                                  type : 'text/javascript'
10841                              });
10842  
10843                              // Evaluate script in global scope
10844                              script.text = content;
10845                              document.getElementsByTagName('head')[0].appendChild(script);
10846                              dom.remove(script);
10847  
10848                              done();
10849                          },
10850                          
10851                          error : error
10852                      });
10853  
10854                      return;
10855                  }
10856              }
10857  
10858              // Create new script element
10859              elm = document.createElement('script');
10860              elm.id = id;
10861              elm.type = 'text/javascript';
10862              elm.src = tinymce._addVer(url);
10863  
10864              // Add onload listener for non IE browsers since IE9
10865              // fires onload event before the script is parsed and executed
10866              if (!tinymce.isIE || tinymce.isIE11)
10867                  elm.onload = done;
10868  
10869              // Add onerror event will get fired on some browsers but not all of them
10870              elm.onerror = error;
10871  
10872              // Opera 9.60 doesn't seem to fire the onreadystate event at correctly
10873              if (!tinymce.isOpera) {
10874                  elm.onreadystatechange = function() {
10875                      var state = elm.readyState;
10876  
10877                      // Loaded state is passed on IE 6 however there
10878                      // are known issues with this method but we can't use
10879                      // XHR in a cross domain loading
10880                      if (state == 'complete' || state == 'loaded')
10881                          done();
10882                  };
10883              }
10884  
10885              // Most browsers support this feature so we report errors
10886              // for those at least to help users track their missing plugins etc
10887              // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
10888              /*elm.onerror = function() {
10889                  alert('Failed to load: ' + url);
10890              };*/
10891  
10892              // Add script to document
10893              (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
10894          };
10895  
10896          this.isDone = function(url) {
10897              return states[url] == LOADED;
10898          };
10899  
10900          this.markDone = function(url) {
10901              states[url] = LOADED;
10902          };
10903  
10904          this.add = this.load = function(url, callback, scope) {
10905              var item, state = states[url];
10906  
10907              // Add url to load queue
10908              if (state == undef) {
10909                  queue.push(url);
10910                  states[url] = QUEUED;
10911              }
10912  
10913              if (callback) {
10914                  // Store away callback for later execution
10915                  if (!scriptLoadedCallbacks[url])
10916                      scriptLoadedCallbacks[url] = [];
10917  
10918                  scriptLoadedCallbacks[url].push({
10919                      func : callback,
10920                      scope : scope || this
10921                  });
10922              }
10923          };
10924  
10925          this.loadQueue = function(callback, scope) {
10926              this.loadScripts(queue, callback, scope);
10927          };
10928  
10929          this.loadScripts = function(scripts, callback, scope) {
10930              var loadScripts;
10931  
10932  			function execScriptLoadedCallbacks(url) {
10933                  // Execute URL callback functions
10934                  tinymce.each(scriptLoadedCallbacks[url], function(callback) {
10935                      callback.func.call(callback.scope);
10936                  });
10937  
10938                  scriptLoadedCallbacks[url] = undef;
10939              };
10940  
10941              queueLoadedCallbacks.push({
10942                  func : callback,
10943                  scope : scope || this
10944              });
10945  
10946              loadScripts = function() {
10947                  var loadingScripts = tinymce.grep(scripts);
10948  
10949                  // Current scripts has been handled
10950                  scripts.length = 0;
10951  
10952                  // Load scripts that needs to be loaded
10953                  tinymce.each(loadingScripts, function(url) {
10954                      // Script is already loaded then execute script callbacks directly
10955                      if (states[url] == LOADED) {
10956                          execScriptLoadedCallbacks(url);
10957                          return;
10958                      }
10959  
10960                      // Is script not loading then start loading it
10961                      if (states[url] != LOADING) {
10962                          states[url] = LOADING;
10963                          loading++;
10964  
10965                          loadScript(url, function() {
10966                              states[url] = LOADED;
10967                              loading--;
10968  
10969                              execScriptLoadedCallbacks(url);
10970  
10971                              // Load more scripts if they where added by the recently loaded script
10972                              loadScripts();
10973                          });
10974                      }
10975                  });
10976  
10977                  // No scripts are currently loading then execute all pending queue loaded callbacks
10978                  if (!loading) {
10979                      tinymce.each(queueLoadedCallbacks, function(callback) {
10980                          callback.func.call(callback.scope);
10981                      });
10982  
10983                      queueLoadedCallbacks.length = 0;
10984                  }
10985              };
10986  
10987              loadScripts();
10988          };
10989      };
10990  
10991      // Global script loader
10992      tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
10993  })(tinymce);
10994  (function(tinymce) {
10995      tinymce.dom.RangeUtils = function(dom) {
10996          var INVISIBLE_CHAR = '\uFEFF';
10997  
10998          this.walk = function(rng, callback) {
10999              var startContainer = rng.startContainer,
11000                  startOffset = rng.startOffset,
11001                  endContainer = rng.endContainer,
11002                  endOffset = rng.endOffset,
11003                  ancestor, startPoint,
11004                  endPoint, node, parent, siblings, nodes;
11005  
11006              // Handle table cell selection the table plugin enables
11007              // you to fake select table cells and perform formatting actions on them
11008              nodes = dom.select('td.mceSelected,th.mceSelected');
11009              if (nodes.length > 0) {
11010                  tinymce.each(nodes, function(node) {
11011                      callback([node]);
11012                  });
11013  
11014                  return;
11015              }
11016  
11017  			function exclude(nodes) {
11018                  var node;
11019  
11020                  // First node is excluded
11021                  node = nodes[0];
11022                  if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
11023                      nodes.splice(0, 1);
11024                  }
11025  
11026                  // Last node is excluded
11027                  node = nodes[nodes.length - 1];
11028                  if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
11029                      nodes.splice(nodes.length - 1, 1);
11030                  }
11031  
11032                  return nodes;
11033              };
11034  
11035  			function collectSiblings(node, name, end_node) {
11036                  var siblings = [];
11037  
11038                  for (; node && node != end_node; node = node[name])
11039                      siblings.push(node);
11040  
11041                  return siblings;
11042              };
11043  
11044  			function findEndPoint(node, root) {
11045                  do {
11046                      if (node.parentNode == root)
11047                          return node;
11048  
11049                      node = node.parentNode;
11050                  } while(node);
11051              };
11052  
11053  			function walkBoundary(start_node, end_node, next) {
11054                  var siblingName = next ? 'nextSibling' : 'previousSibling';
11055  
11056                  for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
11057                      parent = node.parentNode;
11058                      siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
11059  
11060                      if (siblings.length) {
11061                          if (!next)
11062                              siblings.reverse();
11063  
11064                          callback(exclude(siblings));
11065                      }
11066                  }
11067              };
11068  
11069              // If index based start position then resolve it
11070              if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
11071                  startContainer = startContainer.childNodes[startOffset];
11072  
11073              // If index based end position then resolve it
11074              if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
11075                  endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
11076  
11077              // Same container
11078              if (startContainer == endContainer)
11079                  return callback(exclude([startContainer]));
11080  
11081              // Find common ancestor and end points
11082              ancestor = dom.findCommonAncestor(startContainer, endContainer);
11083                  
11084              // Process left side
11085              for (node = startContainer; node; node = node.parentNode) {
11086                  if (node === endContainer)
11087                      return walkBoundary(startContainer, ancestor, true);
11088  
11089                  if (node === ancestor)
11090                      break;
11091              }
11092  
11093              // Process right side
11094              for (node = endContainer; node; node = node.parentNode) {
11095                  if (node === startContainer)
11096                      return walkBoundary(endContainer, ancestor);
11097  
11098                  if (node === ancestor)
11099                      break;
11100              }
11101  
11102              // Find start/end point
11103              startPoint = findEndPoint(startContainer, ancestor) || startContainer;
11104              endPoint = findEndPoint(endContainer, ancestor) || endContainer;
11105  
11106              // Walk left leaf
11107              walkBoundary(startContainer, startPoint, true);
11108  
11109              // Walk the middle from start to end point
11110              siblings = collectSiblings(
11111                  startPoint == startContainer ? startPoint : startPoint.nextSibling,
11112                  'nextSibling',
11113                  endPoint == endContainer ? endPoint.nextSibling : endPoint
11114              );
11115  
11116              if (siblings.length)
11117                  callback(exclude(siblings));
11118  
11119              // Walk right leaf
11120              walkBoundary(endContainer, endPoint);
11121          };
11122  
11123          this.split = function(rng) {
11124              var startContainer = rng.startContainer,
11125                  startOffset = rng.startOffset,
11126                  endContainer = rng.endContainer,
11127                  endOffset = rng.endOffset;
11128  
11129  			function splitText(node, offset) {
11130                  return node.splitText(offset);
11131              };
11132  
11133              // Handle single text node
11134              if (startContainer == endContainer && startContainer.nodeType == 3) {
11135                  if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11136                      endContainer = splitText(startContainer, startOffset);
11137                      startContainer = endContainer.previousSibling;
11138  
11139                      if (endOffset > startOffset) {
11140                          endOffset = endOffset - startOffset;
11141                          startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
11142                          endOffset = endContainer.nodeValue.length;
11143                          startOffset = 0;
11144                      } else {
11145                          endOffset = 0;
11146                      }
11147                  }
11148              } else {
11149                  // Split startContainer text node if needed
11150                  if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
11151                      startContainer = splitText(startContainer, startOffset);
11152                      startOffset = 0;
11153                  }
11154  
11155                  // Split endContainer text node if needed
11156                  if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
11157                      endContainer = splitText(endContainer, endOffset).previousSibling;
11158                      endOffset = endContainer.nodeValue.length;
11159                  }
11160              }
11161  
11162              return {
11163                  startContainer : startContainer,
11164                  startOffset : startOffset,
11165                  endContainer : endContainer,
11166                  endOffset : endOffset
11167              };
11168          };
11169  
11170      };
11171  
11172      tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
11173          if (rng1 && rng2) {
11174              // Compare native IE ranges
11175              if (rng1.item || rng1.duplicate) {
11176                  // Both are control ranges and the selected element matches
11177                  if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
11178                      return true;
11179  
11180                  // Both are text ranges and the range matches
11181                  if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
11182                      return true;
11183              } else {
11184                  // Compare w3c ranges
11185                  return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
11186              }
11187          }
11188  
11189          return false;
11190      };
11191  })(tinymce);
11192  (function(tinymce) {
11193      var Event = tinymce.dom.Event, each = tinymce.each;
11194  
11195      tinymce.create('tinymce.ui.KeyboardNavigation', {
11196          KeyboardNavigation: function(settings, dom) {
11197              var t = this, root = settings.root, items = settings.items,
11198                      enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
11199                      excludeFromTabOrder = settings.excludeFromTabOrder,
11200                      itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
11201  
11202              dom = dom || tinymce.DOM;
11203  
11204              itemFocussed = function(evt) {
11205                  focussedId = evt.target.id;
11206              };
11207              
11208              itemBlurred = function(evt) {
11209                  dom.setAttrib(evt.target.id, 'tabindex', '-1');
11210              };
11211              
11212              rootFocussed = function(evt) {
11213                  var item = dom.get(focussedId);
11214                  dom.setAttrib(item, 'tabindex', '0');
11215                  item.focus();
11216              };
11217              
11218              t.focus = function() {
11219                  dom.get(focussedId).focus();
11220              };
11221  
11222              t.destroy = function() {
11223                  each(items, function(item) {
11224                      var elm = dom.get(item.id);
11225  
11226                      dom.unbind(elm, 'focus', itemFocussed);
11227                      dom.unbind(elm, 'blur', itemBlurred);
11228                  });
11229  
11230                  var rootElm = dom.get(root);
11231                  dom.unbind(rootElm, 'focus', rootFocussed);
11232                  dom.unbind(rootElm, 'keydown', rootKeydown);
11233  
11234                  items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
11235                  t.destroy = function() {};
11236              };
11237              
11238              t.moveFocus = function(dir, evt) {
11239                  var idx = -1, controls = t.controls, newFocus;
11240  
11241                  if (!focussedId)
11242                      return;
11243  
11244                  each(items, function(item, index) {
11245                      if (item.id === focussedId) {
11246                          idx = index;
11247                          return false;
11248                      }
11249                  });
11250  
11251                  idx += dir;
11252                  if (idx < 0) {
11253                      idx = items.length - 1;
11254                  } else if (idx >= items.length) {
11255                      idx = 0;
11256                  }
11257                  
11258                  newFocus = items[idx];
11259                  dom.setAttrib(focussedId, 'tabindex', '-1');
11260                  dom.setAttrib(newFocus.id, 'tabindex', '0');
11261                  dom.get(newFocus.id).focus();
11262  
11263                  if (settings.actOnFocus) {
11264                      settings.onAction(newFocus.id);
11265                  }
11266  
11267                  if (evt)
11268                      Event.cancel(evt);
11269              };
11270              
11271              rootKeydown = function(evt) {
11272                  var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
11273                  
11274                  switch (evt.keyCode) {
11275                      case DOM_VK_LEFT:
11276                          if (enableLeftRight) t.moveFocus(-1);
11277                          Event.cancel(evt);
11278                          break;
11279      
11280                      case DOM_VK_RIGHT:
11281                          if (enableLeftRight) t.moveFocus(1);
11282                          Event.cancel(evt);
11283                          break;
11284      
11285                      case DOM_VK_UP:
11286                          if (enableUpDown) t.moveFocus(-1);
11287                          Event.cancel(evt);
11288                          break;
11289  
11290                      case DOM_VK_DOWN:
11291                          if (enableUpDown) t.moveFocus(1);
11292                          Event.cancel(evt);
11293                          break;
11294  
11295                      case DOM_VK_ESCAPE:
11296                          if (settings.onCancel) {
11297                              settings.onCancel();
11298                              Event.cancel(evt);
11299                          }
11300                          break;
11301  
11302                      case DOM_VK_ENTER:
11303                      case DOM_VK_RETURN:
11304                      case DOM_VK_SPACE:
11305                          if (settings.onAction) {
11306                              settings.onAction(focussedId);
11307                              Event.cancel(evt);
11308                          }
11309                          break;
11310                  }
11311              };
11312  
11313              // Set up state and listeners for each item.
11314              each(items, function(item, idx) {
11315                  var tabindex, elm;
11316  
11317                  if (!item.id) {
11318                      item.id = dom.uniqueId('_mce_item_');
11319                  }
11320  
11321                  elm = dom.get(item.id);
11322  
11323                  if (excludeFromTabOrder) {
11324                      dom.bind(elm, 'blur', itemBlurred);
11325                      tabindex = '-1';
11326                  } else {
11327                      tabindex = (idx === 0 ? '0' : '-1');
11328                  }
11329  
11330                  elm.setAttribute('tabindex', tabindex);
11331                  dom.bind(elm, 'focus', itemFocussed);
11332              });
11333              
11334              // Setup initial state for root element.
11335              if (items[0]){
11336                  focussedId = items[0].id;
11337              }
11338  
11339              dom.setAttrib(root, 'tabindex', '-1');
11340  
11341              // Setup listeners for root element.
11342              var rootElm = dom.get(root);
11343              dom.bind(rootElm, 'focus', rootFocussed);
11344              dom.bind(rootElm, 'keydown', rootKeydown);
11345          }
11346      });
11347  })(tinymce);
11348  (function(tinymce) {
11349      // Shorten class names
11350      var DOM = tinymce.DOM, is = tinymce.is;
11351  
11352      tinymce.create('tinymce.ui.Control', {
11353          Control : function(id, s, editor) {
11354              this.id = id;
11355              this.settings = s = s || {};
11356              this.rendered = false;
11357              this.onRender = new tinymce.util.Dispatcher(this);
11358              this.classPrefix = '';
11359              this.scope = s.scope || this;
11360              this.disabled = 0;
11361              this.active = 0;
11362              this.editor = editor;
11363          },
11364          
11365          setAriaProperty : function(property, value) {
11366              var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
11367              if (element) {
11368                  DOM.setAttrib(element, 'aria-' + property, !!value);
11369              }
11370          },
11371          
11372          focus : function() {
11373              DOM.get(this.id).focus();
11374          },
11375  
11376          setDisabled : function(s) {
11377              if (s != this.disabled) {
11378                  this.setAriaProperty('disabled', s);
11379  
11380                  this.setState('Disabled', s);
11381                  this.setState('Enabled', !s);
11382                  this.disabled = s;
11383              }
11384          },
11385  
11386          isDisabled : function() {
11387              return this.disabled;
11388          },
11389  
11390          setActive : function(s) {
11391              if (s != this.active) {
11392                  this.setState('Active', s);
11393                  this.active = s;
11394                  this.setAriaProperty('pressed', s);
11395              }
11396          },
11397  
11398          isActive : function() {
11399              return this.active;
11400          },
11401  
11402          setState : function(c, s) {
11403              var n = DOM.get(this.id);
11404  
11405              c = this.classPrefix + c;
11406  
11407              if (s)
11408                  DOM.addClass(n, c);
11409              else
11410                  DOM.removeClass(n, c);
11411          },
11412  
11413          isRendered : function() {
11414              return this.rendered;
11415          },
11416  
11417          renderHTML : function() {
11418          },
11419  
11420          renderTo : function(n) {
11421              DOM.setHTML(n, this.renderHTML());
11422          },
11423  
11424          postRender : function() {
11425              var t = this, b;
11426  
11427              // Set pending states
11428              if (is(t.disabled)) {
11429                  b = t.disabled;
11430                  t.disabled = -1;
11431                  t.setDisabled(b);
11432              }
11433  
11434              if (is(t.active)) {
11435                  b = t.active;
11436                  t.active = -1;
11437                  t.setActive(b);
11438              }
11439          },
11440  
11441          remove : function() {
11442              DOM.remove(this.id);
11443              this.destroy();
11444          },
11445  
11446          destroy : function() {
11447              tinymce.dom.Event.clear(this.id);
11448          }
11449      });
11450  })(tinymce);
11451  tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
11452      Container : function(id, s, editor) {
11453          this.parent(id, s, editor);
11454  
11455          this.controls = [];
11456  
11457          this.lookup = {};
11458      },
11459  
11460      add : function(c) {
11461          this.lookup[c.id] = c;
11462          this.controls.push(c);
11463  
11464          return c;
11465      },
11466  
11467      get : function(n) {
11468          return this.lookup[n];
11469      }
11470  });
11471  
11472  tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
11473      Separator : function(id, s) {
11474          this.parent(id, s);
11475          this.classPrefix = 'mceSeparator';
11476          this.setDisabled(true);
11477      },
11478  
11479      renderHTML : function() {
11480          return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
11481      }
11482  });
11483  (function(tinymce) {
11484      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11485  
11486      tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
11487          MenuItem : function(id, s) {
11488              this.parent(id, s);
11489              this.classPrefix = 'mceMenuItem';
11490          },
11491  
11492          setSelected : function(s) {
11493              this.setState('Selected', s);
11494              this.setAriaProperty('checked', !!s);
11495              this.selected = s;
11496          },
11497  
11498          isSelected : function() {
11499              return this.selected;
11500          },
11501  
11502          postRender : function() {
11503              var t = this;
11504              
11505              t.parent();
11506  
11507              // Set pending state
11508              if (is(t.selected))
11509                  t.setSelected(t.selected);
11510          }
11511      });
11512  })(tinymce);
11513  (function(tinymce) {
11514      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
11515  
11516      tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
11517          Menu : function(id, s) {
11518              var t = this;
11519  
11520              t.parent(id, s);
11521              t.items = {};
11522              t.collapsed = false;
11523              t.menuCount = 0;
11524              t.onAddItem = new tinymce.util.Dispatcher(this);
11525          },
11526  
11527          expand : function(d) {
11528              var t = this;
11529  
11530              if (d) {
11531                  walk(t, function(o) {
11532                      if (o.expand)
11533                          o.expand();
11534                  }, 'items', t);
11535              }
11536  
11537              t.collapsed = false;
11538          },
11539  
11540          collapse : function(d) {
11541              var t = this;
11542  
11543              if (d) {
11544                  walk(t, function(o) {
11545                      if (o.collapse)
11546                          o.collapse();
11547                  }, 'items', t);
11548              }
11549  
11550              t.collapsed = true;
11551          },
11552  
11553          isCollapsed : function() {
11554              return this.collapsed;
11555          },
11556  
11557          add : function(o) {
11558              if (!o.settings)
11559                  o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
11560  
11561              this.onAddItem.dispatch(this, o);
11562  
11563              return this.items[o.id] = o;
11564          },
11565  
11566          addSeparator : function() {
11567              return this.add({separator : true});
11568          },
11569  
11570          addMenu : function(o) {
11571              if (!o.collapse)
11572                  o = this.createMenu(o);
11573  
11574              this.menuCount++;
11575  
11576              return this.add(o);
11577          },
11578  
11579          hasMenus : function() {
11580              return this.menuCount !== 0;
11581          },
11582  
11583          remove : function(o) {
11584              delete this.items[o.id];
11585          },
11586  
11587          removeAll : function() {
11588              var t = this;
11589  
11590              walk(t, function(o) {
11591                  if (o.removeAll)
11592                      o.removeAll();
11593                  else
11594                      o.remove();
11595  
11596                  o.destroy();
11597              }, 'items', t);
11598  
11599              t.items = {};
11600          },
11601  
11602          createMenu : function(o) {
11603              var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
11604  
11605              m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
11606  
11607              return m;
11608          }
11609      });
11610  })(tinymce);
11611  (function(tinymce) {
11612      var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
11613  
11614      tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
11615          DropMenu : function(id, s) {
11616              s = s || {};
11617              s.container = s.container || DOM.doc.body;
11618              s.offset_x = s.offset_x || 0;
11619              s.offset_y = s.offset_y || 0;
11620              s.vp_offset_x = s.vp_offset_x || 0;
11621              s.vp_offset_y = s.vp_offset_y || 0;
11622  
11623              if (is(s.icons) && !s.icons)
11624                  s['class'] += ' mceNoIcons';
11625  
11626              this.parent(id, s);
11627              this.onShowMenu = new tinymce.util.Dispatcher(this);
11628              this.onHideMenu = new tinymce.util.Dispatcher(this);
11629              this.classPrefix = 'mceMenu';
11630          },
11631  
11632          createMenu : function(s) {
11633              var t = this, cs = t.settings, m;
11634  
11635              s.container = s.container || cs.container;
11636              s.parent = t;
11637              s.constrain = s.constrain || cs.constrain;
11638              s['class'] = s['class'] || cs['class'];
11639              s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
11640              s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
11641              s.keyboard_focus = cs.keyboard_focus;
11642              m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
11643  
11644              m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
11645  
11646              return m;
11647          },
11648          
11649          focus : function() {
11650              var t = this;
11651              if (t.keyboardNav) {
11652                  t.keyboardNav.focus();
11653              }
11654          },
11655  
11656          update : function() {
11657              var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
11658  
11659              tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
11660              th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
11661  
11662              if (!DOM.boxModel)
11663                  t.element.setStyles({width : tw + 2, height : th + 2});
11664              else
11665                  t.element.setStyles({width : tw, height : th});
11666  
11667              if (s.max_width)
11668                  DOM.setStyle(co, 'width', tw);
11669  
11670              if (s.max_height) {
11671                  DOM.setStyle(co, 'height', th);
11672  
11673                  if (tb.clientHeight < s.max_height)
11674                      DOM.setStyle(co, 'overflow', 'hidden');
11675              }
11676          },
11677  
11678          showMenu : function(x, y, px) {
11679              var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
11680  
11681              t.collapse(1);
11682  
11683              if (t.isMenuVisible)
11684                  return;
11685  
11686              if (!t.rendered) {
11687                  co = DOM.add(t.settings.container, t.renderNode());
11688  
11689                  each(t.items, function(o) {
11690                      o.postRender();
11691                  });
11692  
11693                  t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11694              } else
11695                  co = DOM.get('menu_' + t.id);
11696  
11697              // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
11698              if (!tinymce.isOpera)
11699                  DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
11700  
11701              DOM.show(co);
11702              t.update();
11703  
11704              x += s.offset_x || 0;
11705              y += s.offset_y || 0;
11706              vp.w -= 4;
11707              vp.h -= 4;
11708  
11709              // Move inside viewport if not submenu
11710              if (s.constrain) {
11711                  w = co.clientWidth - ot;
11712                  h = co.clientHeight - ot;
11713                  mx = vp.x + vp.w;
11714                  my = vp.y + vp.h;
11715  
11716                  if ((x + s.vp_offset_x + w) > mx)
11717                      x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
11718  
11719                  if ((y + s.vp_offset_y + h) > my)
11720                      y = Math.max(0, (my - s.vp_offset_y) - h);
11721              }
11722  
11723              DOM.setStyles(co, {left : x , top : y});
11724              t.element.update();
11725  
11726              t.isMenuVisible = 1;
11727              t.mouseClickFunc = Event.add(co, 'click', function(e) {
11728                  var m;
11729  
11730                  e = e.target;
11731  
11732                  if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
11733                      m = t.items[e.id];
11734  
11735                      if (m.isDisabled())
11736                          return;
11737  
11738                      dm = t;
11739  
11740                      while (dm) {
11741                          if (dm.hideMenu)
11742                              dm.hideMenu();
11743  
11744                          dm = dm.settings.parent;
11745                      }
11746  
11747                      if (m.settings.onclick)
11748                          m.settings.onclick(e);
11749  
11750                      return false; // Cancel to fix onbeforeunload problem
11751                  }
11752              });
11753  
11754              if (t.hasMenus()) {
11755                  t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
11756                      var m, r, mi;
11757  
11758                      e = e.target;
11759                      if (e && (e = DOM.getParent(e, 'tr'))) {
11760                          m = t.items[e.id];
11761  
11762                          if (t.lastMenu)
11763                              t.lastMenu.collapse(1);
11764  
11765                          if (m.isDisabled())
11766                              return;
11767  
11768                          if (e && DOM.hasClass(e, cp + 'ItemSub')) {
11769                              //p = DOM.getPos(s.container);
11770                              r = DOM.getRect(e);
11771                              m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
11772                              t.lastMenu = m;
11773                              DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
11774                          }
11775                      }
11776                  });
11777              }
11778              
11779              Event.add(co, 'keydown', t._keyHandler, t);
11780  
11781              t.onShowMenu.dispatch(t);
11782  
11783              if (s.keyboard_focus) { 
11784                  t._setupKeyboardNav(); 
11785              }
11786          },
11787  
11788          hideMenu : function(c) {
11789              var t = this, co = DOM.get('menu_' + t.id), e;
11790  
11791              if (!t.isMenuVisible)
11792                  return;
11793  
11794              if (t.keyboardNav) t.keyboardNav.destroy();
11795              Event.remove(co, 'mouseover', t.mouseOverFunc);
11796              Event.remove(co, 'click', t.mouseClickFunc);
11797              Event.remove(co, 'keydown', t._keyHandler);
11798              DOM.hide(co);
11799              t.isMenuVisible = 0;
11800  
11801              if (!c)
11802                  t.collapse(1);
11803  
11804              if (t.element)
11805                  t.element.hide();
11806  
11807              if (e = DOM.get(t.id))
11808                  DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
11809  
11810              t.onHideMenu.dispatch(t);
11811          },
11812  
11813          add : function(o) {
11814              var t = this, co;
11815  
11816              o = t.parent(o);
11817  
11818              if (t.isRendered && (co = DOM.get('menu_' + t.id)))
11819                  t._add(DOM.select('tbody', co)[0], o);
11820  
11821              return o;
11822          },
11823  
11824          collapse : function(d) {
11825              this.parent(d);
11826              this.hideMenu(1);
11827          },
11828  
11829          remove : function(o) {
11830              DOM.remove(o.id);
11831              this.destroy();
11832  
11833              return this.parent(o);
11834          },
11835  
11836          destroy : function() {
11837              var t = this, co = DOM.get('menu_' + t.id);
11838  
11839              if (t.keyboardNav) t.keyboardNav.destroy();
11840              Event.remove(co, 'mouseover', t.mouseOverFunc);
11841              Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
11842              Event.remove(co, 'click', t.mouseClickFunc);
11843              Event.remove(co, 'keydown', t._keyHandler);
11844  
11845              if (t.element)
11846                  t.element.remove();
11847  
11848              DOM.remove(co);
11849          },
11850  
11851          renderNode : function() {
11852              var t = this, s = t.settings, n, tb, co, w;
11853  
11854              w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
11855              if (t.settings.parent) {
11856                  DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
11857              }
11858              co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
11859              t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
11860  
11861              if (s.menu_line)
11862                  DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
11863  
11864  //            n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
11865              n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
11866              tb = DOM.add(n, 'tbody');
11867  
11868              each(t.items, function(o) {
11869                  t._add(tb, o);
11870              });
11871  
11872              t.rendered = true;
11873  
11874              return w;
11875          },
11876  
11877          // Internal functions
11878          _setupKeyboardNav : function(){
11879              var contextMenu, menuItems, t=this; 
11880              contextMenu = DOM.get('menu_' + t.id);
11881              menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
11882              menuItems.splice(0,0,contextMenu);
11883              t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11884                  root: 'menu_' + t.id,
11885                  items: menuItems,
11886                  onCancel: function() {
11887                      t.hideMenu();
11888                  },
11889                  enableUpDown: true
11890              });
11891              contextMenu.focus();
11892          },
11893  
11894          _keyHandler : function(evt) {
11895              var t = this, e;
11896              switch (evt.keyCode) {
11897                  case 37: // Left
11898                      if (t.settings.parent) {
11899                          t.hideMenu();
11900                          t.settings.parent.focus();
11901                          Event.cancel(evt);
11902                      }
11903                      break;
11904                  case 39: // Right
11905                      if (t.mouseOverFunc)
11906                          t.mouseOverFunc(evt);
11907                      break;
11908              }
11909          },
11910  
11911          _add : function(tb, o) {
11912              var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
11913  
11914              if (s.separator) {
11915                  ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
11916                  DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
11917  
11918                  if (n = ro.previousSibling)
11919                      DOM.addClass(n, 'mceLast');
11920  
11921                  return;
11922              }
11923  
11924              n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
11925              n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
11926              n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
11927  
11928              if (s.parent) {
11929                  DOM.setAttrib(a, 'aria-haspopup', 'true');
11930                  DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
11931              }
11932  
11933              DOM.addClass(it, s['class']);
11934  //            n = DOM.add(n, 'span', {'class' : 'item'});
11935  
11936              ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
11937  
11938              if (s.icon_src)
11939                  DOM.add(ic, 'img', {src : s.icon_src});
11940  
11941              n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
11942  
11943              if (o.settings.style) {
11944                  if (typeof o.settings.style == "function")
11945                      o.settings.style = o.settings.style();
11946  
11947                  DOM.setAttrib(n, 'style', o.settings.style);
11948              }
11949  
11950              if (tb.childNodes.length == 1)
11951                  DOM.addClass(ro, 'mceFirst');
11952  
11953              if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
11954                  DOM.addClass(ro, 'mceFirst');
11955  
11956              if (o.collapse)
11957                  DOM.addClass(ro, cp + 'ItemSub');
11958  
11959              if (n = ro.previousSibling)
11960                  DOM.removeClass(n, 'mceLast');
11961  
11962              DOM.addClass(ro, 'mceLast');
11963          }
11964      });
11965  })(tinymce);
11966  (function(tinymce) {
11967      var DOM = tinymce.DOM;
11968  
11969      tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
11970          Button : function(id, s, ed) {
11971              this.parent(id, s, ed);
11972              this.classPrefix = 'mceButton';
11973          },
11974  
11975          renderHTML : function() {
11976              var cp = this.classPrefix, s = this.settings, h, l;
11977  
11978              l = DOM.encode(s.label || '');
11979              h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
11980              if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
11981                  h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11982              else
11983                  h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
11984  
11985              h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
11986              h += '</a>';
11987              return h;
11988          },
11989  
11990          postRender : function() {
11991              var t = this, s = t.settings, imgBookmark;
11992  
11993              // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
11994              // need to keep the selection in case the selection is lost
11995              if (tinymce.isIE && t.editor) {
11996                  tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
11997                      var nodeName = t.editor.selection.getNode().nodeName;
11998                      imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
11999                  });
12000              }
12001              tinymce.dom.Event.add(t.id, 'click', function(e) {
12002                  if (!t.isDisabled()) {
12003                      // restore the selection in case the selection is lost in IE
12004                      if (tinymce.isIE && t.editor && imgBookmark !== null) {
12005                          t.editor.selection.moveToBookmark(imgBookmark);
12006                      }
12007                      return s.onclick.call(s.scope, e);
12008                  }
12009              });
12010              tinymce.dom.Event.add(t.id, 'keydown', function(e) {
12011                  if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR) {
12012                      tinymce.dom.Event.cancel(e);
12013                      return s.onclick.call(s.scope, e);
12014                  }
12015              });
12016          }
12017      });
12018  })(tinymce);
12019  (function(tinymce) {
12020      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
12021  
12022      tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
12023          ListBox : function(id, s, ed) {
12024              var t = this;
12025  
12026              t.parent(id, s, ed);
12027  
12028              t.items = [];
12029  
12030              t.onChange = new Dispatcher(t);
12031  
12032              t.onPostRender = new Dispatcher(t);
12033  
12034              t.onAdd = new Dispatcher(t);
12035  
12036              t.onRenderMenu = new tinymce.util.Dispatcher(this);
12037  
12038              t.classPrefix = 'mceListBox';
12039              t.marked = {};
12040          },
12041  
12042          select : function(va) {
12043              var t = this, fv, f;
12044  
12045              t.marked = {};
12046  
12047              if (va == undef)
12048                  return t.selectByIndex(-1);
12049  
12050              // Is string or number make function selector
12051              if (va && typeof(va)=="function")
12052                  f = va;
12053              else {
12054                  f = function(v) {
12055                      return v == va;
12056                  };
12057              }
12058  
12059              // Do we need to do something?
12060              if (va != t.selectedValue) {
12061                  // Find item
12062                  each(t.items, function(o, i) {
12063                      if (f(o.value)) {
12064                          fv = 1;
12065                          t.selectByIndex(i);
12066                          return false;
12067                      }
12068                  });
12069  
12070                  if (!fv)
12071                      t.selectByIndex(-1);
12072              }
12073          },
12074  
12075          selectByIndex : function(idx) {
12076              var t = this, e, o, label;
12077  
12078              t.marked = {};
12079  
12080              if (idx != t.selectedIndex) {
12081                  e = DOM.get(t.id + '_text');
12082                  label = DOM.get(t.id + '_voiceDesc');
12083                  o = t.items[idx];
12084  
12085                  if (o) {
12086                      t.selectedValue = o.value;
12087                      t.selectedIndex = idx;
12088                      DOM.setHTML(e, DOM.encode(o.title));
12089                      DOM.setHTML(label, t.settings.title + " - " + o.title);
12090                      DOM.removeClass(e, 'mceTitle');
12091                      DOM.setAttrib(t.id, 'aria-valuenow', o.title);
12092                  } else {
12093                      DOM.setHTML(e, DOM.encode(t.settings.title));
12094                      DOM.setHTML(label, DOM.encode(t.settings.title));
12095                      DOM.addClass(e, 'mceTitle');
12096                      t.selectedValue = t.selectedIndex = null;
12097                      DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
12098                  }
12099                  e = 0;
12100              }
12101          },
12102  
12103          mark : function(value) {
12104              this.marked[value] = true;
12105          },
12106  
12107          add : function(n, v, o) {
12108              var t = this;
12109  
12110              o = o || {};
12111              o = tinymce.extend(o, {
12112                  title : n,
12113                  value : v
12114              });
12115  
12116              t.items.push(o);
12117              t.onAdd.dispatch(t, o);
12118          },
12119  
12120          getLength : function() {
12121              return this.items.length;
12122          },
12123  
12124          renderHTML : function() {
12125              var h = '', t = this, s = t.settings, cp = t.classPrefix;
12126  
12127              h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
12128              h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
12129              h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
12130              h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
12131              h += '</tr></tbody></table></span>';
12132  
12133              return h;
12134          },
12135  
12136          showMenu : function() {
12137              var t = this, p2, e = DOM.get(this.id), m;
12138  
12139              if (t.isDisabled() || t.items.length === 0)
12140                  return;
12141  
12142              if (t.menu && t.menu.isMenuVisible)
12143                  return t.hideMenu();
12144  
12145              if (!t.isMenuRendered) {
12146                  t.renderMenu();
12147                  t.isMenuRendered = true;
12148              }
12149  
12150              p2 = DOM.getPos(e);
12151  
12152              m = t.menu;
12153              m.settings.offset_x = p2.x;
12154              m.settings.offset_y = p2.y;
12155              m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
12156  
12157              // Select in menu
12158              each(t.items, function(o) {
12159                  if (m.items[o.id]) {
12160                      m.items[o.id].setSelected(0);
12161                  }
12162              });
12163  
12164              each(t.items, function(o) {
12165                  if (m.items[o.id] && t.marked[o.value]) {
12166                      m.items[o.id].setSelected(1);
12167                  }
12168  
12169                  if (o.value === t.selectedValue) {
12170                      m.items[o.id].setSelected(1);
12171                  }
12172              });
12173  
12174              m.showMenu(0, e.clientHeight);
12175  
12176              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12177              DOM.addClass(t.id, t.classPrefix + 'Selected');
12178  
12179              //DOM.get(t.id + '_text').focus();
12180          },
12181  
12182          hideMenu : function(e) {
12183              var t = this;
12184  
12185              if (t.menu && t.menu.isMenuVisible) {
12186                  DOM.removeClass(t.id, t.classPrefix + 'Selected');
12187  
12188                  // Prevent double toogles by canceling the mouse click event to the button
12189                  if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
12190                      return;
12191  
12192                  if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12193                      DOM.removeClass(t.id, t.classPrefix + 'Selected');
12194                      Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12195                      t.menu.hideMenu();
12196                  }
12197              }
12198          },
12199  
12200          renderMenu : function() {
12201              var t = this, m;
12202  
12203              m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12204                  menu_line : 1,
12205                  'class' : t.classPrefix + 'Menu mceNoIcons',
12206                  max_width : 250,
12207                  max_height : 150
12208              });
12209  
12210              m.onHideMenu.add(function() {
12211                  t.hideMenu();
12212                  t.focus();
12213              });
12214  
12215              m.add({
12216                  title : t.settings.title,
12217                  'class' : 'mceMenuItemTitle',
12218                  onclick : function() {
12219                      if (t.settings.onselect('') !== false)
12220                          t.select(''); // Must be runned after
12221                  }
12222              });
12223  
12224              each(t.items, function(o) {
12225                  // No value then treat it as a title
12226                  if (o.value === undef) {
12227                      m.add({
12228                          title : o.title,
12229                          role : "option",
12230                          'class' : 'mceMenuItemTitle',
12231                          onclick : function() {
12232                              if (t.settings.onselect('') !== false)
12233                                  t.select(''); // Must be runned after
12234                          }
12235                      });
12236                  } else {
12237                      o.id = DOM.uniqueId();
12238                      o.role= "option";
12239                      o.onclick = function() {
12240                          if (t.settings.onselect(o.value) !== false)
12241                              t.select(o.value); // Must be runned after
12242                      };
12243  
12244                      m.add(o);
12245                  }
12246              });
12247  
12248              t.onRenderMenu.dispatch(t, m);
12249              t.menu = m;
12250          },
12251  
12252          postRender : function() {
12253              var t = this, cp = t.classPrefix;
12254  
12255              Event.add(t.id, 'click', t.showMenu, t);
12256              Event.add(t.id, 'keydown', function(evt) {
12257                  if (evt.keyCode == 32) { // Space
12258                      t.showMenu(evt);
12259                      Event.cancel(evt);
12260                  }
12261              });
12262              Event.add(t.id, 'focus', function() {
12263                  if (!t._focused) {
12264                      t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
12265                          if (e.keyCode == 40) {
12266                              t.showMenu();
12267                              Event.cancel(e);
12268                          }
12269                      });
12270                      t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
12271                          var v;
12272                          if (e.keyCode == 13) {
12273                              // Fake select on enter
12274                              v = t.selectedValue;
12275                              t.selectedValue = null; // Needs to be null to fake change
12276                              Event.cancel(e);
12277                              t.settings.onselect(v);
12278                          }
12279                      });
12280                  }
12281  
12282                  t._focused = 1;
12283              });
12284              Event.add(t.id, 'blur', function() {
12285                  Event.remove(t.id, 'keydown', t.keyDownHandler);
12286                  Event.remove(t.id, 'keypress', t.keyPressHandler);
12287                  t._focused = 0;
12288              });
12289  
12290              // Old IE doesn't have hover on all elements
12291              if (tinymce.isIE6 || !DOM.boxModel) {
12292                  Event.add(t.id, 'mouseover', function() {
12293                      if (!DOM.hasClass(t.id, cp + 'Disabled'))
12294                          DOM.addClass(t.id, cp + 'Hover');
12295                  });
12296  
12297                  Event.add(t.id, 'mouseout', function() {
12298                      if (!DOM.hasClass(t.id, cp + 'Disabled'))
12299                          DOM.removeClass(t.id, cp + 'Hover');
12300                  });
12301              }
12302  
12303              t.onPostRender.dispatch(t, DOM.get(t.id));
12304          },
12305  
12306          destroy : function() {
12307              this.parent();
12308  
12309              Event.clear(this.id + '_text');
12310              Event.clear(this.id + '_open');
12311          }
12312      });
12313  })(tinymce);
12314  (function(tinymce) {
12315      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
12316  
12317      tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
12318          NativeListBox : function(id, s) {
12319              this.parent(id, s);
12320              this.classPrefix = 'mceNativeListBox';
12321          },
12322  
12323          setDisabled : function(s) {
12324              DOM.get(this.id).disabled = s;
12325              this.setAriaProperty('disabled', s);
12326          },
12327  
12328          isDisabled : function() {
12329              return DOM.get(this.id).disabled;
12330          },
12331  
12332          select : function(va) {
12333              var t = this, fv, f;
12334  
12335              if (va == undef)
12336                  return t.selectByIndex(-1);
12337  
12338              // Is string or number make function selector
12339              if (va && typeof(va)=="function")
12340                  f = va;
12341              else {
12342                  f = function(v) {
12343                      return v == va;
12344                  };
12345              }
12346  
12347              // Do we need to do something?
12348              if (va != t.selectedValue) {
12349                  // Find item
12350                  each(t.items, function(o, i) {
12351                      if (f(o.value)) {
12352                          fv = 1;
12353                          t.selectByIndex(i);
12354                          return false;
12355                      }
12356                  });
12357  
12358                  if (!fv)
12359                      t.selectByIndex(-1);
12360              }
12361          },
12362  
12363          selectByIndex : function(idx) {
12364              DOM.get(this.id).selectedIndex = idx + 1;
12365              this.selectedValue = this.items[idx] ? this.items[idx].value : null;
12366          },
12367  
12368          add : function(n, v, a) {
12369              var o, t = this;
12370  
12371              a = a || {};
12372              a.value = v;
12373  
12374              if (t.isRendered())
12375                  DOM.add(DOM.get(this.id), 'option', a, n);
12376  
12377              o = {
12378                  title : n,
12379                  value : v,
12380                  attribs : a
12381              };
12382  
12383              t.items.push(o);
12384              t.onAdd.dispatch(t, o);
12385          },
12386  
12387          getLength : function() {
12388              return this.items.length;
12389          },
12390  
12391          renderHTML : function() {
12392              var h, t = this;
12393  
12394              h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
12395  
12396              each(t.items, function(it) {
12397                  h += DOM.createHTML('option', {value : it.value}, it.title);
12398              });
12399  
12400              h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
12401              h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
12402              return h;
12403          },
12404  
12405          postRender : function() {
12406              var t = this, ch, changeListenerAdded = true;
12407  
12408              t.rendered = true;
12409  
12410  			function onChange(e) {
12411                  var v = t.items[e.target.selectedIndex - 1];
12412  
12413                  if (v && (v = v.value)) {
12414                      t.onChange.dispatch(t, v);
12415  
12416                      if (t.settings.onselect)
12417                          t.settings.onselect(v);
12418                  }
12419              };
12420  
12421              Event.add(t.id, 'change', onChange);
12422  
12423              // Accessibility keyhandler
12424              Event.add(t.id, 'keydown', function(e) {
12425                  var bf, DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
12426  
12427                  Event.remove(t.id, 'change', ch);
12428                  changeListenerAdded = false;
12429  
12430                  bf = Event.add(t.id, 'blur', function() {
12431                      if (changeListenerAdded) return;
12432                      changeListenerAdded = true;
12433                      Event.add(t.id, 'change', onChange);
12434                      Event.remove(t.id, 'blur', bf);
12435                  });
12436  
12437                  if (e.keyCode == DOM_VK_RETURN || e.keyCode == DOM_VK_SPACE) {
12438                      onChange(e);
12439                      return Event.cancel(e);
12440                  } else if (e.keyCode == DOM_VK_DOWN || e.keyCode == DOM_VK_UP) {
12441                      // allow native implementation (navigate select element options)
12442                      e.stopImmediatePropagation();
12443                  }
12444              });
12445  
12446              t.onPostRender.dispatch(t, DOM.get(t.id));
12447          }
12448      });
12449  })(tinymce);
12450  (function(tinymce) {
12451      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12452  
12453      tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
12454          MenuButton : function(id, s, ed) {
12455              this.parent(id, s, ed);
12456  
12457              this.onRenderMenu = new tinymce.util.Dispatcher(this);
12458  
12459              s.menu_container = s.menu_container || DOM.doc.body;
12460          },
12461  
12462          showMenu : function() {
12463              var t = this, p1, p2, e = DOM.get(t.id), m;
12464  
12465              if (t.isDisabled())
12466                  return;
12467  
12468              if (!t.isMenuRendered) {
12469                  t.renderMenu();
12470                  t.isMenuRendered = true;
12471              }
12472  
12473              if (t.isMenuVisible)
12474                  return t.hideMenu();
12475  
12476              p1 = DOM.getPos(t.settings.menu_container);
12477              p2 = DOM.getPos(e);
12478  
12479              m = t.menu;
12480              m.settings.offset_x = p2.x;
12481              m.settings.offset_y = p2.y;
12482              m.settings.vp_offset_x = p2.x;
12483              m.settings.vp_offset_y = p2.y;
12484              m.settings.keyboard_focus = t._focused;
12485              m.showMenu(0, e.firstChild.clientHeight);
12486  
12487              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12488              t.setState('Selected', 1);
12489  
12490              t.isMenuVisible = 1;
12491          },
12492  
12493          renderMenu : function() {
12494              var t = this, m;
12495  
12496              m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
12497                  menu_line : 1,
12498                  'class' : this.classPrefix + 'Menu',
12499                  icons : t.settings.icons
12500              });
12501  
12502              m.onHideMenu.add(function() {
12503                  t.hideMenu();
12504                  t.focus();
12505              });
12506  
12507              t.onRenderMenu.dispatch(t, m);
12508              t.menu = m;
12509          },
12510  
12511          hideMenu : function(e) {
12512              var t = this;
12513  
12514              // Prevent double toogles by canceling the mouse click event to the button
12515              if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
12516                  return;
12517  
12518              if (!e || !DOM.getParent(e.target, '.mceMenu')) {
12519                  t.setState('Selected', 0);
12520                  Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12521                  if (t.menu)
12522                      t.menu.hideMenu();
12523              }
12524  
12525              t.isMenuVisible = 0;
12526          },
12527  
12528          postRender : function() {
12529              var t = this, s = t.settings;
12530  
12531              Event.add(t.id, 'click', function() {
12532                  if (!t.isDisabled()) {
12533                      if (s.onclick)
12534                          s.onclick(t.value);
12535  
12536                      t.showMenu();
12537                  }
12538              });
12539          }
12540      });
12541  })(tinymce);
12542  (function(tinymce) {
12543      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
12544  
12545      tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
12546          SplitButton : function(id, s, ed) {
12547              this.parent(id, s, ed);
12548              this.classPrefix = 'mceSplitButton';
12549          },
12550  
12551          renderHTML : function() {
12552              var h, t = this, s = t.settings, h1;
12553  
12554              h = '<tbody><tr>';
12555  
12556              if (s.image)
12557                  h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
12558              else
12559                  h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
12560  
12561              h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
12562              h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12563      
12564              h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
12565              h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
12566  
12567              h += '</tr></tbody>';
12568              h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
12569              return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
12570          },
12571  
12572          postRender : function() {
12573              var t = this, s = t.settings, activate;
12574  
12575              if (s.onclick) {
12576                  activate = function(evt) {
12577                      if (!t.isDisabled()) {
12578                          s.onclick(t.value);
12579                          Event.cancel(evt);
12580                      }
12581                  };
12582                  Event.add(t.id + '_action', 'click', activate);
12583                  Event.add(t.id, ['click', 'keydown'], function(evt) {
12584                      var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
12585                      if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
12586                          activate();
12587                          Event.cancel(evt);
12588                      } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
12589                          t.showMenu();
12590                          Event.cancel(evt);
12591                      }
12592                  });
12593              }
12594  
12595              Event.add(t.id + '_open', 'click', function (evt) {
12596                  t.showMenu();
12597                  Event.cancel(evt);
12598              });
12599              Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
12600              Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
12601  
12602              // Old IE doesn't have hover on all elements
12603              if (tinymce.isIE6 || !DOM.boxModel) {
12604                  Event.add(t.id, 'mouseover', function() {
12605                      if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12606                          DOM.addClass(t.id, 'mceSplitButtonHover');
12607                  });
12608  
12609                  Event.add(t.id, 'mouseout', function() {
12610                      if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
12611                          DOM.removeClass(t.id, 'mceSplitButtonHover');
12612                  });
12613              }
12614          },
12615  
12616          destroy : function() {
12617              this.parent();
12618  
12619              Event.clear(this.id + '_action');
12620              Event.clear(this.id + '_open');
12621              Event.clear(this.id);
12622          }
12623      });
12624  })(tinymce);
12625  (function(tinymce) {
12626      var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
12627  
12628      tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
12629          ColorSplitButton : function(id, s, ed) {
12630              var t = this;
12631  
12632              t.parent(id, s, ed);
12633  
12634              t.settings = s = tinymce.extend({
12635                  colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
12636                  grid_width : 8,
12637                  default_color : '#888888'
12638              }, t.settings);
12639  
12640              t.onShowMenu = new tinymce.util.Dispatcher(t);
12641  
12642              t.onHideMenu = new tinymce.util.Dispatcher(t);
12643  
12644              t.value = s.default_color;
12645          },
12646  
12647          showMenu : function() {
12648              var t = this, r, p, e, p2;
12649  
12650              if (t.isDisabled())
12651                  return;
12652  
12653              if (!t.isMenuRendered) {
12654                  t.renderMenu();
12655                  t.isMenuRendered = true;
12656              }
12657  
12658              if (t.isMenuVisible)
12659                  return t.hideMenu();
12660  
12661              e = DOM.get(t.id);
12662              DOM.show(t.id + '_menu');
12663              DOM.addClass(e, 'mceSplitButtonSelected');
12664              p2 = DOM.getPos(e);
12665              DOM.setStyles(t.id + '_menu', {
12666                  left : p2.x,
12667                  top : p2.y + e.firstChild.clientHeight,
12668                  zIndex : 200000
12669              });
12670              e = 0;
12671  
12672              Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
12673              t.onShowMenu.dispatch(t);
12674  
12675              if (t._focused) {
12676                  t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
12677                      if (e.keyCode == 27)
12678                          t.hideMenu();
12679                  });
12680  
12681                  DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
12682              }
12683  
12684              t.keyboardNav = new tinymce.ui.KeyboardNavigation({
12685                  root: t.id + '_menu',
12686                  items: DOM.select('a', t.id + '_menu'),
12687                  onCancel: function() {
12688                      t.hideMenu();
12689                      t.focus();
12690                  }
12691              });
12692  
12693              t.keyboardNav.focus();
12694              t.isMenuVisible = 1;
12695          },
12696  
12697          hideMenu : function(e) {
12698              var t = this;
12699  
12700              if (t.isMenuVisible) {
12701                  // Prevent double toogles by canceling the mouse click event to the button
12702                  if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
12703                      return;
12704  
12705                  if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
12706                      DOM.removeClass(t.id, 'mceSplitButtonSelected');
12707                      Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
12708                      Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
12709                      DOM.hide(t.id + '_menu');
12710                  }
12711  
12712                  t.isMenuVisible = 0;
12713                  t.onHideMenu.dispatch();
12714                  t.keyboardNav.destroy();
12715              }
12716          },
12717  
12718          renderMenu : function() {
12719              var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
12720  
12721              w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
12722              m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
12723              DOM.add(m, 'span', {'class' : 'mceMenuLine'});
12724  
12725              n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
12726              tb = DOM.add(n, 'tbody');
12727  
12728              // Generate color grid
12729              i = 0;
12730              each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
12731                  c = c.replace(/^#/, '');
12732  
12733                  if (!i--) {
12734                      tr = DOM.add(tb, 'tr');
12735                      i = s.grid_width - 1;
12736                  }
12737  
12738                  n = DOM.add(tr, 'td');
12739                  var settings = {
12740                      href : 'javascript:;',
12741                      style : {
12742                          backgroundColor : '#' + c
12743                      },
12744                      'title': t.editor.getLang('colors.' + c, c),
12745                      'data-mce-color' : '#' + c
12746                  };
12747  
12748                  // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
12749                  if (!tinymce.isIE ) {
12750                      settings.role = 'option';
12751                  }
12752  
12753                  n = DOM.add(n, 'a', settings);
12754  
12755                  if (t.editor.forcedHighContrastMode) {
12756                      n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
12757                      if (n.getContext && (context = n.getContext("2d"))) {
12758                          context.fillStyle = '#' + c;
12759                          context.fillRect(0, 0, 16, 16);
12760                      } else {
12761                          // No point leaving a canvas element around if it's not supported for drawing on anyway.
12762                          DOM.remove(n);
12763                      }
12764                  }
12765              });
12766  
12767              if (s.more_colors_func) {
12768                  n = DOM.add(tb, 'tr');
12769                  n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
12770                  n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
12771  
12772                  Event.add(n, 'click', function(e) {
12773                      s.more_colors_func.call(s.more_colors_scope || this);
12774                      return Event.cancel(e); // Cancel to fix onbeforeunload problem
12775                  });
12776              }
12777  
12778              DOM.addClass(m, 'mceColorSplitMenu');
12779  
12780              // Prevent IE from scrolling and hindering click to occur #4019
12781              Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
12782  
12783              Event.add(t.id + '_menu', 'click', function(e) {
12784                  var c;
12785  
12786                  e = DOM.getParent(e.target, 'a', tb);
12787  
12788                  if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
12789                      t.setColor(c);
12790  
12791                  return false; // Prevent IE auto save warning
12792              });
12793  
12794              return w;
12795          },
12796  
12797          setColor : function(c) {
12798              this.displayColor(c);
12799              this.hideMenu();
12800              this.settings.onselect(c);
12801          },
12802          
12803          displayColor : function(c) {
12804              var t = this;
12805  
12806              DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
12807  
12808              t.value = c;
12809          },
12810  
12811          postRender : function() {
12812              var t = this, id = t.id;
12813  
12814              t.parent();
12815              DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
12816              DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
12817          },
12818  
12819          destroy : function() {
12820              var self = this;
12821  
12822              self.parent();
12823  
12824              Event.clear(self.id + '_menu');
12825              Event.clear(self.id + '_more');
12826              DOM.remove(self.id + '_menu');
12827  
12828              if (self.keyboardNav) {
12829                  self.keyboardNav.destroy();
12830              }
12831          }
12832      });
12833  })(tinymce);
12834  (function(tinymce) {
12835  // Shorten class names
12836  var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
12837  tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
12838      renderHTML : function() {
12839          var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
12840  
12841          h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
12842          //TODO: ACC test this out - adding a role = application for getting the landmarks working well.
12843          h.push("<span role='application'>");
12844          h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
12845          each(controls, function(toolbar) {
12846              h.push(toolbar.renderHTML());
12847          });
12848          h.push("</span>");
12849          h.push('</div>');
12850  
12851          return h.join('');
12852      },
12853      
12854      focus : function() {
12855          var t = this;
12856          dom.get(t.id).focus();
12857      },
12858      
12859      postRender : function() {
12860          var t = this, items = [];
12861  
12862          each(t.controls, function(toolbar) {
12863              each (toolbar.controls, function(control) {
12864                  if (control.id) {
12865                      items.push(control);
12866                  }
12867              });
12868          });
12869  
12870          t.keyNav = new tinymce.ui.KeyboardNavigation({
12871              root: t.id,
12872              items: items,
12873              onCancel: function() {
12874                  //Move focus if webkit so that navigation back will read the item.
12875                  if (tinymce.isWebKit) {
12876                      dom.get(t.editor.id+"_ifr").focus();
12877                  }
12878                  t.editor.focus();
12879              },
12880              excludeFromTabOrder: !t.settings.tab_focus_toolbar
12881          });
12882      },
12883      
12884      destroy : function() {
12885          var self = this;
12886  
12887          self.parent();
12888          self.keyNav.destroy();
12889          Event.clear(self.id);
12890      }
12891  });
12892  })(tinymce);
12893  (function(tinymce) {
12894  // Shorten class names
12895  var dom = tinymce.DOM, each = tinymce.each;
12896  tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
12897      renderHTML : function() {
12898          var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
12899  
12900          cl = t.controls;
12901          for (i=0; i<cl.length; i++) {
12902              // Get current control, prev control, next control and if the control is a list box or not
12903              co = cl[i];
12904              pr = cl[i - 1];
12905              nx = cl[i + 1];
12906  
12907              // Add toolbar start
12908              if (i === 0) {
12909                  c = 'mceToolbarStart';
12910  
12911                  if (co.Button)
12912                      c += ' mceToolbarStartButton';
12913                  else if (co.SplitButton)
12914                      c += ' mceToolbarStartSplitButton';
12915                  else if (co.ListBox)
12916                      c += ' mceToolbarStartListBox';
12917  
12918                  h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12919              }
12920  
12921              // Add toolbar end before list box and after the previous button
12922              // This is to fix the o2k7 editor skins
12923              if (pr && co.ListBox) {
12924                  if (pr.Button || pr.SplitButton)
12925                      h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
12926              }
12927  
12928              // Render control HTML
12929  
12930              // IE 8 quick fix, needed to propertly generate a hit area for anchors
12931              if (dom.stdMode)
12932                  h += '<td style="position: relative">' + co.renderHTML() + '</td>';
12933              else
12934                  h += '<td>' + co.renderHTML() + '</td>';
12935  
12936              // Add toolbar start after list box and before the next button
12937              // This is to fix the o2k7 editor skins
12938              if (nx && co.ListBox) {
12939                  if (nx.Button || nx.SplitButton)
12940                      h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
12941              }
12942          }
12943  
12944          c = 'mceToolbarEnd';
12945  
12946          if (co.Button)
12947              c += ' mceToolbarEndButton';
12948          else if (co.SplitButton)
12949              c += ' mceToolbarEndSplitButton';
12950          else if (co.ListBox)
12951              c += ' mceToolbarEndListBox';
12952  
12953          h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
12954  
12955          return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
12956      }
12957  });
12958  })(tinymce);
12959  (function(tinymce) {
12960      var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
12961  
12962      tinymce.create('tinymce.AddOnManager', {
12963          AddOnManager : function() {
12964              var self = this;
12965  
12966              self.items = [];
12967              self.urls = {};
12968              self.lookup = {};
12969              self.onAdd = new Dispatcher(self);
12970          },
12971  
12972          get : function(n) {
12973              if (this.lookup[n]) {
12974                  return this.lookup[n].instance;
12975              } else {
12976                  return undefined;
12977              }
12978          },
12979  
12980          dependencies : function(n) {
12981              var result;
12982              if (this.lookup[n]) {
12983                  result = this.lookup[n].dependencies;
12984              }
12985              return result || [];
12986          },
12987  
12988          requireLangPack : function(n) {
12989              var s = tinymce.settings;
12990  
12991              if (s && s.language && s.language_load !== false)
12992                  tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
12993          },
12994  
12995          add : function(id, o, dependencies) {
12996              this.items.push(o);
12997              this.lookup[id] = {instance:o, dependencies:dependencies};
12998              this.onAdd.dispatch(this, id, o);
12999  
13000              return o;
13001          },
13002          createUrl: function(baseUrl, dep) {
13003              if (typeof dep === "object") {
13004                  return dep
13005              } else {
13006                  return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
13007              }
13008          },
13009  
13010          addComponents: function(pluginName, scripts) {
13011              var pluginUrl = this.urls[pluginName];
13012              tinymce.each(scripts, function(script){
13013                  tinymce.ScriptLoader.add(pluginUrl+"/"+script);    
13014              });
13015          },
13016  
13017          load : function(n, u, cb, s) {
13018              var t = this, url = u;
13019  
13020  			function loadDependencies() {
13021                  var dependencies = t.dependencies(n);
13022                  tinymce.each(dependencies, function(dep) {
13023                      var newUrl = t.createUrl(u, dep);
13024                      t.load(newUrl.resource, newUrl, undefined, undefined);
13025                  });
13026                  if (cb) {
13027                      if (s) {
13028                          cb.call(s);
13029                      } else {
13030                          cb.call(tinymce.ScriptLoader);
13031                      }
13032                  }
13033              }
13034  
13035              if (t.urls[n])
13036                  return;
13037              if (typeof u === "object")
13038                  url = u.prefix + u.resource + u.suffix;
13039  
13040              if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
13041                  url = tinymce.baseURL + '/' + url;
13042  
13043              t.urls[n] = url.substring(0, url.lastIndexOf('/'));
13044  
13045              if (t.lookup[n]) {
13046                  loadDependencies();
13047              } else {
13048                  tinymce.ScriptLoader.add(url, loadDependencies, s);
13049              }
13050          }
13051      });
13052  
13053      // Create plugin and theme managers
13054      tinymce.PluginManager = new tinymce.AddOnManager();
13055      tinymce.ThemeManager = new tinymce.AddOnManager();
13056  }(tinymce));
13057  
13058  (function(tinymce) {
13059      // Shorten names
13060      var each = tinymce.each, extend = tinymce.extend,
13061          DOM = tinymce.DOM, Event = tinymce.dom.Event,
13062          ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13063          explode = tinymce.explode,
13064          Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
13065  
13066      // Setup some URLs where the editor API is located and where the document is
13067      tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
13068      if (!/[\/\\]$/.test(tinymce.documentBaseURL))
13069          tinymce.documentBaseURL += '/';
13070  
13071      tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
13072  
13073      tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
13074  
13075      // Add before unload listener
13076      // This was required since IE was leaking memory if you added and removed beforeunload listeners
13077      // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
13078      tinymce.onBeforeUnload = new Dispatcher(tinymce);
13079  
13080      // Must be on window or IE will leak if the editor is placed in frame or iframe
13081      Event.add(window, 'beforeunload', function(e) {
13082          tinymce.onBeforeUnload.dispatch(tinymce, e);
13083      });
13084  
13085      tinymce.onAddEditor = new Dispatcher(tinymce);
13086  
13087      tinymce.onRemoveEditor = new Dispatcher(tinymce);
13088  
13089      tinymce.EditorManager = extend(tinymce, {
13090          editors : [],
13091  
13092          i18n : {},
13093  
13094          activeEditor : null,
13095  
13096          init : function(s) {
13097              var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
13098  
13099  			function createId(elm) {
13100                  var id = elm.id;
13101      
13102                  // Use element id, or unique name or generate a unique id
13103                  if (!id) {
13104                      id = elm.name;
13105      
13106                      if (id && !DOM.get(id)) {
13107                          id = elm.name;
13108                      } else {
13109                          // Generate unique name
13110                          id = DOM.uniqueId();
13111                      }
13112  
13113                      elm.setAttribute('id', id);
13114                  }
13115  
13116                  return id;
13117              };
13118  
13119  			function execCallback(se, n, s) {
13120                  var f = se[n];
13121  
13122                  if (!f)
13123                      return;
13124  
13125                  if (tinymce.is(f, 'string')) {
13126                      s = f.replace(/\.\w+$/, '');
13127                      s = s ? tinymce.resolve(s) : 0;
13128                      f = tinymce.resolve(f);
13129                  }
13130  
13131                  return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
13132              };
13133  
13134  			function hasClass(n, c) {
13135                  return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
13136              };
13137  
13138              t.settings = s;
13139  
13140              // Legacy call
13141              Event.bind(window, 'ready', function() {
13142                  var l, co;
13143  
13144                  execCallback(s, 'onpageload');
13145  
13146                  switch (s.mode) {
13147                      case "exact":
13148                          l = s.elements || '';
13149  
13150                          if(l.length > 0) {
13151                              each(explode(l), function(v) {
13152                                  if (DOM.get(v)) {
13153                                      ed = new tinymce.Editor(v, s);
13154                                      el.push(ed);
13155                                      ed.render(1);
13156                                  } else {
13157                                      each(document.forms, function(f) {
13158                                          each(f.elements, function(e) {
13159                                              if (e.name === v) {
13160                                                  v = 'mce_editor_' + instanceCounter++;
13161                                                  DOM.setAttrib(e, 'id', v);
13162  
13163                                                  ed = new tinymce.Editor(v, s);
13164                                                  el.push(ed);
13165                                                  ed.render(1);
13166                                              }
13167                                          });
13168                                      });
13169                                  }
13170                              });
13171                          }
13172                          break;
13173  
13174                      case "textareas":
13175                      case "specific_textareas":
13176                          each(DOM.select('textarea'), function(elm) {
13177                              if (s.editor_deselector && hasClass(elm, s.editor_deselector))
13178                                  return;
13179  
13180                              if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
13181                                  ed = new tinymce.Editor(createId(elm), s);
13182                                  el.push(ed);
13183                                  ed.render(1);
13184                              }
13185                          });
13186                          break;
13187                      
13188                      default:
13189                          if (s.types) {
13190                              // Process type specific selector
13191                              each(s.types, function(type) {
13192                                  each(DOM.select(type.selector), function(elm) {
13193                                      var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
13194                                      el.push(editor);
13195                                      editor.render(1);
13196                                  });
13197                              });
13198                          } else if (s.selector) {
13199                              // Process global selector
13200                              each(DOM.select(s.selector), function(elm) {
13201                                  var editor = new tinymce.Editor(createId(elm), s);
13202                                  el.push(editor);
13203                                  editor.render(1);
13204                              });
13205                          }
13206                  }
13207  
13208                  // Call onInit when all editors are initialized
13209                  if (s.oninit) {
13210                      l = co = 0;
13211  
13212                      each(el, function(ed) {
13213                          co++;
13214  
13215                          if (!ed.initialized) {
13216                              // Wait for it
13217                              ed.onInit.add(function() {
13218                                  l++;
13219  
13220                                  // All done
13221                                  if (l == co)
13222                                      execCallback(s, 'oninit');
13223                              });
13224                          } else
13225                              l++;
13226  
13227                          // All done
13228                          if (l == co)
13229                              execCallback(s, 'oninit');                    
13230                      });
13231                  }
13232              });
13233          },
13234  
13235          get : function(id) {
13236              if (id === undef)
13237                  return this.editors;
13238  
13239              if (!this.editors.hasOwnProperty(id))
13240                  return undef;
13241  
13242              return this.editors[id];
13243          },
13244  
13245          getInstanceById : function(id) {
13246              return this.get(id);
13247          },
13248  
13249          add : function(editor) {
13250              var self = this, editors = self.editors;
13251  
13252              // Add named and index editor instance
13253              editors[editor.id] = editor;
13254              editors.push(editor);
13255  
13256              self._setActive(editor);
13257              self.onAddEditor.dispatch(self, editor);
13258  
13259  
13260              return editor;
13261          },
13262  
13263          remove : function(editor) {
13264              var t = this, i, editors = t.editors;
13265  
13266              // Not in the collection
13267              if (!editors[editor.id])
13268                  return null;
13269  
13270              delete editors[editor.id];
13271  
13272              for (i = 0; i < editors.length; i++) {
13273                  if (editors[i] == editor) {
13274                      editors.splice(i, 1);
13275                      break;
13276                  }
13277              }
13278  
13279              // Select another editor since the active one was removed
13280              if (t.activeEditor == editor)
13281                  t._setActive(editors[0]);
13282  
13283              editor.destroy();
13284              t.onRemoveEditor.dispatch(t, editor);
13285  
13286              return editor;
13287          },
13288  
13289          execCommand : function(c, u, v) {
13290              var t = this, ed = t.get(v), w;
13291  
13292  			function clr() {
13293                  ed.destroy();
13294                  w.detachEvent('onunload', clr);
13295                  w = w.tinyMCE = w.tinymce = null; // IE leak
13296              };
13297  
13298              // Manager commands
13299              switch (c) {
13300                  case "mceFocus":
13301                      ed.focus();
13302                      return true;
13303  
13304                  case "mceAddEditor":
13305                  case "mceAddControl":
13306                      if (!t.get(v))
13307                          new tinymce.Editor(v, t.settings).render();
13308  
13309                      return true;
13310  
13311                  case "mceAddFrameControl":
13312                      w = v.window;
13313  
13314                      // Add tinyMCE global instance and tinymce namespace to specified window
13315                      w.tinyMCE = tinyMCE;
13316                      w.tinymce = tinymce;
13317  
13318                      tinymce.DOM.doc = w.document;
13319                      tinymce.DOM.win = w;
13320  
13321                      ed = new tinymce.Editor(v.element_id, v);
13322                      ed.render();
13323  
13324                      // Fix IE memory leaks
13325                      if (tinymce.isIE && ! tinymce.isIE11) {
13326                          w.attachEvent('onunload', clr);
13327                      }
13328  
13329                      v.page_window = null;
13330  
13331                      return true;
13332  
13333                  case "mceRemoveEditor":
13334                  case "mceRemoveControl":
13335                      if (ed)
13336                          ed.remove();
13337  
13338                      return true;
13339  
13340                  case 'mceToggleEditor':
13341                      if (!ed) {
13342                          t.execCommand('mceAddControl', 0, v);
13343                          return true;
13344                      }
13345  
13346                      if (ed.isHidden())
13347                          ed.show();
13348                      else
13349                          ed.hide();
13350  
13351                      return true;
13352              }
13353  
13354              // Run command on active editor
13355              if (t.activeEditor)
13356                  return t.activeEditor.execCommand(c, u, v);
13357  
13358              return false;
13359          },
13360  
13361          execInstanceCommand : function(id, c, u, v) {
13362              var ed = this.get(id);
13363  
13364              if (ed)
13365                  return ed.execCommand(c, u, v);
13366  
13367              return false;
13368          },
13369  
13370          triggerSave : function() {
13371              each(this.editors, function(e) {
13372                  e.save();
13373              });
13374          },
13375  
13376          addI18n : function(p, o) {
13377              var lo, i18n = this.i18n;
13378  
13379              if (!tinymce.is(p, 'string')) {
13380                  each(p, function(o, lc) {
13381                      each(o, function(o, g) {
13382                          each(o, function(o, k) {
13383                              if (g === 'common')
13384                                  i18n[lc + '.' + k] = o;
13385                              else
13386                                  i18n[lc + '.' + g + '.' + k] = o;
13387                          });
13388                      });
13389                  });
13390              } else {
13391                  each(o, function(o, k) {
13392                      i18n[p + '.' + k] = o;
13393                  });
13394              }
13395          },
13396  
13397          // Private methods
13398  
13399          _setActive : function(editor) {
13400              this.selectedInstance = this.activeEditor = editor;
13401          }
13402      });
13403  })(tinymce);
13404  
13405  (function(tinymce) {
13406      // Shorten these names
13407      var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
13408          each = tinymce.each, isGecko = tinymce.isGecko,
13409          isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
13410          ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
13411          explode = tinymce.explode;
13412  
13413      tinymce.create('tinymce.Editor', {
13414          Editor : function(id, settings) {
13415              var self = this, TRUE = true;
13416  
13417              self.settings = settings = extend({
13418                  id : id,
13419                  language : 'en',
13420                  theme : 'advanced',
13421                  skin : 'default',
13422                  delta_width : 0,
13423                  delta_height : 0,
13424                  popup_css : '',
13425                  plugins : '',
13426                  document_base_url : tinymce.documentBaseURL,
13427                  add_form_submit_trigger : TRUE,
13428                  submit_patch : TRUE,
13429                  add_unload_trigger : TRUE,
13430                  convert_urls : TRUE,
13431                  relative_urls : TRUE,
13432                  remove_script_host : TRUE,
13433                  table_inline_editing : false,
13434                  object_resizing : TRUE,
13435                  accessibility_focus : TRUE,
13436                  doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
13437                  visual : TRUE,
13438                  font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
13439                  font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
13440                  apply_source_formatting : TRUE,
13441                  directionality : 'ltr',
13442                  forced_root_block : 'p',
13443                  hidden_input : TRUE,
13444                  padd_empty_editor : TRUE,
13445                  render_ui : TRUE,
13446                  indentation : '30px',
13447                  fix_table_elements : TRUE,
13448                  inline_styles : TRUE,
13449                  convert_fonts_to_spans : TRUE,
13450                  indent : 'simple',
13451                  indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13452                  indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
13453                  validate : TRUE,
13454                  entity_encoding : 'named',
13455                  url_converter : self.convertURL,
13456                  url_converter_scope : self,
13457                  ie7_compat : TRUE
13458              }, settings);
13459  
13460              self.id = self.editorId = id;
13461  
13462              self.isNotDirty = false;
13463  
13464              self.plugins = {};
13465  
13466              self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
13467                  base_uri : tinyMCE.baseURI
13468              });
13469  
13470              self.baseURI = tinymce.baseURI;
13471  
13472              self.contentCSS = [];
13473  
13474              self.contentStyles = [];
13475  
13476              // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
13477              self.setupEvents();
13478  
13479              // Internal command handler objects
13480              self.execCommands = {};
13481              self.queryStateCommands = {};
13482              self.queryValueCommands = {};
13483  
13484              // Call setup
13485              self.execCallback('setup', self);
13486          },
13487  
13488          render : function(nst) {
13489              var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
13490  
13491              // Page is not loaded yet, wait for it
13492              if (!Event.domLoaded) {
13493                  Event.add(window, 'ready', function() {
13494                      t.render();
13495                  });
13496                  return;
13497              }
13498  
13499              tinyMCE.settings = s;
13500  
13501              // Element not found, then skip initialization
13502              if (!t.getElement())
13503                  return;
13504  
13505              // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
13506              // here since the browser says it has contentEditable support but there is no visible caret.
13507              if (tinymce.isIDevice && !tinymce.isIOS5)
13508                  return;
13509  
13510              // Add hidden input for non input elements inside form elements
13511              if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
13512                  DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
13513  
13514              // Hide target element early to prevent content flashing
13515              if (!s.content_editable) {
13516                  t.orgVisibility = t.getElement().style.visibility;
13517                  t.getElement().style.visibility = 'hidden';
13518              }
13519  
13520              if (tinymce.WindowManager)
13521                  t.windowManager = new tinymce.WindowManager(t);
13522  
13523              if (s.encoding == 'xml') {
13524                  t.onGetContent.add(function(ed, o) {
13525                      if (o.save)
13526                          o.content = DOM.encode(o.content);
13527                  });
13528              }
13529  
13530              if (s.add_form_submit_trigger) {
13531                  t.onSubmit.addToTop(function() {
13532                      if (t.initialized) {
13533                          t.save();
13534                          t.isNotDirty = 1;
13535                      }
13536                  });
13537              }
13538  
13539              if (s.add_unload_trigger) {
13540                  t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
13541                      if (t.initialized && !t.destroyed && !t.isHidden())
13542                          t.save({format : 'raw', no_events : true});
13543                  });
13544              }
13545  
13546              tinymce.addUnload(t.destroy, t);
13547  
13548              if (s.submit_patch) {
13549                  t.onBeforeRenderUI.add(function() {
13550                      var n = t.getElement().form;
13551  
13552                      if (!n)
13553                          return;
13554  
13555                      // Already patched
13556                      if (n._mceOldSubmit)
13557                          return;
13558  
13559                      // Check page uses id="submit" or name="submit" for it's submit button
13560                      if (!n.submit.nodeType && !n.submit.length) {
13561                          t.formElement = n;
13562                          n._mceOldSubmit = n.submit;
13563                          n.submit = function() {
13564                              // Save all instances
13565                              tinymce.triggerSave();
13566                              t.isNotDirty = 1;
13567  
13568                              return t.formElement._mceOldSubmit(t.formElement);
13569                          };
13570                      }
13571  
13572                      n = null;
13573                  });
13574              }
13575  
13576              // Load scripts
13577  			function loadScripts() {
13578                  if (s.language && s.language_load !== false)
13579                      sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
13580  
13581                  if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
13582                      ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
13583  
13584                  each(explode(s.plugins), function(p) {
13585                      if (p &&!PluginManager.urls[p]) {
13586                          if (p.charAt(0) == '-') {
13587                              p = p.substr(1, p.length);
13588                              var dependencies = PluginManager.dependencies(p);
13589                              each(dependencies, function(dep) {
13590                                  var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
13591                                  dep = PluginManager.createUrl(defaultSettings, dep);
13592                                  PluginManager.load(dep.resource, dep);
13593                              });
13594                          } else {
13595                              // Skip safari plugin, since it is removed as of 3.3b1
13596                              if (p == 'safari') {
13597                                  return;
13598                              }
13599                              PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
13600                          }
13601                      }
13602                  });
13603  
13604                  // Init when que is loaded
13605                  sl.loadQueue(function() {
13606                      if (!t.removed)
13607                          t.init();
13608                  });
13609              };
13610  
13611              loadScripts();
13612          },
13613  
13614          init : function() {
13615              var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
13616  
13617              tinymce.add(t);
13618  
13619              s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
13620  
13621              if (s.theme) {
13622                  if (typeof s.theme != "function") {
13623                      s.theme = s.theme.replace(/-/, '');
13624                      o = ThemeManager.get(s.theme);
13625                      t.theme = new o();
13626  
13627                      if (t.theme.init)
13628                          t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
13629                  } else {
13630                      t.theme = s.theme;
13631                  }
13632              }
13633  
13634  			function initPlugin(p) {
13635                  var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
13636                  if (c && tinymce.inArray(initializedPlugins,p) === -1) {
13637                      each(PluginManager.dependencies(p), function(dep){
13638                          initPlugin(dep);
13639                      });
13640                      po = new c(t, u);
13641  
13642                      t.plugins[p] = po;
13643  
13644                      if (po.init) {
13645                          po.init(t, u);
13646                          initializedPlugins.push(p);
13647                      }
13648                  }
13649              }
13650              
13651              // Create all plugins
13652              each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
13653  
13654              // Setup popup CSS path(s)
13655              if (s.popup_css !== false) {
13656                  if (s.popup_css)
13657                      s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
13658                  else
13659                      s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
13660              }
13661  
13662              if (s.popup_css_add)
13663                  s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
13664  
13665              t.controlManager = new tinymce.ControlManager(t);
13666  
13667              // Enables users to override the control factory
13668              t.onBeforeRenderUI.dispatch(t, t.controlManager);
13669  
13670              // Measure box
13671              if (s.render_ui && t.theme) {
13672                  t.orgDisplay = e.style.display;
13673  
13674                  if (typeof s.theme != "function") {
13675                      w = s.width || e.style.width || e.offsetWidth;
13676                      h = s.height || e.style.height || e.offsetHeight;
13677                      mh = s.min_height || 100;
13678                      re = /^[0-9\.]+(|px)$/i;
13679  
13680                      if (re.test('' + w))
13681                          w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
13682  
13683                      if (re.test('' + h))
13684                          h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
13685  
13686                      // Render UI
13687                      o = t.theme.renderUI({
13688                          targetNode : e,
13689                          width : w,
13690                          height : h,
13691                          deltaWidth : s.delta_width,
13692                          deltaHeight : s.delta_height
13693                      });
13694  
13695                      // Resize editor
13696                      DOM.setStyles(o.sizeContainer || o.editorContainer, {
13697                          width : w,
13698                          height : h
13699                      });
13700  
13701                      h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
13702                      if (h < mh)
13703                          h = mh;
13704                  } else {
13705                      o = s.theme(t, e);
13706  
13707                      // Convert element type to id:s
13708                      if (o.editorContainer.nodeType) {
13709                          o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
13710                      }
13711  
13712                      // Convert element type to id:s
13713                      if (o.iframeContainer.nodeType) {
13714                          o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
13715                      }
13716  
13717                      // Use specified iframe height or the targets offsetHeight
13718                      h = o.iframeHeight || e.offsetHeight;
13719  
13720                      // Store away the selection when it's changed to it can be restored later with a editor.focus() call
13721                      if (isIE) {
13722                          t.onInit.add(function(ed) {
13723                              ed.dom.bind(ed.getBody(), 'beforedeactivate keydown keyup', function() {
13724                                  ed.bookmark = ed.selection.getBookmark(1);
13725                              });
13726                          });
13727  
13728                          t.onNodeChange.add(function(ed) {
13729                              if (document.activeElement.id == ed.id + "_ifr") {
13730                                  ed.bookmark = ed.selection.getBookmark(1);
13731                              }
13732                          });
13733                      }
13734                  }
13735  
13736                  t.editorContainer = o.editorContainer;
13737              }
13738  
13739              // Load specified content CSS last
13740              if (s.content_css) {
13741                  each(explode(s.content_css), function(u) {
13742                      t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
13743                  });
13744              }
13745  
13746              // Load specified content CSS last
13747              if (s.content_style) {
13748                  t.contentStyles.push(s.content_style);
13749              }
13750  
13751              // Content editable mode ends here
13752              if (s.content_editable) {
13753                  e = n = o = null; // Fix IE leak
13754                  return t.initContentBody();
13755              }
13756  
13757              // User specified a document.domain value
13758              if (document.domain && location.hostname != document.domain)
13759                  tinymce.relaxedDomain = document.domain;
13760  
13761              t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
13762  
13763              // We only need to override paths if we have to
13764              // IE has a bug where it remove site absolute urls to relative ones if this is specified
13765              if (s.document_base_url != tinymce.documentBaseURL)
13766                  t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
13767  
13768              // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
13769              if (tinymce.isIE8) {
13770                  if (s.ie7_compat)
13771                      t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
13772                  else
13773                      t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
13774              }
13775  
13776              t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
13777  
13778              // Load the CSS by injecting them into the HTML this will reduce "flicker"
13779              for (i = 0; i < t.contentCSS.length; i++) {
13780                  t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
13781              }
13782  
13783              t.contentCSS = [];
13784  
13785              bi = s.body_id || 'tinymce';
13786              if (bi.indexOf('=') != -1) {
13787                  bi = t.getParam('body_id', '', 'hash');
13788                  bi = bi[t.id] || bi;
13789              }
13790  
13791              bc = s.body_class || '';
13792              if (bc.indexOf('=') != -1) {
13793                  bc = t.getParam('body_class', '', 'hash');
13794                  bc = bc[t.id] || '';
13795              }
13796  
13797              t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
13798  
13799              // Domain relaxing enabled, then set document domain
13800              if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
13801                  // We need to write the contents here in IE since multiple writes messes up refresh button and back button
13802                  u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
13803              }
13804  
13805              // Create iframe
13806              // TODO: ACC add the appropriate description on this.
13807              n = DOM.add(o.iframeContainer, 'iframe', { 
13808                  id : t.id + "_ifr",
13809                  src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
13810                  frameBorder : '0',
13811                  allowTransparency : "true",
13812                  title : s.aria_label,
13813                  style : {
13814                      width : '100%',
13815                      height : h,
13816                      display : 'block' // Important for Gecko to render the iframe correctly
13817                  }
13818              });
13819  
13820              t.contentAreaContainer = o.iframeContainer;
13821  
13822              if (o.editorContainer) {
13823                  DOM.get(o.editorContainer).style.display = t.orgDisplay;
13824              }
13825  
13826              // Restore visibility on target element
13827              e.style.visibility = t.orgVisibility;
13828  
13829              DOM.get(t.id).style.display = 'none';
13830              DOM.setAttrib(t.id, 'aria-hidden', true);
13831  
13832              if (!tinymce.relaxedDomain || !u)
13833                  t.initContentBody();
13834  
13835              e = n = o = null; // Cleanup
13836          },
13837  
13838          initContentBody : function() {
13839              var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
13840  
13841              // Setup iframe body
13842              if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
13843                  doc.open();
13844                  doc.write(self.iframeHTML);
13845                  doc.close();
13846  
13847                  if (tinymce.relaxedDomain)
13848                      doc.domain = tinymce.relaxedDomain;
13849              }
13850  
13851              if (settings.content_editable) {
13852                  DOM.addClass(targetElm, 'mceContentBody');
13853                  self.contentDocument = doc = settings.content_document || document;
13854                  self.contentWindow = settings.content_window || window;
13855                  self.bodyElement = targetElm;
13856  
13857                  // Prevent leak in IE
13858                  settings.content_document = settings.content_window = null;
13859              }
13860  
13861              // It will not steal focus while setting contentEditable
13862              body = self.getBody();
13863              body.disabled = true;
13864  
13865              if (!settings.readonly)
13866                  body.contentEditable = self.getParam('content_editable_state', true);
13867  
13868              body.disabled = false;
13869  
13870              self.schema = new tinymce.html.Schema(settings);
13871  
13872              self.dom = new tinymce.dom.DOMUtils(doc, {
13873                  keep_values : true,
13874                  url_converter : self.convertURL,
13875                  url_converter_scope : self,
13876                  hex_colors : settings.force_hex_style_colors,
13877                  class_filter : settings.class_filter,
13878                  update_styles : true,
13879                  root_element : settings.content_editable ? self.id : null,
13880                  schema : self.schema
13881              });
13882  
13883              self.parser = new tinymce.html.DomParser(settings, self.schema);
13884  
13885              // Convert src and href into data-mce-src, data-mce-href and data-mce-style
13886              self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
13887                  var i = nodes.length, node, dom = self.dom, value, internalName;
13888  
13889                  while (i--) {
13890                      node = nodes[i];
13891                      value = node.attr(name);
13892                      internalName = 'data-mce-' + name;
13893  
13894                      // Add internal attribute if we need to we don't on a refresh of the document
13895                      if (!node.attributes.map[internalName]) {    
13896                          if (name === "style")
13897                              node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
13898                          else
13899                              node.attr(internalName, self.convertURL(value, name, node.name));
13900                      }
13901                  }
13902              });
13903  
13904              // Keep scripts from executing
13905              self.parser.addNodeFilter('script', function(nodes, name) {
13906                  var i = nodes.length, node;
13907  
13908                  while (i--) {
13909                      node = nodes[i];
13910                      node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
13911                  }
13912              });
13913  
13914              self.parser.addNodeFilter('#cdata', function(nodes, name) {
13915                  var i = nodes.length, node;
13916  
13917                  while (i--) {
13918                      node = nodes[i];
13919                      node.type = 8;
13920                      node.name = '#comment';
13921                      node.value = '[CDATA[' + node.value + ']]';
13922                  }
13923              });
13924  
13925              self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
13926                  var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
13927  
13928                  while (i--) {
13929                      node = nodes[i];
13930  
13931                      if (node.isEmpty(nonEmptyElements))
13932                          node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
13933                  }
13934              });
13935  
13936              self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
13937  
13938              self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
13939  
13940              self.formatter = new tinymce.Formatter(self);
13941  
13942              self.undoManager = new tinymce.UndoManager(self);
13943  
13944              self.forceBlocks = new tinymce.ForceBlocks(self);
13945              self.enterKey = new tinymce.EnterKey(self);
13946              self.editorCommands = new tinymce.EditorCommands(self);
13947  
13948              self.onExecCommand.add(function(editor, command) {
13949                  // Don't refresh the select lists until caret move
13950                  if (!/^(FontName|FontSize)$/.test(command))
13951                      self.nodeChanged();
13952              });
13953  
13954              // Pass through
13955              self.serializer.onPreProcess.add(function(se, o) {
13956                  return self.onPreProcess.dispatch(self, o, se);
13957              });
13958  
13959              self.serializer.onPostProcess.add(function(se, o) {
13960                  return self.onPostProcess.dispatch(self, o, se);
13961              });
13962  
13963              self.onPreInit.dispatch(self);
13964  
13965              if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
13966                  doc.body.spellcheck = false;
13967  
13968              if (!settings.readonly) {
13969                  self.bindNativeEvents();
13970              }
13971  
13972              self.controlManager.onPostRender.dispatch(self, self.controlManager);
13973              self.onPostRender.dispatch(self);
13974  
13975              self.quirks = tinymce.util.Quirks(self);
13976  
13977              if (settings.directionality)
13978                  body.dir = settings.directionality;
13979  
13980              if (settings.nowrap)
13981                  body.style.whiteSpace = "nowrap";
13982  
13983              if (settings.protect) {
13984                  self.onBeforeSetContent.add(function(ed, o) {
13985                      each(settings.protect, function(pattern) {
13986                          o.content = o.content.replace(pattern, function(str) {
13987                              return '<!--mce:protected ' + escape(str) + '-->';
13988                          });
13989                      });
13990                  });
13991              }
13992  
13993              // Add visual aids when new contents is added
13994              self.onSetContent.add(function() {
13995                  self.addVisual(self.getBody());
13996              });
13997  
13998              // Remove empty contents
13999              if (settings.padd_empty_editor) {
14000                  self.onPostProcess.add(function(ed, o) {
14001                      o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
14002                  });
14003              }
14004  
14005              self.load({initial : true, format : 'html'});
14006              self.startContent = self.getContent({format : 'raw'});
14007  
14008              self.initialized = true;
14009  
14010              self.onInit.dispatch(self);
14011              self.execCallback('setupcontent_callback', self.id, body, doc);
14012              self.execCallback('init_instance_callback', self);
14013              self.focus(true);
14014              self.nodeChanged({initial : true});
14015  
14016              // Add editor specific CSS styles
14017              if (self.contentStyles.length > 0) {
14018                  contentCssText = '';
14019  
14020                  each(self.contentStyles, function(style) {
14021                      contentCssText += style + "\r\n";
14022                  });
14023  
14024                  self.dom.addStyle(contentCssText);
14025              }
14026  
14027              // Load specified content CSS last
14028              each(self.contentCSS, function(url) {
14029                  self.dom.loadCSS(url);
14030              });
14031  
14032              // Handle auto focus
14033              if (settings.auto_focus) {
14034                  setTimeout(function () {
14035                      var ed = tinymce.get(settings.auto_focus);
14036  
14037                      ed.selection.select(ed.getBody(), 1);
14038                      ed.selection.collapse(1);
14039                      ed.getBody().focus();
14040                      ed.getWin().focus();
14041                  }, 100);
14042              }
14043  
14044              // Clean up references for IE
14045              targetElm = doc = body = null;
14046          },
14047  
14048          focus : function(skip_focus) {
14049              var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
14050  
14051              if (!skip_focus) {
14052                  if (self.bookmark) {
14053                      selection.moveToBookmark(self.bookmark);
14054                      self.bookmark = null;
14055                  }
14056  
14057                  // Get selected control element
14058                  ieRng = selection.getRng();
14059                  if (ieRng.item) {
14060                      controlElm = ieRng.item(0);
14061                  }
14062  
14063                  self._refreshContentEditable();
14064  
14065                  // Focus the window iframe
14066                  if (!contentEditable) {
14067                      self.getWin().focus();
14068                  }
14069  
14070                  // Focus the body as well since it's contentEditable
14071                  if (tinymce.isGecko || contentEditable) {
14072                      body = self.getBody();
14073  
14074                      // Check for setActive since it doesn't scroll to the element
14075                      if (body.setActive && ! tinymce.isIE11) {
14076                          body.setActive();
14077                      } else {
14078                          body.focus();
14079                      }
14080  
14081                      if (contentEditable) {
14082                          selection.normalize();
14083                      }
14084                  }
14085  
14086                  // Restore selected control element
14087                  // This is needed when for example an image is selected within a
14088                  // layer a call to focus will then remove the control selection
14089                  if (controlElm && controlElm.ownerDocument == doc) {
14090                      ieRng = doc.body.createControlRange();
14091                      ieRng.addElement(controlElm);
14092                      ieRng.select();
14093                  }
14094              }
14095  
14096              if (tinymce.activeEditor != self) {
14097                  if ((oed = tinymce.activeEditor) != null)
14098                      oed.onDeactivate.dispatch(oed, self);
14099  
14100                  self.onActivate.dispatch(self, oed);
14101              }
14102  
14103              tinymce._setActive(self);
14104          },
14105  
14106          execCallback : function(n) {
14107              var t = this, f = t.settings[n], s;
14108  
14109              if (!f)
14110                  return;
14111  
14112              // Look through lookup
14113              if (t.callbackLookup && (s = t.callbackLookup[n])) {
14114                  f = s.func;
14115                  s = s.scope;
14116              }
14117  
14118              if (is(f, 'string')) {
14119                  s = f.replace(/\.\w+$/, '');
14120                  s = s ? tinymce.resolve(s) : 0;
14121                  f = tinymce.resolve(f);
14122                  t.callbackLookup = t.callbackLookup || {};
14123                  t.callbackLookup[n] = {func : f, scope : s};
14124              }
14125  
14126              return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
14127          },
14128  
14129          translate : function(s) {
14130              var c = this.settings.language || 'en', i18n = tinymce.i18n;
14131  
14132              if (!s)
14133                  return '';
14134  
14135              return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
14136                  return i18n[c + '.' + b] || '{#' + b + '}';
14137              });
14138          },
14139  
14140          getLang : function(n, dv) {
14141              return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
14142          },
14143  
14144          getParam : function(n, dv, ty) {
14145              var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
14146  
14147              if (ty === 'hash') {
14148                  o = {};
14149  
14150                  if (is(v, 'string')) {
14151                      each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
14152                          v = v.split('=');
14153  
14154                          if (v.length > 1)
14155                              o[tr(v[0])] = tr(v[1]);
14156                          else
14157                              o[tr(v[0])] = tr(v);
14158                      });
14159                  } else
14160                      o = v;
14161  
14162                  return o;
14163              }
14164  
14165              return v;
14166          },
14167  
14168          nodeChanged : function(o) {
14169              var self = this, selection = self.selection, node;
14170  
14171              // Fix for bug #1896577 it seems that this can not be fired while the editor is loading
14172              if (self.initialized) {
14173                  o = o || {};
14174  
14175                  // Get start node
14176                  node = selection.getStart() || self.getBody();
14177                  node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
14178  
14179                  // Get parents and add them to object
14180                  o.parents = [];
14181                  self.dom.getParent(node, function(node) {
14182                      if (node.nodeName == 'BODY')
14183                          return true;
14184  
14185                      o.parents.push(node);
14186                  });
14187  
14188                  self.onNodeChange.dispatch(
14189                      self,
14190                      o ? o.controlManager || self.controlManager : self.controlManager,
14191                      node,
14192                      selection.isCollapsed(),
14193                      o
14194                  );
14195              }
14196          },
14197  
14198          addButton : function(name, settings) {
14199              var self = this;
14200  
14201              self.buttons = self.buttons || {};
14202              self.buttons[name] = settings;
14203          },
14204  
14205          addCommand : function(name, callback, scope) {
14206              this.execCommands[name] = {func : callback, scope : scope || this};
14207          },
14208  
14209          addQueryStateHandler : function(name, callback, scope) {
14210              this.queryStateCommands[name] = {func : callback, scope : scope || this};
14211          },
14212  
14213          addQueryValueHandler : function(name, callback, scope) {
14214              this.queryValueCommands[name] = {func : callback, scope : scope || this};
14215          },
14216  
14217          addShortcut : function(pa, desc, cmd_func, sc) {
14218              var t = this, c;
14219  
14220              if (t.settings.custom_shortcuts === false)
14221                  return false;
14222  
14223              t.shortcuts = t.shortcuts || {};
14224  
14225              if (is(cmd_func, 'string')) {
14226                  c = cmd_func;
14227  
14228                  cmd_func = function() {
14229                      t.execCommand(c, false, null);
14230                  };
14231              }
14232  
14233              if (is(cmd_func, 'object')) {
14234                  c = cmd_func;
14235  
14236                  cmd_func = function() {
14237                      t.execCommand(c[0], c[1], c[2]);
14238                  };
14239              }
14240  
14241              each(explode(pa), function(pa) {
14242                  var o = {
14243                      func : cmd_func,
14244                      scope : sc || this,
14245                      desc : t.translate(desc),
14246                      alt : false,
14247                      ctrl : false,
14248                      shift : false
14249                  };
14250  
14251                  each(explode(pa, '+'), function(v) {
14252                      switch (v) {
14253                          case 'alt':
14254                          case 'ctrl':
14255                          case 'shift':
14256                              o[v] = true;
14257                              break;
14258  
14259                          default:
14260                              o.charCode = v.charCodeAt(0);
14261                              o.keyCode = v.toUpperCase().charCodeAt(0);
14262                      }
14263                  });
14264  
14265                  t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
14266              });
14267  
14268              return true;
14269          },
14270  
14271          execCommand : function(cmd, ui, val, a) {
14272              var t = this, s = 0, o, st;
14273  
14274              if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
14275                  t.focus();
14276  
14277              a = extend({}, a);
14278              t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
14279              if (a.terminate)
14280                  return false;
14281  
14282              // Command callback
14283              if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
14284                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14285                  return true;
14286              }
14287  
14288              // Registred commands
14289              if (o = t.execCommands[cmd]) {
14290                  st = o.func.call(o.scope, ui, val);
14291  
14292                  // Fall through on true
14293                  if (st !== true) {
14294                      t.onExecCommand.dispatch(t, cmd, ui, val, a);
14295                      return st;
14296                  }
14297              }
14298  
14299              // Plugin commands
14300              each(t.plugins, function(p) {
14301                  if (p.execCommand && p.execCommand(cmd, ui, val)) {
14302                      t.onExecCommand.dispatch(t, cmd, ui, val, a);
14303                      s = 1;
14304                      return false;
14305                  }
14306              });
14307  
14308              if (s)
14309                  return true;
14310  
14311              // Theme commands
14312              if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
14313                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14314                  return true;
14315              }
14316  
14317              // Editor commands
14318              if (t.editorCommands.execCommand(cmd, ui, val)) {
14319                  t.onExecCommand.dispatch(t, cmd, ui, val, a);
14320                  return true;
14321              }
14322  
14323              // Browser commands
14324              t.getDoc().execCommand(cmd, ui, val);
14325              t.onExecCommand.dispatch(t, cmd, ui, val, a);
14326          },
14327  
14328          queryCommandState : function(cmd) {
14329              var t = this, o, s;
14330  
14331              // Is hidden then return undefined
14332              if (t._isHidden())
14333                  return;
14334  
14335              // Registred commands
14336              if (o = t.queryStateCommands[cmd]) {
14337                  s = o.func.call(o.scope);
14338  
14339                  // Fall though on true
14340                  if (s !== true)
14341                      return s;
14342              }
14343  
14344              // Registred commands
14345              o = t.editorCommands.queryCommandState(cmd);
14346              if (o !== -1)
14347                  return o;
14348  
14349              // Browser commands
14350              try {
14351                  return this.getDoc().queryCommandState(cmd);
14352              } catch (ex) {
14353                  // Fails sometimes see bug: 1896577
14354              }
14355          },
14356  
14357          queryCommandValue : function(c) {
14358              var t = this, o, s;
14359  
14360              // Is hidden then return undefined
14361              if (t._isHidden())
14362                  return;
14363  
14364              // Registred commands
14365              if (o = t.queryValueCommands[c]) {
14366                  s = o.func.call(o.scope);
14367  
14368                  // Fall though on true
14369                  if (s !== true)
14370                      return s;
14371              }
14372  
14373              // Registred commands
14374              o = t.editorCommands.queryCommandValue(c);
14375              if (is(o))
14376                  return o;
14377  
14378              // Browser commands
14379              try {
14380                  return this.getDoc().queryCommandValue(c);
14381              } catch (ex) {
14382                  // Fails sometimes see bug: 1896577
14383              }
14384          },
14385  
14386          show : function() {
14387              var self = this;
14388  
14389              DOM.show(self.getContainer());
14390              DOM.hide(self.id);
14391              self.load();
14392          },
14393  
14394          hide : function() {
14395              var self = this, doc = self.getDoc();
14396  
14397              // Fixed bug where IE has a blinking cursor left from the editor
14398              if (isIE && doc)
14399                  doc.execCommand('SelectAll');
14400  
14401              // We must save before we hide so Safari doesn't crash
14402              self.save();
14403  
14404              // defer the call to hide to prevent an IE9 crash #4921
14405              DOM.hide(self.getContainer());
14406              DOM.setStyle(self.id, 'display', self.orgDisplay);
14407          },
14408  
14409          isHidden : function() {
14410              return !DOM.isHidden(this.id);
14411          },
14412  
14413          setProgressState : function(b, ti, o) {
14414              this.onSetProgressState.dispatch(this, b, ti, o);
14415  
14416              return b;
14417          },
14418  
14419          load : function(o) {
14420              var t = this, e = t.getElement(), h;
14421  
14422              if (e) {
14423                  o = o || {};
14424                  o.load = true;
14425  
14426                  // Double encode existing entities in the value
14427                  h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
14428                  o.element = e;
14429  
14430                  if (!o.no_events)
14431                      t.onLoadContent.dispatch(t, o);
14432  
14433                  o.element = e = null;
14434  
14435                  return h;
14436              }
14437          },
14438  
14439          save : function(o) {
14440              var t = this, e = t.getElement(), h, f;
14441  
14442              if (!e || !t.initialized)
14443                  return;
14444  
14445              o = o || {};
14446              o.save = true;
14447  
14448              o.element = e;
14449              h = o.content = t.getContent(o);
14450  
14451              if (!o.no_events)
14452                  t.onSaveContent.dispatch(t, o);
14453  
14454              h = o.content;
14455  
14456              if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
14457                  e.innerHTML = h;
14458  
14459                  // Update hidden form element
14460                  if (f = DOM.getParent(t.id, 'form')) {
14461                      each(f.elements, function(e) {
14462                          if (e.name == t.id) {
14463                              e.value = h;
14464                              return false;
14465                          }
14466                      });
14467                  }
14468              } else
14469                  e.value = h;
14470  
14471              o.element = e = null;
14472  
14473              return h;
14474          },
14475  
14476          setContent : function(content, args) {
14477              var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
14478  
14479              // Setup args object
14480              args = args || {};
14481              args.format = args.format || 'html';
14482              args.set = true;
14483              args.content = content;
14484  
14485              // Do preprocessing
14486              if (!args.no_events)
14487                  self.onBeforeSetContent.dispatch(self, args);
14488  
14489              content = args.content;
14490  
14491              // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
14492              // It will also be impossible to place the caret in the editor unless there is a BR element present
14493              if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
14494                  forcedRootBlockName = self.settings.forced_root_block;
14495                  if (forcedRootBlockName)
14496                      content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
14497                  else
14498                      content = '<br data-mce-bogus="1">';
14499  
14500                  body.innerHTML = content;
14501                  self.selection.select(body, true);
14502                  self.selection.collapse(true);
14503                  return;
14504              }
14505  
14506              // Parse and serialize the html
14507              if (args.format !== 'raw') {
14508                  content = new tinymce.html.Serializer({}, self.schema).serialize(
14509                      self.parser.parse(content)
14510                  );
14511              }
14512  
14513              // Set the new cleaned contents to the editor
14514              args.content = tinymce.trim(content);
14515              self.dom.setHTML(body, args.content);
14516  
14517              // Do post processing
14518              if (!args.no_events)
14519                  self.onSetContent.dispatch(self, args);
14520  
14521              // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
14522              if (!self.settings.content_editable || document.activeElement === self.getBody()) {
14523                  self.selection.normalize();
14524              }
14525  
14526              return args.content;
14527          },
14528  
14529          getContent : function(args) {
14530              var self = this, content, body = self.getBody();
14531  
14532              // Setup args object
14533              args = args || {};
14534              args.format = args.format || 'html';
14535              args.get = true;
14536              args.getInner = true;
14537  
14538              // Do preprocessing
14539              if (!args.no_events)
14540                  self.onBeforeGetContent.dispatch(self, args);
14541  
14542              // Get raw contents or by default the cleaned contents
14543              if (args.format == 'raw')
14544                  content = body.innerHTML;
14545              else if (args.format == 'text')
14546                  content = body.innerText || body.textContent;
14547              else
14548                  content = self.serializer.serialize(body, args);
14549  
14550              // Trim whitespace in beginning/end of HTML
14551              if (args.format != 'text') {
14552                  args.content = tinymce.trim(content);
14553              } else {
14554                  args.content = content;
14555              }
14556  
14557              // Do post processing
14558              if (!args.no_events)
14559                  self.onGetContent.dispatch(self, args);
14560  
14561              return args.content;
14562          },
14563  
14564          isDirty : function() {
14565              var self = this;
14566  
14567              return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
14568          },
14569  
14570          getContainer : function() {
14571              var self = this;
14572  
14573              if (!self.container)
14574                  self.container = DOM.get(self.editorContainer || self.id + '_parent');
14575  
14576              return self.container;
14577          },
14578  
14579          getContentAreaContainer : function() {
14580              return this.contentAreaContainer;
14581          },
14582  
14583          getElement : function() {
14584              return DOM.get(this.settings.content_element || this.id);
14585          },
14586  
14587          getWin : function() {
14588              var self = this, elm;
14589  
14590              if (!self.contentWindow) {
14591                  elm = DOM.get(self.id + "_ifr");
14592  
14593                  if (elm)
14594                      self.contentWindow = elm.contentWindow;
14595              }
14596  
14597              return self.contentWindow;
14598          },
14599  
14600          getDoc : function() {
14601              var self = this, win;
14602  
14603              if (!self.contentDocument) {
14604                  win = self.getWin();
14605  
14606                  if (win)
14607                      self.contentDocument = win.document;
14608              }
14609  
14610              return self.contentDocument;
14611          },
14612  
14613          getBody : function() {
14614              return this.bodyElement || this.getDoc().body;
14615          },
14616  
14617          convertURL : function(url, name, elm) {
14618              var self = this, settings = self.settings;
14619  
14620              // Use callback instead
14621              if (settings.urlconverter_callback)
14622                  return self.execCallback('urlconverter_callback', url, elm, true, name);
14623  
14624              // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
14625              if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
14626                  return url;
14627  
14628              // Convert to relative
14629              if (settings.relative_urls)
14630                  return self.documentBaseURI.toRelative(url);
14631  
14632              // Convert to absolute
14633              url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
14634  
14635              return url;
14636          },
14637  
14638          addVisual : function(elm) {
14639              var self = this, settings = self.settings, dom = self.dom, cls;
14640  
14641              elm = elm || self.getBody();
14642  
14643              if (!is(self.hasVisual))
14644                  self.hasVisual = settings.visual;
14645  
14646              each(dom.select('table,a', elm), function(elm) {
14647                  var value;
14648  
14649                  switch (elm.nodeName) {
14650                      case 'TABLE':
14651                          cls = settings.visual_table_class || 'mceItemTable';
14652                          value = dom.getAttrib(elm, 'border');
14653  
14654                          if (!value || value == '0') {
14655                              if (self.hasVisual)
14656                                  dom.addClass(elm, cls);
14657                              else
14658                                  dom.removeClass(elm, cls);
14659                          }
14660  
14661                          return;
14662  
14663                      case 'A':
14664                          if (!dom.getAttrib(elm, 'href', false)) {
14665                              value = dom.getAttrib(elm, 'name') || elm.id;
14666                              cls = 'mceItemAnchor';
14667  
14668                              if (value) {
14669                                  if (self.hasVisual)
14670                                      dom.addClass(elm, cls);
14671                                  else
14672                                      dom.removeClass(elm, cls);
14673                              }
14674                          }
14675  
14676                          return;
14677                  }
14678              });
14679  
14680              self.onVisualAid.dispatch(self, elm, self.hasVisual);
14681          },
14682  
14683          remove : function() {
14684              var self = this, elm = self.getContainer(), doc = self.getDoc();
14685  
14686              if (!self.removed) {
14687                  self.removed = 1; // Cancels post remove event execution
14688  
14689                  // Fixed bug where IE has a blinking cursor left from the editor
14690                  if (isIE && doc)
14691                      doc.execCommand('SelectAll');
14692  
14693                  // We must save before we hide so Safari doesn't crash
14694                  self.save();
14695  
14696                  DOM.setStyle(self.id, 'display', self.orgDisplay);
14697  
14698                  // Don't clear the window or document if content editable
14699                  // is enabled since other instances might still be present
14700                  if (!self.settings.content_editable) {
14701                      Event.unbind(self.getWin());
14702                      Event.unbind(self.getDoc());
14703                  }
14704  
14705                  Event.unbind(self.getBody());
14706                  Event.clear(elm);
14707  
14708                  self.execCallback('remove_instance_callback', self);
14709                  self.onRemove.dispatch(self);
14710  
14711                  // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
14712                  self.onExecCommand.listeners = [];
14713  
14714                  tinymce.remove(self);
14715                  DOM.remove(elm);
14716              }
14717          },
14718  
14719          destroy : function(s) {
14720              var t = this;
14721  
14722              // One time is enough
14723              if (t.destroyed)
14724                  return;
14725  
14726              // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
14727              if (isGecko) {
14728                  Event.unbind(t.getDoc());
14729                  Event.unbind(t.getWin());
14730                  Event.unbind(t.getBody());
14731              }
14732  
14733              if (!s) {
14734                  tinymce.removeUnload(t.destroy);
14735                  tinyMCE.onBeforeUnload.remove(t._beforeUnload);
14736  
14737                  // Manual destroy
14738                  if (t.theme && t.theme.destroy)
14739                      t.theme.destroy();
14740  
14741                  // Destroy controls, selection and dom
14742                  t.controlManager.destroy();
14743                  t.selection.destroy();
14744                  t.dom.destroy();
14745              }
14746  
14747              if (t.formElement) {
14748                  t.formElement.submit = t.formElement._mceOldSubmit;
14749                  t.formElement._mceOldSubmit = null;
14750              }
14751  
14752              t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
14753  
14754              if (t.selection)
14755                  t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
14756  
14757              t.destroyed = 1;
14758          },
14759  
14760          // Internal functions
14761  
14762          _refreshContentEditable : function() {
14763              var self = this, body, parent;
14764  
14765              // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
14766              if (self._isHidden()) {
14767                  body = self.getBody();
14768                  parent = body.parentNode;
14769  
14770                  parent.removeChild(body);
14771                  parent.appendChild(body);
14772  
14773                  body.focus();
14774              }
14775          },
14776  
14777          _isHidden : function() {
14778              var s;
14779  
14780              if (!isGecko)
14781                  return 0;
14782  
14783              // Weird, wheres that cursor selection?
14784              s = this.selection.getSel();
14785              return (!s || !s.rangeCount || s.rangeCount === 0);
14786          }
14787      });
14788  })(tinymce);
14789  (function(tinymce) {
14790      var each = tinymce.each;
14791  
14792      tinymce.Editor.prototype.setupEvents = function() {
14793          var self = this, settings = self.settings;
14794  
14795          // Add events to the editor
14796          each([
14797              'onPreInit',
14798  
14799              'onBeforeRenderUI',
14800  
14801              'onPostRender',
14802  
14803              'onLoad',
14804  
14805              'onInit',
14806  
14807              'onRemove',
14808  
14809              'onActivate',
14810  
14811              'onDeactivate',
14812  
14813              'onClick',
14814  
14815              'onEvent',
14816  
14817              'onMouseUp',
14818  
14819              'onMouseDown',
14820  
14821              'onDblClick',
14822  
14823              'onKeyDown',
14824  
14825              'onKeyUp',
14826  
14827              'onKeyPress',
14828  
14829              'onContextMenu',
14830  
14831              'onSubmit',
14832  
14833              'onReset',
14834  
14835              'onPaste',
14836  
14837              'onPreProcess',
14838  
14839              'onPostProcess',
14840  
14841              'onBeforeSetContent',
14842  
14843              'onBeforeGetContent',
14844  
14845              'onSetContent',
14846  
14847              'onGetContent',
14848  
14849              'onLoadContent',
14850  
14851              'onSaveContent',
14852  
14853              'onNodeChange',
14854  
14855              'onChange',
14856  
14857              'onBeforeExecCommand',
14858  
14859              'onExecCommand',
14860  
14861              'onUndo',
14862  
14863              'onRedo',
14864  
14865              'onVisualAid',
14866  
14867              'onSetProgressState',
14868  
14869              'onSetAttrib'
14870          ], function(name) {
14871              self[name] = new tinymce.util.Dispatcher(self);
14872          });
14873  
14874          // Handle legacy cleanup_callback option
14875          if (settings.cleanup_callback) {
14876              self.onBeforeSetContent.add(function(ed, o) {
14877                  o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14878              });
14879  
14880              self.onPreProcess.add(function(ed, o) {
14881                  if (o.set)
14882                      ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
14883  
14884                  if (o.get)
14885                      ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
14886              });
14887  
14888              self.onPostProcess.add(function(ed, o) {
14889                  if (o.set)
14890                      o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
14891  
14892                  if (o.get)                        
14893                      o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
14894              });
14895          }
14896  
14897          // Handle legacy save_callback option
14898          if (settings.save_callback) {
14899              self.onGetContent.add(function(ed, o) {
14900                  if (o.save)
14901                      o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14902              });
14903          }
14904  
14905          // Handle legacy handle_event_callback option
14906          if (settings.handle_event_callback) {
14907              self.onEvent.add(function(ed, e, o) {
14908                  if (self.execCallback('handle_event_callback', e, ed, o) === false) {
14909                      e.preventDefault();
14910                      e.stopPropagation();
14911                  }
14912              });
14913          }
14914  
14915          // Handle legacy handle_node_change_callback option
14916          if (settings.handle_node_change_callback) {
14917              self.onNodeChange.add(function(ed, cm, n) {
14918                  ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
14919              });
14920          }
14921  
14922          // Handle legacy save_callback option
14923          if (settings.save_callback) {
14924              self.onSaveContent.add(function(ed, o) {
14925                  var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
14926  
14927                  if (h)
14928                      o.content = h;
14929              });
14930          }
14931  
14932          // Handle legacy onchange_callback option
14933          if (settings.onchange_callback) {
14934              self.onChange.add(function(ed, l) {
14935                  ed.execCallback('onchange_callback', ed, l);
14936              });
14937          }
14938      };
14939  
14940      tinymce.Editor.prototype.bindNativeEvents = function() {
14941          // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
14942          var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
14943  
14944          nativeToDispatcherMap = {
14945              mouseup : 'onMouseUp',
14946              mousedown : 'onMouseDown',
14947              click : 'onClick',
14948              keyup : 'onKeyUp',
14949              keydown : 'onKeyDown',
14950              keypress : 'onKeyPress',
14951              submit : 'onSubmit',
14952              reset : 'onReset',
14953              contextmenu : 'onContextMenu',
14954              dblclick : 'onDblClick',
14955              paste : 'onPaste' // Doesn't work in all browsers yet
14956          };
14957  
14958          // Handler that takes a native event and sends it out to a dispatcher like onKeyDown
14959  		function eventHandler(evt, args) {
14960              var type = evt.type;
14961  
14962              // Don't fire events when it's removed
14963              if (self.removed)
14964                  return;
14965  
14966              // Sends the native event out to a global dispatcher then to the specific event dispatcher
14967              if (self.onEvent.dispatch(self, evt, args) !== false) {
14968                  self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
14969              }
14970          };
14971  
14972          // Opera doesn't support focus event for contentEditable elements so we need to fake it
14973  		function doOperaFocus(e) {
14974              self.focus(true);
14975          };
14976  
14977  		function nodeChanged(ed, e) {
14978              // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
14979              if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
14980                  self.selection.normalize();
14981              }
14982  
14983              self.nodeChanged();
14984          }
14985  
14986          // Add DOM events
14987          each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
14988              var root = settings.content_editable ? self.getBody() : self.getDoc();
14989  
14990              switch (nativeName) {
14991                  case 'contextmenu':
14992                      dom.bind(root, nativeName, eventHandler);
14993                      break;
14994  
14995                  case 'paste':
14996                      dom.bind(self.getBody(), nativeName, eventHandler);
14997                      break;
14998  
14999                  case 'submit':
15000                  case 'reset':
15001                      dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
15002                      break;
15003  
15004                  default:
15005                      dom.bind(root, nativeName, eventHandler);
15006              }
15007          });
15008  
15009          // Set the editor as active when focused
15010          dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
15011              self.focus(true);
15012          });
15013  
15014          if (settings.content_editable && tinymce.isOpera) {
15015              dom.bind(self.getBody(), 'click', doOperaFocus);
15016              dom.bind(self.getBody(), 'keydown', doOperaFocus);
15017          }
15018  
15019          // Add node change handler
15020          self.onMouseUp.add(nodeChanged);
15021  
15022          self.onKeyUp.add(function(ed, e) {
15023              var keyCode = e.keyCode;
15024  
15025              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
15026                  nodeChanged(ed, e);
15027          });
15028  
15029          // Add reset handler
15030          self.onReset.add(function() {
15031              self.setContent(self.startContent, {format : 'raw'});
15032          });
15033  
15034          // Add shortcuts
15035  		function handleShortcut(e, execute) {
15036              if (e.altKey || e.ctrlKey || e.metaKey) {
15037                  each(self.shortcuts, function(shortcut) {
15038                      var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
15039  
15040                      if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
15041                          return;
15042  
15043                      if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
15044                          e.preventDefault();
15045  
15046                          if (execute) {
15047                              shortcut.func.call(shortcut.scope);
15048                          }
15049  
15050                          return true;
15051                      }
15052                  });
15053              }
15054          };
15055  
15056          self.onKeyUp.add(function(ed, e) {
15057              handleShortcut(e);
15058          });
15059  
15060          self.onKeyPress.add(function(ed, e) {
15061              handleShortcut(e);
15062          });
15063  
15064          self.onKeyDown.add(function(ed, e) {
15065              handleShortcut(e, true);
15066          });
15067  
15068          if (tinymce.isOpera) {
15069              self.onClick.add(function(ed, e) {
15070                  e.preventDefault();
15071              });
15072          }
15073      };
15074  })(tinymce);
15075  (function(tinymce) {
15076      // Added for compression purposes
15077      var each = tinymce.each, undef, TRUE = true, FALSE = false;
15078  
15079      tinymce.EditorCommands = function(editor) {
15080          var dom = editor.dom,
15081              selection = editor.selection,
15082              commands = {state: {}, exec : {}, value : {}},
15083              settings = editor.settings,
15084              formatter = editor.formatter,
15085              bookmark;
15086  
15087  		function execCommand(command, ui, value) {
15088              var func;
15089  
15090              command = command.toLowerCase();
15091              if (func = commands.exec[command]) {
15092                  func(command, ui, value);
15093                  return TRUE;
15094              }
15095  
15096              return FALSE;
15097          };
15098  
15099  		function queryCommandState(command) {
15100              var func;
15101  
15102              command = command.toLowerCase();
15103              if (func = commands.state[command])
15104                  return func(command);
15105  
15106              return -1;
15107          };
15108  
15109  		function queryCommandValue(command) {
15110              var func;
15111  
15112              command = command.toLowerCase();
15113              if (func = commands.value[command])
15114                  return func(command);
15115  
15116              return FALSE;
15117          };
15118  
15119  		function addCommands(command_list, type) {
15120              type = type || 'exec';
15121  
15122              each(command_list, function(callback, command) {
15123                  each(command.toLowerCase().split(','), function(command) {
15124                      commands[type][command] = callback;
15125                  });
15126              });
15127          };
15128  
15129          // Expose public methods
15130          tinymce.extend(this, {
15131              execCommand : execCommand,
15132              queryCommandState : queryCommandState,
15133              queryCommandValue : queryCommandValue,
15134              addCommands : addCommands
15135          });
15136  
15137          // Private methods
15138  
15139  		function execNativeCommand(command, ui, value) {
15140              if (ui === undef)
15141                  ui = FALSE;
15142  
15143              if (value === undef)
15144                  value = null;
15145  
15146              return editor.getDoc().execCommand(command, ui, value);
15147          };
15148  
15149  		function isFormatMatch(name) {
15150              return formatter.match(name);
15151          };
15152  
15153  		function toggleFormat(name, value) {
15154              formatter.toggle(name, value ? {value : value} : undef);
15155          };
15156  
15157  		function storeSelection(type) {
15158              bookmark = selection.getBookmark(type);
15159          };
15160  
15161  		function restoreSelection() {
15162              selection.moveToBookmark(bookmark);
15163          };
15164  
15165          // Add execCommand overrides
15166          addCommands({
15167              // Ignore these, added for compatibility
15168              'mceResetDesignMode,mceBeginUndoLevel' : function() {},
15169  
15170              // Add undo manager logic
15171              'mceEndUndoLevel,mceAddUndoLevel' : function() {
15172                  editor.undoManager.add();
15173              },
15174  
15175              'Cut,Copy,Paste' : function(command) {
15176                  var doc = editor.getDoc(), failed;
15177  
15178                  // Try executing the native command
15179                  try {
15180                      execNativeCommand(command);
15181                  } catch (ex) {
15182                      // Command failed
15183                      failed = TRUE;
15184                  }
15185  
15186                  // Present alert message about clipboard access not being available
15187                  if (failed || !doc.queryCommandSupported(command)) {
15188                      if (tinymce.isGecko) {
15189                          editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
15190                              if (state)
15191                                  open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
15192                          });
15193                      } else
15194                          editor.windowManager.alert(editor.getLang('clipboard_no_support'));
15195                  }
15196              },
15197  
15198              // Override unlink command
15199              unlink : function(command) {
15200                  if (selection.isCollapsed())
15201                      selection.select(selection.getNode());
15202  
15203                  execNativeCommand(command);
15204                  selection.collapse(FALSE);
15205              },
15206  
15207              // Override justify commands to use the text formatter engine
15208              'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15209                  var align = command.substring(7);
15210  
15211                  // Remove all other alignments first
15212                  each('left,center,right,full'.split(','), function(name) {
15213                      if (align != name)
15214                          formatter.remove('align' + name);
15215                  });
15216  
15217                  toggleFormat('align' + align);
15218                  execCommand('mceRepaint');
15219              },
15220  
15221              // Override list commands to fix WebKit bug
15222              'InsertUnorderedList,InsertOrderedList' : function(command) {
15223                  var listElm, listParent;
15224  
15225                  execNativeCommand(command);
15226  
15227                  // WebKit produces lists within block elements so we need to split them
15228                  // we will replace the native list creation logic to custom logic later on
15229                  // TODO: Remove this when the list creation logic is removed
15230                  listElm = dom.getParent(selection.getNode(), 'ol,ul');
15231                  if (listElm) {
15232                      listParent = listElm.parentNode;
15233  
15234                      // If list is within a text block then split that block
15235                      if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
15236                          storeSelection();
15237                          dom.split(listParent, listElm);
15238                          restoreSelection();
15239                      }
15240                  }
15241              },
15242  
15243              // Override commands to use the text formatter engine
15244              'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15245                  toggleFormat(command);
15246              },
15247  
15248              // Override commands to use the text formatter engine
15249              'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
15250                  toggleFormat(command, value);
15251              },
15252  
15253              FontSize : function(command, ui, value) {
15254                  var fontClasses, fontSizes;
15255  
15256                  // Convert font size 1-7 to styles
15257                  if (value >= 1 && value <= 7) {
15258                      fontSizes = tinymce.explode(settings.font_size_style_values);
15259                      fontClasses = tinymce.explode(settings.font_size_classes);
15260  
15261                      if (fontClasses)
15262                          value = fontClasses[value - 1] || value;
15263                      else
15264                          value = fontSizes[value - 1] || value;
15265                  }
15266  
15267                  toggleFormat(command, value);
15268              },
15269  
15270              RemoveFormat : function(command) {
15271                  formatter.remove(command);
15272              },
15273  
15274              mceBlockQuote : function(command) {
15275                  toggleFormat('blockquote');
15276              },
15277  
15278              FormatBlock : function(command, ui, value) {
15279                  return toggleFormat(value || 'p');
15280              },
15281  
15282              mceCleanup : function() {
15283                  var bookmark = selection.getBookmark();
15284  
15285                  editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
15286  
15287                  selection.moveToBookmark(bookmark);
15288              },
15289  
15290              mceRemoveNode : function(command, ui, value) {
15291                  var node = value || selection.getNode();
15292  
15293                  // Make sure that the body node isn't removed
15294                  if (node != editor.getBody()) {
15295                      storeSelection();
15296                      editor.dom.remove(node, TRUE);
15297                      restoreSelection();
15298                  }
15299              },
15300  
15301              mceSelectNodeDepth : function(command, ui, value) {
15302                  var counter = 0;
15303  
15304                  dom.getParent(selection.getNode(), function(node) {
15305                      if (node.nodeType == 1 && counter++ == value) {
15306                          selection.select(node);
15307                          return FALSE;
15308                      }
15309                  }, editor.getBody());
15310              },
15311  
15312              mceSelectNode : function(command, ui, value) {
15313                  selection.select(value);
15314              },
15315  
15316              mceInsertContent : function(command, ui, value) {
15317                  var parser, serializer, parentNode, rootNode, fragment, args,
15318                      marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
15319  
15320                  //selection.normalize();
15321  
15322                  // Setup parser and serializer
15323                  parser = editor.parser;
15324                  serializer = new tinymce.html.Serializer({}, editor.schema);
15325                  bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
15326  
15327                  // Run beforeSetContent handlers on the HTML to be inserted
15328                  args = {content: value, format: 'html'};
15329                  selection.onBeforeSetContent.dispatch(selection, args);
15330                  value = args.content;
15331  
15332                  // Add caret at end of contents if it's missing
15333                  if (value.indexOf('{$caret}') == -1)
15334                      value += '{$caret}';
15335  
15336                  // Replace the caret marker with a span bookmark element
15337                  value = value.replace(/\{\$caret\}/, bookmarkHtml);
15338  
15339                  // Insert node maker where we will insert the new HTML and get it's parent
15340                  if (!selection.isCollapsed())
15341                      editor.getDoc().execCommand('Delete', false, null);
15342  
15343                  parentNode = selection.getNode();
15344  
15345                  // Parse the fragment within the context of the parent node
15346                  args = {context : parentNode.nodeName.toLowerCase()};
15347                  fragment = parser.parse(value, args);
15348  
15349                  // Move the caret to a more suitable location
15350                  node = fragment.lastChild;
15351                  if (node.attr('id') == 'mce_marker') {
15352                      marker = node;
15353  
15354                      for (node = node.prev; node; node = node.walk(true)) {
15355                          if (node.type == 3 || !dom.isBlock(node.name)) {
15356                              node.parent.insert(marker, node, node.name === 'br');
15357                              break;
15358                          }
15359                      }
15360                  }
15361  
15362                  // If parser says valid we can insert the contents into that parent
15363                  if (!args.invalid) {
15364                      value = serializer.serialize(fragment);
15365  
15366                      // Check if parent is empty or only has one BR element then set the innerHTML of that parent
15367                      node = parentNode.firstChild;
15368                      node2 = parentNode.lastChild;
15369                      if (!node || (node === node2 && node.nodeName === 'BR'))
15370                          dom.setHTML(parentNode, value);
15371                      else
15372                          selection.setContent(value);
15373                  } else {
15374                      // If the fragment was invalid within that context then we need
15375                      // to parse and process the parent it's inserted into
15376  
15377                      // Insert bookmark node and get the parent
15378                      selection.setContent(bookmarkHtml);
15379                      parentNode = selection.getNode();
15380                      rootNode = editor.getBody();
15381  
15382                      // Opera will return the document node when selection is in root
15383                      if (parentNode.nodeType == 9)
15384                          parentNode = node = rootNode;
15385                      else
15386                          node = parentNode;
15387  
15388                      // Find the ancestor just before the root element
15389                      while (node !== rootNode) {
15390                          parentNode = node;
15391                          node = node.parentNode;
15392                      }
15393  
15394                      // Get the outer/inner HTML depending on if we are in the root and parser and serialize that
15395                      value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
15396                      value = serializer.serialize(
15397                          parser.parse(
15398                              // Need to replace by using a function since $ in the contents would otherwise be a problem
15399                              value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
15400                                  return serializer.serialize(fragment);
15401                              })
15402                          )
15403                      );
15404  
15405                      // Set the inner/outer HTML depending on if we are in the root or not
15406                      if (parentNode == rootNode)
15407                          dom.setHTML(rootNode, value);
15408                      else
15409                          dom.setOuterHTML(parentNode, value);
15410                  }
15411  
15412                  marker = dom.get('mce_marker');
15413  
15414                  // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
15415                  nodeRect = dom.getRect(marker);
15416                  viewPortRect = dom.getViewPort(editor.getWin());
15417  
15418                  // Check if node is out side the viewport if it is then scroll to it
15419                  if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
15420                      (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
15421                      viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
15422                      viewportBodyElement.scrollLeft = nodeRect.x;
15423                      viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
15424                  }
15425  
15426                  // Move selection before marker and remove it
15427                  rng = dom.createRng();
15428  
15429                  // If previous sibling is a text node set the selection to the end of that node
15430                  node = marker.previousSibling;
15431                  if (node && node.nodeType == 3) {
15432                      rng.setStart(node, node.nodeValue.length);
15433                  } else {
15434                      // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
15435                      rng.setStartBefore(marker);
15436                      rng.setEndBefore(marker);
15437                  }
15438  
15439                  // Remove the marker node and set the new range
15440                  dom.remove(marker);
15441                  selection.setRng(rng);
15442  
15443                  // Dispatch after event and add any visual elements needed
15444                  selection.onSetContent.dispatch(selection, args);
15445                  editor.addVisual();
15446              },
15447  
15448              mceInsertRawHTML : function(command, ui, value) {
15449                  selection.setContent('tiny_mce_marker');
15450                  editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
15451              },
15452  
15453              mceToggleFormat : function(command, ui, value) {
15454                  toggleFormat(value);
15455              },
15456  
15457              mceSetContent : function(command, ui, value) {
15458                  editor.setContent(value);
15459              },
15460  
15461              'Indent,Outdent' : function(command) {
15462                  var intentValue, indentUnit, value;
15463  
15464                  // Setup indent level
15465                  intentValue = settings.indentation;
15466                  indentUnit = /[a-z%]+$/i.exec(intentValue);
15467                  intentValue = parseInt(intentValue);
15468  
15469                  if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
15470                      // If forced_root_blocks is set to false we don't have a block to indent so lets create a div
15471                      if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
15472                          formatter.apply('div');
15473                      }
15474  
15475                      each(selection.getSelectedBlocks(), function(element) {
15476                          if (command == 'outdent') {
15477                              value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
15478                              dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
15479                          } else
15480                              dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
15481                      });
15482                  } else
15483                      execNativeCommand(command);
15484              },
15485  
15486              mceRepaint : function() {
15487                  var bookmark;
15488  
15489                  if (tinymce.isGecko) {
15490                      try {
15491                          storeSelection(TRUE);
15492  
15493                          if (selection.getSel())
15494                              selection.getSel().selectAllChildren(editor.getBody());
15495  
15496                          selection.collapse(TRUE);
15497                          restoreSelection();
15498                      } catch (ex) {
15499                          // Ignore
15500                      }
15501                  }
15502              },
15503  
15504              mceToggleFormat : function(command, ui, value) {
15505                  formatter.toggle(value);
15506              },
15507  
15508              InsertHorizontalRule : function() {
15509                  editor.execCommand('mceInsertContent', false, '<hr />');
15510              },
15511  
15512              mceToggleVisualAid : function() {
15513                  editor.hasVisual = !editor.hasVisual;
15514                  editor.addVisual();
15515              },
15516  
15517              mceReplaceContent : function(command, ui, value) {
15518                  editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
15519              },
15520  
15521              mceInsertLink : function(command, ui, value) {
15522                  var anchor;
15523  
15524                  if (typeof(value) == 'string')
15525                      value = {href : value};
15526  
15527                  anchor = dom.getParent(selection.getNode(), 'a');
15528  
15529                  // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
15530                  value.href = value.href.replace(' ', '%20');
15531  
15532                  // Remove existing links if there could be child links or that the href isn't specified
15533                  if (!anchor || !value.href) {
15534                      formatter.remove('link');
15535                  }        
15536  
15537                  // Apply new link to selection
15538                  if (value.href) {
15539                      formatter.apply('link', value, anchor);
15540                  }
15541              },
15542  
15543              selectAll : function() {
15544                  var root = dom.getRoot(), rng = dom.createRng();
15545  
15546                  // Old IE does a better job with selectall than new versions
15547                  if (selection.getRng().setStart) {
15548                      rng.setStart(root, 0);
15549                      rng.setEnd(root, root.childNodes.length);
15550  
15551                      selection.setRng(rng);
15552                  } else {
15553                      execNativeCommand('SelectAll');
15554                  }
15555              }
15556          });
15557  
15558          // Add queryCommandState overrides
15559          addCommands({
15560              // Override justify commands
15561              'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
15562                  var name = 'align' + command.substring(7);
15563                  var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
15564                  var matches = tinymce.map(nodes, function(node) {
15565                      return !!formatter.matchNode(node, name);
15566                  });
15567                  return tinymce.inArray(matches, TRUE) !== -1;
15568              },
15569  
15570              'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
15571                  return isFormatMatch(command);
15572              },
15573  
15574              mceBlockQuote : function() {
15575                  return isFormatMatch('blockquote');
15576              },
15577  
15578              Outdent : function() {
15579                  var node;
15580  
15581                  if (settings.inline_styles) {
15582                      if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15583                          return TRUE;
15584  
15585                      if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
15586                          return TRUE;
15587                  }
15588  
15589                  return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
15590              },
15591  
15592              'InsertUnorderedList,InsertOrderedList' : function(command) {
15593                  var list = dom.getParent(selection.getNode(), 'ul,ol');
15594                  return list && 
15595                       (command === 'insertunorderedlist' && list.tagName === 'UL'
15596                     || command === 'insertorderedlist' && list.tagName === 'OL');
15597              }
15598          }, 'state');
15599  
15600          // Add queryCommandValue overrides
15601          addCommands({
15602              'FontSize,FontName' : function(command) {
15603                  var value = 0, parent;
15604  
15605                  if (parent = dom.getParent(selection.getNode(), 'span')) {
15606                      if (command == 'fontsize')
15607                          value = parent.style.fontSize;
15608                      else
15609                          value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
15610                  }
15611  
15612                  return value;
15613              }
15614          }, 'value');
15615  
15616          // Add undo manager logic
15617          addCommands({
15618              Undo : function() {
15619                  editor.undoManager.undo();
15620              },
15621  
15622              Redo : function() {
15623                  editor.undoManager.redo();
15624              }
15625          });
15626      };
15627  })(tinymce);
15628  (function(tinymce) {
15629      var Dispatcher = tinymce.util.Dispatcher;
15630  
15631      tinymce.UndoManager = function(editor) {
15632          var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
15633  
15634  		function getContent() {
15635              // Remove whitespace before/after and remove pure bogus nodes
15636              return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
15637          };
15638  
15639  		function addNonTypingUndoLevel() {
15640              self.typing = false;
15641              self.add();
15642          };
15643  
15644          // Create event instances
15645          onBeforeAdd = new Dispatcher(self);
15646          onAdd       = new Dispatcher(self);
15647          onUndo      = new Dispatcher(self);
15648          onRedo      = new Dispatcher(self);
15649  
15650          // Pass though onAdd event from UndoManager to Editor as onChange
15651          onAdd.add(function(undoman, level) {
15652              if (undoman.hasUndo())
15653                  return editor.onChange.dispatch(editor, level, undoman);
15654          });
15655  
15656          // Pass though onUndo event from UndoManager to Editor
15657          onUndo.add(function(undoman, level) {
15658              return editor.onUndo.dispatch(editor, level, undoman);
15659          });
15660  
15661          // Pass though onRedo event from UndoManager to Editor
15662          onRedo.add(function(undoman, level) {
15663              return editor.onRedo.dispatch(editor, level, undoman);
15664          });
15665  
15666          // Add initial undo level when the editor is initialized
15667          editor.onInit.add(function() {
15668              self.add();
15669          });
15670  
15671          // Get position before an execCommand is processed
15672          editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
15673              if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15674                  self.beforeChange();
15675              }
15676          });
15677  
15678          // Add undo level after an execCommand call was made
15679          editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
15680              if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
15681                  self.add();
15682              }
15683          });
15684  
15685          // Add undo level on save contents, drag end and blur/focusout
15686          editor.onSaveContent.add(addNonTypingUndoLevel);
15687          editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
15688          editor.dom.bind(editor.getBody(), 'focusout', function(e) {
15689              if (!editor.removed && self.typing) {
15690                  addNonTypingUndoLevel();
15691              }
15692          });
15693  
15694          editor.onKeyUp.add(function(editor, e) {
15695              var keyCode = e.keyCode;
15696  
15697              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
15698                  addNonTypingUndoLevel();
15699              }
15700          });
15701  
15702          editor.onKeyDown.add(function(editor, e) {
15703              var keyCode = e.keyCode;
15704  
15705              // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
15706              if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
15707                  if (self.typing) {
15708                      addNonTypingUndoLevel();
15709                  }
15710  
15711                  return;
15712              }
15713  
15714              // If key isn't shift,ctrl,alt,capslock,metakey
15715              if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
15716                  self.beforeChange();
15717                  self.typing = true;
15718                  self.add();
15719              }
15720          });
15721  
15722          editor.onMouseDown.add(function(editor, e) {
15723              if (self.typing) {
15724                  addNonTypingUndoLevel();
15725              }
15726          });
15727  
15728          // Add keyboard shortcuts for undo/redo keys
15729          editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
15730          editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
15731  
15732          self = {
15733              // Explose for debugging reasons
15734              data : data,
15735  
15736              typing : false,
15737              
15738              onBeforeAdd: onBeforeAdd,
15739  
15740              onAdd : onAdd,
15741  
15742              onUndo : onUndo,
15743  
15744              onRedo : onRedo,
15745  
15746              beforeChange : function() {
15747                  beforeBookmark = editor.selection.getBookmark(2, true);
15748              },
15749  
15750              add : function(level) {
15751                  var i, settings = editor.settings, lastLevel;
15752  
15753                  level = level || {};
15754                  level.content = getContent();
15755                  
15756                  self.onBeforeAdd.dispatch(self, level);
15757  
15758                  // Add undo level if needed
15759                  lastLevel = data[index];
15760                  if (lastLevel && lastLevel.content == level.content)
15761                      return null;
15762  
15763                  // Set before bookmark on previous level
15764                  if (data[index])
15765                      data[index].beforeBookmark = beforeBookmark;
15766  
15767                  // Time to compress
15768                  if (settings.custom_undo_redo_levels) {
15769                      if (data.length > settings.custom_undo_redo_levels) {
15770                          for (i = 0; i < data.length - 1; i++)
15771                              data[i] = data[i + 1];
15772  
15773                          data.length--;
15774                          index = data.length;
15775                      }
15776                  }
15777  
15778                  // Get a non intrusive normalized bookmark
15779                  level.bookmark = editor.selection.getBookmark(2, true);
15780  
15781                  // Crop array if needed
15782                  if (index < data.length - 1)
15783                      data.length = index + 1;
15784  
15785                  data.push(level);
15786                  index = data.length - 1;
15787  
15788                  self.onAdd.dispatch(self, level);
15789                  editor.isNotDirty = 0;
15790  
15791                  return level;
15792              },
15793  
15794              undo : function() {
15795                  var level, i;
15796  
15797                  if (self.typing) {
15798                      self.add();
15799                      self.typing = false;
15800                  }
15801  
15802                  if (index > 0) {
15803                      level = data[--index];
15804  
15805                      editor.setContent(level.content, {format : 'raw'});
15806                      editor.selection.moveToBookmark(level.beforeBookmark);
15807  
15808                      self.onUndo.dispatch(self, level);
15809                  }
15810  
15811                  return level;
15812              },
15813  
15814              redo : function() {
15815                  var level;
15816  
15817                  if (index < data.length - 1) {
15818                      level = data[++index];
15819  
15820                      editor.setContent(level.content, {format : 'raw'});
15821                      editor.selection.moveToBookmark(level.bookmark);
15822  
15823                      self.onRedo.dispatch(self, level);
15824                  }
15825  
15826                  return level;
15827              },
15828  
15829              clear : function() {
15830                  data = [];
15831                  index = 0;
15832                  self.typing = false;
15833              },
15834  
15835              hasUndo : function() {
15836                  return index > 0 || this.typing;
15837              },
15838  
15839              hasRedo : function() {
15840                  return index < data.length - 1 && !this.typing;
15841              }
15842          };
15843  
15844          return self;
15845      };
15846  })(tinymce);
15847  tinymce.ForceBlocks = function(editor) {
15848      var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
15849  
15850  	function addRootBlocks() {
15851          var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
15852  
15853          if (!node || node.nodeType !== 1 || !settings.forced_root_block)
15854              return;
15855  
15856          // Check if node is wrapped in block
15857          while (node && node != rootNode) {
15858              if (blockElements[node.nodeName])
15859                  return;
15860  
15861              node = node.parentNode;
15862          }
15863  
15864          // Get current selection
15865          rng = selection.getRng();
15866          if (rng.setStart) {
15867              startContainer = rng.startContainer;
15868              startOffset = rng.startOffset;
15869              endContainer = rng.endContainer;
15870              endOffset = rng.endOffset;
15871          } else {
15872              // Force control range into text range
15873              if (rng.item) {
15874                  node = rng.item(0);
15875                  rng = editor.getDoc().body.createTextRange();
15876                  rng.moveToElementText(node);
15877              }
15878  
15879              isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
15880              tmpRng = rng.duplicate();
15881              tmpRng.collapse(true);
15882              startOffset = tmpRng.move('character', offset) * -1;
15883  
15884              if (!tmpRng.collapsed) {
15885                  tmpRng = rng.duplicate();
15886                  tmpRng.collapse(false);
15887                  endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
15888              }
15889          }
15890  
15891          // Wrap non block elements and text nodes
15892          node = rootNode.firstChild;
15893          while (node) {
15894              if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
15895                  // Remove empty text nodes
15896                  if (node.nodeType === 3 && node.nodeValue.length == 0) {
15897                      tempNode = node;
15898                      node = node.nextSibling;
15899                      dom.remove(tempNode);
15900                      continue;
15901                  }
15902  
15903                  if (!rootBlockNode) {
15904                      rootBlockNode = dom.create(settings.forced_root_block);
15905                      node.parentNode.insertBefore(rootBlockNode, node);
15906                      wrapped = true;
15907                  }
15908  
15909                  tempNode = node;
15910                  node = node.nextSibling;
15911                  rootBlockNode.appendChild(tempNode);
15912              } else {
15913                  rootBlockNode = null;
15914                  node = node.nextSibling;
15915              }
15916          }
15917  
15918          if (wrapped) {
15919              if (rng.setStart) {
15920                  rng.setStart(startContainer, startOffset);
15921                  rng.setEnd(endContainer, endOffset);
15922                  selection.setRng(rng);
15923              } else {
15924                  // Only select if the previous selection was inside the document to prevent auto focus in quirks mode
15925                  if (isInEditorDocument) {
15926                      try {
15927                          rng = editor.getDoc().body.createTextRange();
15928                          rng.moveToElementText(rootNode);
15929                          rng.collapse(true);
15930                          rng.moveStart('character', startOffset);
15931  
15932                          if (endOffset > 0)
15933                              rng.moveEnd('character', endOffset);
15934  
15935                          rng.select();
15936                      } catch (ex) {
15937                          // Ignore
15938                      }
15939                  }
15940              }
15941  
15942              editor.nodeChanged();
15943          }
15944      };
15945  
15946      // Force root blocks
15947      if (settings.forced_root_block) {
15948          editor.onKeyUp.add(addRootBlocks);
15949          editor.onNodeChange.add(addRootBlocks);
15950      }
15951  };
15952  (function(tinymce) {
15953      // Shorten names
15954      var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
15955  
15956      tinymce.create('tinymce.ControlManager', {
15957          ControlManager : function(ed, s) {
15958              var t = this, i;
15959  
15960              s = s || {};
15961              t.editor = ed;
15962              t.controls = {};
15963              t.onAdd = new tinymce.util.Dispatcher(t);
15964              t.onPostRender = new tinymce.util.Dispatcher(t);
15965              t.prefix = s.prefix || ed.id + '_';
15966              t._cls = {};
15967  
15968              t.onPostRender.add(function() {
15969                  each(t.controls, function(c) {
15970                      c.postRender();
15971                  });
15972              });
15973          },
15974  
15975          get : function(id) {
15976              return this.controls[this.prefix + id] || this.controls[id];
15977          },
15978  
15979          setActive : function(id, s) {
15980              var c = null;
15981  
15982              if (c = this.get(id))
15983                  c.setActive(s);
15984  
15985              return c;
15986          },
15987  
15988          setDisabled : function(id, s) {
15989              var c = null;
15990  
15991              if (c = this.get(id))
15992                  c.setDisabled(s);
15993  
15994              return c;
15995          },
15996  
15997          add : function(c) {
15998              var t = this;
15999  
16000              if (c) {
16001                  t.controls[c.id] = c;
16002                  t.onAdd.dispatch(c, t);
16003              }
16004  
16005              return c;
16006          },
16007  
16008          createControl : function(name) {
16009              var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
16010  
16011              // Build control factory cache
16012              if (!self.controlFactories) {
16013                  self.controlFactories = [];
16014                  each(editor.plugins, function(plugin) {
16015                      if (plugin.createControl) {
16016                          self.controlFactories.push(plugin);
16017                      }
16018                  });
16019              }
16020  
16021              // Create controls by asking cached factories
16022              factories = self.controlFactories;
16023              for (i = 0, l = factories.length; i < l; i++) {
16024                  ctrl = factories[i].createControl(name, self);
16025  
16026                  if (ctrl) {
16027                      return self.add(ctrl);
16028                  }
16029              }
16030  
16031              // Create sepearator
16032              if (name === "|" || name === "separator") {
16033                  return self.createSeparator();
16034              }
16035  
16036              // Create control from button collection
16037              if (editor.buttons && (ctrl = editor.buttons[name])) {
16038                  return self.createButton(name, ctrl);
16039              }
16040  
16041              return self.add(ctrl);
16042          },
16043  
16044          createDropMenu : function(id, s, cc) {
16045              var t = this, ed = t.editor, c, bm, v, cls;
16046  
16047              s = extend({
16048                  'class' : 'mceDropDown',
16049                  constrain : ed.settings.constrain_menus
16050              }, s);
16051  
16052              s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
16053              if (v = ed.getParam('skin_variant'))
16054                  s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
16055  
16056              s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
16057  
16058              id = t.prefix + id;
16059              cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
16060              c = t.controls[id] = new cls(id, s);
16061              c.onAddItem.add(function(c, o) {
16062                  var s = o.settings;
16063  
16064                  s.title = ed.getLang(s.title, s.title);
16065  
16066                  if (!s.onclick) {
16067                      s.onclick = function(v) {
16068                          if (s.cmd)
16069                              ed.execCommand(s.cmd, s.ui || false, s.value);
16070                      };
16071                  }
16072              });
16073  
16074              ed.onRemove.add(function() {
16075                  c.destroy();
16076              });
16077  
16078              // Fix for bug #1897785, #1898007
16079              if (tinymce.isIE) {
16080                  c.onShowMenu.add(function() {
16081                      // IE 8 needs focus in order to store away a range with the current collapsed caret location
16082                      ed.focus();
16083  
16084                      bm = ed.selection.getBookmark(1);
16085                  });
16086  
16087                  c.onHideMenu.add(function() {
16088                      if (bm) {
16089                          ed.selection.moveToBookmark(bm);
16090                          bm = 0;
16091                      }
16092                  });
16093              }
16094  
16095              return t.add(c);
16096          },
16097  
16098          createListBox : function(id, s, cc) {
16099              var t = this, ed = t.editor, cmd, c, cls;
16100  
16101              if (t.get(id))
16102                  return null;
16103  
16104              s.title = ed.translate(s.title);
16105              s.scope = s.scope || ed;
16106  
16107              if (!s.onselect) {
16108                  s.onselect = function(v) {
16109                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16110                  };
16111              }
16112  
16113              s = extend({
16114                  title : s.title,
16115                  'class' : 'mce_' + id,
16116                  scope : s.scope,
16117                  control_manager : t
16118              }, s);
16119  
16120              id = t.prefix + id;
16121  
16122  
16123  			function useNativeListForAccessibility(ed) {
16124                  return ed.settings.use_accessible_selects && !tinymce.isGecko
16125              }
16126  
16127              if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
16128                  c = new tinymce.ui.NativeListBox(id, s);
16129              else {
16130                  cls = cc || t._cls.listbox || tinymce.ui.ListBox;
16131                  c = new cls(id, s, ed);
16132              }
16133  
16134              t.controls[id] = c;
16135  
16136              // Fix focus problem in Safari
16137              if (tinymce.isWebKit) {
16138                  c.onPostRender.add(function(c, n) {
16139                      // Store bookmark on mousedown
16140                      Event.add(n, 'mousedown', function() {
16141                          ed.bookmark = ed.selection.getBookmark(1);
16142                      });
16143  
16144                      // Restore on focus, since it might be lost
16145                      Event.add(n, 'focus', function() {
16146                          ed.selection.moveToBookmark(ed.bookmark);
16147                          ed.bookmark = null;
16148                      });
16149                  });
16150              }
16151  
16152              if (c.hideMenu)
16153                  ed.onMouseDown.add(c.hideMenu, c);
16154  
16155              return t.add(c);
16156          },
16157  
16158          createButton : function(id, s, cc) {
16159              var t = this, ed = t.editor, o, c, cls;
16160  
16161              if (t.get(id))
16162                  return null;
16163  
16164              s.title = ed.translate(s.title);
16165              s.label = ed.translate(s.label);
16166              s.scope = s.scope || ed;
16167  
16168              if (!s.onclick && !s.menu_button) {
16169                  s.onclick = function() {
16170                      ed.execCommand(s.cmd, s.ui || false, s.value);
16171                  };
16172              }
16173  
16174              s = extend({
16175                  title : s.title,
16176                  'class' : 'mce_' + id,
16177                  unavailable_prefix : ed.getLang('unavailable', ''),
16178                  scope : s.scope,
16179                  control_manager : t
16180              }, s);
16181  
16182              id = t.prefix + id;
16183  
16184              if (s.menu_button) {
16185                  cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
16186                  c = new cls(id, s, ed);
16187                  ed.onMouseDown.add(c.hideMenu, c);
16188              } else {
16189                  cls = t._cls.button || tinymce.ui.Button;
16190                  c = new cls(id, s, ed);
16191              }
16192  
16193              return t.add(c);
16194          },
16195  
16196          createMenuButton : function(id, s, cc) {
16197              s = s || {};
16198              s.menu_button = 1;
16199  
16200              return this.createButton(id, s, cc);
16201          },
16202  
16203          createSplitButton : function(id, s, cc) {
16204              var t = this, ed = t.editor, cmd, c, cls;
16205  
16206              if (t.get(id))
16207                  return null;
16208  
16209              s.title = ed.translate(s.title);
16210              s.scope = s.scope || ed;
16211  
16212              if (!s.onclick) {
16213                  s.onclick = function(v) {
16214                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16215                  };
16216              }
16217  
16218              if (!s.onselect) {
16219                  s.onselect = function(v) {
16220                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16221                  };
16222              }
16223  
16224              s = extend({
16225                  title : s.title,
16226                  'class' : 'mce_' + id,
16227                  scope : s.scope,
16228                  control_manager : t
16229              }, s);
16230  
16231              id = t.prefix + id;
16232              cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
16233              c = t.add(new cls(id, s, ed));
16234              ed.onMouseDown.add(c.hideMenu, c);
16235  
16236              return c;
16237          },
16238  
16239          createColorSplitButton : function(id, s, cc) {
16240              var t = this, ed = t.editor, cmd, c, cls, bm;
16241  
16242              if (t.get(id))
16243                  return null;
16244  
16245              s.title = ed.translate(s.title);
16246              s.scope = s.scope || ed;
16247  
16248              if (!s.onclick) {
16249                  s.onclick = function(v) {
16250                      if (tinymce.isIE)
16251                          bm = ed.selection.getBookmark(1);
16252  
16253                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16254                  };
16255              }
16256  
16257              if (!s.onselect) {
16258                  s.onselect = function(v) {
16259                      ed.execCommand(s.cmd, s.ui || false, v || s.value);
16260                  };
16261              }
16262  
16263              s = extend({
16264                  title : s.title,
16265                  'class' : 'mce_' + id,
16266                  'menu_class' : ed.getParam('skin') + 'Skin',
16267                  scope : s.scope,
16268                  more_colors_title : ed.getLang('more_colors')
16269              }, s);
16270  
16271              id = t.prefix + id;
16272              cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
16273              c = new cls(id, s, ed);
16274              ed.onMouseDown.add(c.hideMenu, c);
16275  
16276              // Remove the menu element when the editor is removed
16277              ed.onRemove.add(function() {
16278                  c.destroy();
16279              });
16280  
16281              // Fix for bug #1897785, #1898007
16282              if (tinymce.isIE) {
16283                  c.onShowMenu.add(function() {
16284                      // IE 8 needs focus in order to store away a range with the current collapsed caret location
16285                      ed.focus();
16286                      bm = ed.selection.getBookmark(1);
16287                  });
16288  
16289                  c.onHideMenu.add(function() {
16290                      if (bm) {
16291                          ed.selection.moveToBookmark(bm);
16292                          bm = 0;
16293                      }
16294                  });
16295              }
16296  
16297              return t.add(c);
16298          },
16299  
16300          createToolbar : function(id, s, cc) {
16301              var c, t = this, cls;
16302  
16303              id = t.prefix + id;
16304              cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
16305              c = new cls(id, s, t.editor);
16306  
16307              if (t.get(id))
16308                  return null;
16309  
16310              return t.add(c);
16311          },
16312          
16313          createToolbarGroup : function(id, s, cc) {
16314              var c, t = this, cls;
16315              id = t.prefix + id;
16316              cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
16317              c = new cls(id, s, t.editor);
16318              
16319              if (t.get(id))
16320                  return null;
16321              
16322              return t.add(c);
16323          },
16324  
16325          createSeparator : function(cc) {
16326              var cls = cc || this._cls.separator || tinymce.ui.Separator;
16327  
16328              return new cls();
16329          },
16330  
16331          setControlType : function(n, c) {
16332              return this._cls[n.toLowerCase()] = c;
16333          },
16334      
16335          destroy : function() {
16336              each(this.controls, function(c) {
16337                  c.destroy();
16338              });
16339  
16340              this.controls = null;
16341          }
16342      });
16343  })(tinymce);
16344  (function(tinymce) {
16345      var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
16346  
16347      tinymce.create('tinymce.WindowManager', {
16348          WindowManager : function(ed) {
16349              var t = this;
16350  
16351              t.editor = ed;
16352              t.onOpen = new Dispatcher(t);
16353              t.onClose = new Dispatcher(t);
16354              t.params = {};
16355              t.features = {};
16356          },
16357  
16358          open : function(s, p) {
16359              var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
16360  
16361              // Default some options
16362              s = s || {};
16363              p = p || {};
16364              sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
16365              sh = isOpera ? vp.h : screen.height;
16366              s.name = s.name || 'mc_' + new Date().getTime();
16367              s.width = parseInt(s.width || 320);
16368              s.height = parseInt(s.height || 240);
16369              s.resizable = true;
16370              s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
16371              s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
16372              p.inline = false;
16373              p.mce_width = s.width;
16374              p.mce_height = s.height;
16375              p.mce_auto_focus = s.auto_focus;
16376  
16377              if (mo) {
16378                  if (isIE) {
16379                      s.center = true;
16380                      s.help = false;
16381                      s.dialogWidth = s.width + 'px';
16382                      s.dialogHeight = s.height + 'px';
16383                      s.scroll = s.scrollbars || false;
16384                  }
16385              }
16386  
16387              // Build features string
16388              each(s, function(v, k) {
16389                  if (tinymce.is(v, 'boolean'))
16390                      v = v ? 'yes' : 'no';
16391  
16392                  if (!/^(name|url)$/.test(k)) {
16393                      if (isIE && mo)
16394                          f += (f ? ';' : '') + k + ':' + v;
16395                      else
16396                          f += (f ? ',' : '') + k + '=' + v;
16397                  }
16398              });
16399  
16400              t.features = s;
16401              t.params = p;
16402              t.onOpen.dispatch(t, s, p);
16403  
16404              u = s.url || s.file;
16405              u = tinymce._addVer(u);
16406  
16407              try {
16408                  if (isIE && mo) {
16409                      w = 1;
16410                      window.showModalDialog(u, window, f);
16411                  } else
16412                      w = window.open(u, s.name, f);
16413              } catch (ex) {
16414                  // Ignore
16415              }
16416  
16417              if (!w)
16418                  alert(t.editor.getLang('popup_blocked'));
16419          },
16420  
16421          close : function(w) {
16422              w.close();
16423              this.onClose.dispatch(this);
16424          },
16425  
16426          createInstance : function(cl, a, b, c, d, e) {
16427              var f = tinymce.resolve(cl);
16428  
16429              return new f(a, b, c, d, e);
16430          },
16431  
16432          confirm : function(t, cb, s, w) {
16433              w = w || window;
16434  
16435              cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
16436          },
16437  
16438          alert : function(tx, cb, s, w) {
16439              var t = this;
16440  
16441              w = w || window;
16442              w.alert(t._decode(t.editor.getLang(tx, tx)));
16443  
16444              if (cb)
16445                  cb.call(s || t);
16446          },
16447  
16448          resizeBy : function(dw, dh, win) {
16449              win.resizeBy(dw, dh);
16450          },
16451  
16452          // Internal functions
16453  
16454          _decode : function(s) {
16455              return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
16456          }
16457      });
16458  }(tinymce));
16459  (function(tinymce) {
16460      tinymce.Formatter = function(ed) {
16461          var formats = {},
16462              each = tinymce.each,
16463              dom = ed.dom,
16464              selection = ed.selection,
16465              TreeWalker = tinymce.dom.TreeWalker,
16466              rangeUtils = new tinymce.dom.RangeUtils(dom),
16467              isValid = ed.schema.isValidChild,
16468              isArray = tinymce.isArray,
16469              isBlock = dom.isBlock,
16470              forcedRootBlock = ed.settings.forced_root_block,
16471              nodeIndex = dom.nodeIndex,
16472              INVISIBLE_CHAR = '\uFEFF',
16473              MCE_ATTR_RE = /^(src|href|style)$/,
16474              FALSE = false,
16475              TRUE = true,
16476              formatChangeData,
16477              undef,
16478              getContentEditable = dom.getContentEditable;
16479  
16480  		function isTextBlock(name) {
16481              if (name.nodeType) {
16482                  name = name.nodeName;
16483              }
16484  
16485              return !!ed.schema.getTextBlockElements()[name.toLowerCase()];
16486          }
16487  
16488  		function getParents(node, selector) {
16489              return dom.getParents(node, selector, dom.getRoot());
16490          };
16491  
16492  		function isCaretNode(node) {
16493              return node.nodeType === 1 && node.id === '_mce_caret';
16494          };
16495  
16496  		function defaultFormats() {
16497              register({
16498                  alignleft : [
16499                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
16500                      {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
16501                  ],
16502  
16503                  aligncenter : [
16504                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
16505                      {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
16506                      {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
16507                  ],
16508  
16509                  alignright : [
16510                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
16511                      {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
16512                  ],
16513  
16514                  alignfull : [
16515                      {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
16516                  ],
16517  
16518                  bold : [
16519                      {inline : 'strong', remove : 'all'},
16520                      {inline : 'span', styles : {fontWeight : 'bold'}},
16521                      {inline : 'b', remove : 'all'}
16522                  ],
16523  
16524                  italic : [
16525                      {inline : 'em', remove : 'all'},
16526                      {inline : 'span', styles : {fontStyle : 'italic'}},
16527                      {inline : 'i', remove : 'all'}
16528                  ],
16529  
16530                  underline : [
16531                      {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
16532                      {inline : 'u', remove : 'all'}
16533                  ],
16534  
16535                  strikethrough : [
16536                      {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
16537                      {inline : 'strike', remove : 'all'}
16538                  ],
16539  
16540                  forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
16541                  hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
16542                  fontname : {inline : 'span', styles : {fontFamily : '%value'}},
16543                  fontsize : {inline : 'span', styles : {fontSize : '%value'}},
16544                  fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
16545                  blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
16546                  subscript : {inline : 'sub'},
16547                  superscript : {inline : 'sup'},
16548  
16549                  link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
16550                      onmatch : function(node) {
16551                          return true;
16552                      },
16553  
16554                      onformat : function(elm, fmt, vars) {
16555                          each(vars, function(value, key) {
16556                              dom.setAttrib(elm, key, value);
16557                          });
16558                      }
16559                  },
16560  
16561                  removeformat : [
16562                      {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
16563                      {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
16564                      {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
16565                  ]
16566              });
16567  
16568              // Register default block formats
16569              each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
16570                  register(name, {block : name, remove : 'all'});
16571              });
16572  
16573              // Register user defined formats
16574              register(ed.settings.formats);
16575          };
16576  
16577  		function addKeyboardShortcuts() {
16578              // Add some inline shortcuts
16579              ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
16580              ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
16581              ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
16582  
16583              // BlockFormat shortcuts keys
16584              for (var i = 1; i <= 6; i++) {
16585                  ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
16586              }
16587  
16588              ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
16589              ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
16590              ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
16591          };
16592  
16593          // Public functions
16594  
16595  		function get(name) {
16596              return name ? formats[name] : formats;
16597          };
16598  
16599  		function register(name, format) {
16600              if (name) {
16601                  if (typeof(name) !== 'string') {
16602                      each(name, function(format, name) {
16603                          register(name, format);
16604                      });
16605                  } else {
16606                      // Force format into array and add it to internal collection
16607                      format = format.length ? format : [format];
16608  
16609                      each(format, function(format) {
16610                          // Set deep to false by default on selector formats this to avoid removing
16611                          // alignment on images inside paragraphs when alignment is changed on paragraphs
16612                          if (format.deep === undef)
16613                              format.deep = !format.selector;
16614  
16615                          // Default to true
16616                          if (format.split === undef)
16617                              format.split = !format.selector || format.inline;
16618  
16619                          // Default to true
16620                          if (format.remove === undef && format.selector && !format.inline)
16621                              format.remove = 'none';
16622  
16623                          // Mark format as a mixed format inline + block level
16624                          if (format.selector && format.inline) {
16625                              format.mixed = true;
16626                              format.block_expand = true;
16627                          }
16628  
16629                          // Split classes if needed
16630                          if (typeof(format.classes) === 'string')
16631                              format.classes = format.classes.split(/\s+/);
16632                      });
16633  
16634                      formats[name] = format;
16635                  }
16636              }
16637          };
16638  
16639          var getTextDecoration = function(node) {
16640              var decoration;
16641  
16642              ed.dom.getParent(node, function(n) {
16643                  decoration = ed.dom.getStyle(n, 'text-decoration');
16644                  return decoration && decoration !== 'none';
16645              });
16646  
16647              return decoration;
16648          };
16649  
16650          var processUnderlineAndColor = function(node) {
16651              var textDecoration;
16652              if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
16653                  textDecoration = getTextDecoration(node.parentNode);
16654                  if (ed.dom.getStyle(node, 'color') && textDecoration) {
16655                      ed.dom.setStyle(node, 'text-decoration', textDecoration);
16656                  } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
16657                      ed.dom.setStyle(node, 'text-decoration', null);
16658                  }
16659              }
16660          };
16661  
16662  		function apply(name, vars, node) {
16663              var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
16664  
16665  			function setElementFormat(elm, fmt) {
16666                  fmt = fmt || format;
16667  
16668                  if (elm) {
16669                      if (fmt.onformat) {
16670                          fmt.onformat(elm, fmt, vars, node);
16671                      }
16672  
16673                      each(fmt.styles, function(value, name) {
16674                          dom.setStyle(elm, name, replaceVars(value, vars));
16675                      });
16676  
16677                      each(fmt.attributes, function(value, name) {
16678                          dom.setAttrib(elm, name, replaceVars(value, vars));
16679                      });
16680  
16681                      each(fmt.classes, function(value) {
16682                          value = replaceVars(value, vars);
16683  
16684                          if (!dom.hasClass(elm, value))
16685                              dom.addClass(elm, value);
16686                      });
16687                  }
16688              };
16689  			function adjustSelectionToVisibleSelection() {
16690  				function findSelectionEnd(start, end) {
16691                      var walker = new TreeWalker(end);
16692                      for (node = walker.current(); node; node = walker.prev()) {
16693                          if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
16694                              return node;
16695                          }
16696                      }
16697                  };
16698  
16699                  // Adjust selection so that a end container with a end offset of zero is not included in the selection
16700                  // as this isn't visible to the user.
16701                  var rng = ed.selection.getRng();
16702                  var start = rng.startContainer;
16703                  var end = rng.endContainer;
16704  
16705                  if (start != end && rng.endOffset === 0) {
16706                      var newEnd = findSelectionEnd(start, end);
16707                      var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
16708  
16709                      rng.setEnd(newEnd, endOffset);
16710                  }
16711  
16712                  return rng;
16713              }
16714              
16715  			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
16716                  var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
16717                  
16718                  // find the index of the first child list.
16719                  each(node.childNodes, function(n, index) {
16720                      if (n.nodeName === "UL" || n.nodeName === "OL") {
16721                          listIndex = index;
16722                          list = n;
16723                          return false;
16724                      }
16725                  });
16726                  
16727                  // get the index of the bookmarks
16728                  each(node.childNodes, function(n, index) {
16729                      if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
16730                          if (n.id == bookmark.id + "_start") {
16731                              startIndex = index;
16732                          } else if (n.id == bookmark.id + "_end") {
16733                              endIndex = index;
16734                          }
16735                      }
16736                  });
16737                  
16738                  // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
16739                  if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
16740                      each(tinymce.grep(node.childNodes), process);
16741                      return 0;
16742                  } else {
16743                      currentWrapElm = dom.clone(wrapElm, FALSE);
16744  
16745                      // create a list of the nodes on the same side of the list as the selection
16746                      each(tinymce.grep(node.childNodes), function(n, index) {
16747                          if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
16748                              nodes.push(n); 
16749                              n.parentNode.removeChild(n);
16750                          }
16751                      });
16752  
16753                      // insert the wrapping element either before or after the list.
16754                      if (startIndex < listIndex) {
16755                          node.insertBefore(currentWrapElm, list);
16756                      } else if (startIndex > listIndex) {
16757                          node.insertBefore(currentWrapElm, list.nextSibling);
16758                      }
16759                      
16760                      // add the new nodes to the list.
16761                      newWrappers.push(currentWrapElm);
16762  
16763                      each(nodes, function(node) {
16764                          currentWrapElm.appendChild(node);
16765                      });
16766  
16767                      return currentWrapElm;
16768                  }
16769              };
16770  
16771  			function applyRngStyle(rng, bookmark, node_specific) {
16772                  var newWrappers = [], wrapName, wrapElm, contentEditable = true;
16773  
16774                  // Setup wrapper element
16775                  wrapName = format.inline || format.block;
16776                  wrapElm = dom.create(wrapName);
16777                  setElementFormat(wrapElm);
16778  
16779                  rangeUtils.walk(rng, function(nodes) {
16780                      var currentWrapElm;
16781  
16782  					function process(node) {
16783                          var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
16784  
16785                          lastContentEditable = contentEditable;
16786                          nodeName = node.nodeName.toLowerCase();
16787                          parentName = node.parentNode.nodeName.toLowerCase();
16788  
16789                          // Node has a contentEditable value
16790                          if (node.nodeType === 1 && getContentEditable(node)) {
16791                              lastContentEditable = contentEditable;
16792                              contentEditable = getContentEditable(node) === "true";
16793                              hasContentEditableState = true; // We don't want to wrap the container only it's children
16794                          }
16795  
16796                          // Stop wrapping on br elements
16797                          if (isEq(nodeName, 'br')) {
16798                              currentWrapElm = 0;
16799  
16800                              // Remove any br elements when we wrap things
16801                              if (format.block)
16802                                  dom.remove(node);
16803  
16804                              return;
16805                          }
16806  
16807                          // If node is wrapper type
16808                          if (format.wrapper && matchNode(node, name, vars)) {
16809                              currentWrapElm = 0;
16810                              return;
16811                          }
16812  
16813                          // Can we rename the block
16814                          if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
16815                              node = dom.rename(node, wrapName);
16816                              setElementFormat(node);
16817                              newWrappers.push(node);
16818                              currentWrapElm = 0;
16819                              return;
16820                          }
16821  
16822                          // Handle selector patterns
16823                          if (format.selector) {
16824                              // Look for matching formats
16825                              each(formatList, function(format) {
16826                                  // Check collapsed state if it exists
16827                                  if ('collapsed' in format && format.collapsed !== isCollapsed) {
16828                                      return;
16829                                  }
16830  
16831                                  if (dom.is(node, format.selector) && !isCaretNode(node)) {
16832                                      setElementFormat(node, format);
16833                                      found = true;
16834                                  }
16835                              });
16836  
16837                              // Continue processing if a selector match wasn't found and a inline element is defined
16838                              if (!format.inline || found) {
16839                                  currentWrapElm = 0;
16840                                  return;
16841                              }
16842                          }
16843  
16844                          // Is it valid to wrap this item
16845                          if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
16846                                  !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node) && (!format.inline || !isBlock(node))) {
16847                              // Start wrapping
16848                              if (!currentWrapElm) {
16849                                  // Wrap the node
16850                                  currentWrapElm = dom.clone(wrapElm, FALSE);
16851                                  node.parentNode.insertBefore(currentWrapElm, node);
16852                                  newWrappers.push(currentWrapElm);
16853                              }
16854  
16855                              currentWrapElm.appendChild(node);
16856                          } else if (nodeName == 'li' && bookmark) {
16857                              // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
16858                              currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
16859                          } else {
16860                              // Start a new wrapper for possible children
16861                              currentWrapElm = 0;
16862                              
16863                              each(tinymce.grep(node.childNodes), process);
16864  
16865                              if (hasContentEditableState) {
16866                                  contentEditable = lastContentEditable; // Restore last contentEditable state from stack
16867                              }
16868  
16869                              // End the last wrapper
16870                              currentWrapElm = 0;
16871                          }
16872                      };
16873  
16874                      // Process siblings from range
16875                      each(nodes, process);
16876                  });
16877  
16878                  // Wrap links inside as well, for example color inside a link when the wrapper is around the link
16879                  if (format.wrap_links === false) {
16880                      each(newWrappers, function(node) {
16881  						function process(node) {
16882                              var i, currentWrapElm, children;
16883  
16884                              if (node.nodeName === 'A') {
16885                                  currentWrapElm = dom.clone(wrapElm, FALSE);
16886                                  newWrappers.push(currentWrapElm);
16887  
16888                                  children = tinymce.grep(node.childNodes);
16889                                  for (i = 0; i < children.length; i++)
16890                                      currentWrapElm.appendChild(children[i]);
16891  
16892                                  node.appendChild(currentWrapElm);
16893                              }
16894  
16895                              each(tinymce.grep(node.childNodes), process);
16896                          };
16897  
16898                          process(node);
16899                      });
16900                  }
16901  
16902                  // Cleanup
16903                  
16904                  each(newWrappers, function(node) {
16905                      var childCount;
16906  
16907  					function getChildCount(node) {
16908                          var count = 0;
16909  
16910                          each(node.childNodes, function(node) {
16911                              if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
16912                                  count++;
16913                          });
16914  
16915                          return count;
16916                      };
16917  
16918  					function mergeStyles(node) {
16919                          var child, clone;
16920  
16921                          each(node.childNodes, function(node) {
16922                              if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
16923                                  child = node;
16924                                  return FALSE; // break loop
16925                              }
16926                          });
16927  
16928                          // If child was found and of the same type as the current node
16929                          if (child && matchName(child, format)) {
16930                              clone = dom.clone(child, FALSE);
16931                              setElementFormat(clone);
16932  
16933                              dom.replace(clone, node, TRUE);
16934                              dom.remove(child, 1);
16935                          }
16936  
16937                          return clone || node;
16938                      };
16939  
16940                      childCount = getChildCount(node);
16941  
16942                      // Remove empty nodes but only if there is multiple wrappers and they are not block
16943                      // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
16944                      if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
16945                          dom.remove(node, 1);
16946                          return;
16947                      }
16948  
16949                      if (format.inline || format.wrapper) {
16950                          // Merges the current node with it's children of similar type to reduce the number of elements
16951                          if (!format.exact && childCount === 1)
16952                              node = mergeStyles(node);
16953  
16954                          // Remove/merge children
16955                          each(formatList, function(format) {
16956                              // Merge all children of similar type will move styles from child to parent
16957                              // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
16958                              // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
16959                              each(dom.select(format.inline, node), function(child) {
16960                                  var parent;
16961  
16962                                  // When wrap_links is set to false we don't want
16963                                  // to remove the format on children within links
16964                                  if (format.wrap_links === false) {
16965                                      parent = child.parentNode;
16966  
16967                                      do {
16968                                          if (parent.nodeName === 'A')
16969                                              return;
16970                                      } while (parent = parent.parentNode);
16971                                  }
16972  
16973                                  removeFormat(format, vars, child, format.exact ? child : null);
16974                              });
16975                          });
16976  
16977                          // Remove child if direct parent is of same type
16978                          if (matchNode(node.parentNode, name, vars)) {
16979                              dom.remove(node, 1);
16980                              node = 0;
16981                              return TRUE;
16982                          }
16983  
16984                          // Look for parent with similar style format
16985                          if (format.merge_with_parents) {
16986                              dom.getParent(node.parentNode, function(parent) {
16987                                  if (matchNode(parent, name, vars)) {
16988                                      dom.remove(node, 1);
16989                                      node = 0;
16990                                      return TRUE;
16991                                  }
16992                              });
16993                          }
16994  
16995                          // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
16996                          if (node && format.merge_siblings !== false) {
16997                              node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
16998                              node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
16999                          }
17000                      }
17001                  });
17002              };
17003  
17004              if (format) {
17005                  if (node) {
17006                      if (node.nodeType) {
17007                          rng = dom.createRng();
17008                          rng.setStartBefore(node);
17009                          rng.setEndAfter(node);
17010                          applyRngStyle(expandRng(rng, formatList), null, true);
17011                      } else {
17012                          applyRngStyle(node, null, true);
17013                      }
17014                  } else {
17015                      if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
17016                          // Obtain selection node before selection is unselected by applyRngStyle()
17017                          var curSelNode = ed.selection.getNode();
17018  
17019                          // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
17020                          // It's kind of a hack but people should be using the default block type P since all desktop editors work that way
17021                          if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
17022                              apply(formatList[0].defaultBlock);
17023                          }
17024  
17025                          // Apply formatting to selection
17026                          ed.selection.setRng(adjustSelectionToVisibleSelection());
17027                          bookmark = selection.getBookmark();
17028                          applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
17029  
17030                          // Colored nodes should be underlined so that the color of the underline matches the text color.
17031                          if (format.styles && (format.styles.color || format.styles.textDecoration)) {
17032                              tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
17033                              processUnderlineAndColor(curSelNode);
17034                          }
17035  
17036                          selection.moveToBookmark(bookmark);
17037                          moveStart(selection.getRng(TRUE));
17038                          ed.nodeChanged();
17039                      } else
17040                          performCaretAction('apply', name, vars);
17041                  }
17042              }
17043          };
17044  
17045  		function remove(name, vars, node) {
17046              var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
17047  
17048              // Merges the styles for each node
17049  			function process(node) {
17050                  var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
17051  
17052                  // Skip on text nodes as they have neither format to remove nor children
17053                  if (node.nodeType === 3) {
17054                      return;
17055                  }
17056  
17057                  // Node has a contentEditable value
17058                  if (node.nodeType === 1 && getContentEditable(node)) {
17059                      lastContentEditable = contentEditable;
17060                      contentEditable = getContentEditable(node) === "true";
17061                      hasContentEditableState = true; // We don't want to wrap the container only it's children
17062                  }
17063  
17064                  // Grab the children first since the nodelist might be changed
17065                  children = tinymce.grep(node.childNodes);
17066  
17067                  // Process current node
17068                  if (contentEditable && !hasContentEditableState) {
17069                      for (i = 0, l = formatList.length; i < l; i++) {
17070                          if (removeFormat(formatList[i], vars, node, node))
17071                              break;
17072                      }
17073                  }
17074  
17075                  // Process the children
17076                  if (format.deep) {
17077                      if (children.length) {                    
17078                          for (i = 0, l = children.length; i < l; i++)
17079                              process(children[i]);
17080  
17081                          if (hasContentEditableState) {
17082                              contentEditable = lastContentEditable; // Restore last contentEditable state from stack
17083                          }
17084                      }
17085                  }
17086              };
17087  
17088  			function findFormatRoot(container) {
17089                  var formatRoot;
17090  
17091                  // Find format root
17092                  each(getParents(container.parentNode).reverse(), function(parent) {
17093                      var format;
17094  
17095                      // Find format root element
17096                      if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
17097                          // Is the node matching the format we are looking for
17098                          format = matchNode(parent, name, vars);
17099                          if (format && format.split !== false)
17100                              formatRoot = parent;
17101                      }
17102                  });
17103  
17104                  return formatRoot;
17105              };
17106  
17107  			function wrapAndSplit(format_root, container, target, split) {
17108                  var parent, clone, lastClone, firstClone, i, formatRootParent;
17109  
17110                  // Format root found then clone formats and split it
17111                  if (format_root) {
17112                      formatRootParent = format_root.parentNode;
17113  
17114                      for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
17115                          clone = dom.clone(parent, FALSE);
17116  
17117                          for (i = 0; i < formatList.length; i++) {
17118                              if (removeFormat(formatList[i], vars, clone, clone)) {
17119                                  clone = 0;
17120                                  break;
17121                              }
17122                          }
17123  
17124                          // Build wrapper node
17125                          if (clone) {
17126                              if (lastClone)
17127                                  clone.appendChild(lastClone);
17128  
17129                              if (!firstClone)
17130                                  firstClone = clone;
17131  
17132                              lastClone = clone;
17133                          }
17134                      }
17135  
17136                      // Never split block elements if the format is mixed
17137                      if (split && (!format.mixed || !isBlock(format_root)))
17138                          container = dom.split(format_root, container);
17139  
17140                      // Wrap container in cloned formats
17141                      if (lastClone) {
17142                          target.parentNode.insertBefore(lastClone, target);
17143                          firstClone.appendChild(target);
17144                      }
17145                  }
17146  
17147                  return container;
17148              };
17149  
17150  			function splitToFormatRoot(container) {
17151                  return wrapAndSplit(findFormatRoot(container), container, container, true);
17152              };
17153  
17154  			function unwrap(start) {
17155                  var node = dom.get(start ? '_start' : '_end'),
17156                      out = node[start ? 'firstChild' : 'lastChild'];
17157  
17158                  // If the end is placed within the start the result will be removed
17159                  // So this checks if the out node is a bookmark node if it is it
17160                  // checks for another more suitable node
17161                  if (isBookmarkNode(out))
17162                      out = out[start ? 'firstChild' : 'lastChild'];
17163  
17164                  dom.remove(node, true);
17165  
17166                  return out;
17167              };
17168  
17169  			function removeRngStyle(rng) {
17170                  var startContainer, endContainer, node;
17171  
17172                  rng = expandRng(rng, formatList, TRUE);
17173  
17174                  if (format.split) {
17175                      startContainer = getContainer(rng, TRUE);
17176                      endContainer = getContainer(rng);
17177  
17178                      if (startContainer != endContainer) {
17179                          // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
17180                          // This will happen if you tripple click a table cell and use remove formatting
17181                          if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
17182                              startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
17183                          }
17184  
17185                          // Wrap start/end nodes in span element since these might be cloned/moved
17186                          startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
17187                          endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
17188  
17189                          // Split start/end
17190                          splitToFormatRoot(startContainer);
17191                          splitToFormatRoot(endContainer);
17192  
17193                          // Unwrap start/end to get real elements again
17194                          startContainer = unwrap(TRUE);
17195                          endContainer = unwrap();
17196                      } else
17197                          startContainer = endContainer = splitToFormatRoot(startContainer);
17198  
17199                      // Update range positions since they might have changed after the split operations
17200                      rng.startContainer = startContainer.parentNode;
17201                      rng.startOffset = nodeIndex(startContainer);
17202                      rng.endContainer = endContainer.parentNode;
17203                      rng.endOffset = nodeIndex(endContainer) + 1;
17204                  }
17205  
17206                  // Remove items between start/end
17207                  rangeUtils.walk(rng, function(nodes) {
17208                      each(nodes, function(node) {
17209                          process(node);
17210  
17211                          // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
17212                          if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
17213                              removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
17214                          }
17215                      });
17216                  });
17217              };
17218  
17219              // Handle node
17220              if (node) {
17221                  if (node.nodeType) {
17222                      rng = dom.createRng();
17223                      rng.setStartBefore(node);
17224                      rng.setEndAfter(node);
17225                      removeRngStyle(rng);
17226                  } else {
17227                      removeRngStyle(node);
17228                  }
17229  
17230                  return;
17231              }
17232  
17233              if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
17234                  bookmark = selection.getBookmark();
17235                  removeRngStyle(selection.getRng(TRUE));
17236                  selection.moveToBookmark(bookmark);
17237  
17238                  // Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
17239                  if (format.inline && match(name, vars, selection.getStart())) {
17240                      moveStart(selection.getRng(true));
17241                  }
17242  
17243                  ed.nodeChanged();
17244              } else
17245                  performCaretAction('remove', name, vars);
17246          };
17247  
17248  		function toggle(name, vars, node) {
17249              var fmt = get(name);
17250  
17251              if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
17252                  remove(name, vars, node);
17253              else
17254                  apply(name, vars, node);
17255          };
17256  
17257  		function matchNode(node, name, vars, similar) {
17258              var formatList = get(name), format, i, classes;
17259  
17260  			function matchItems(node, format, item_name) {
17261                  var key, value, items = format[item_name], i;
17262  
17263                  // Custom match
17264                  if (format.onmatch) {
17265                      return format.onmatch(node, format, item_name);
17266                  }
17267  
17268                  // Check all items
17269                  if (items) {
17270                      // Non indexed object
17271                      if (items.length === undef) {
17272                          for (key in items) {
17273                              if (items.hasOwnProperty(key)) {
17274                                  if (item_name === 'attributes')
17275                                      value = dom.getAttrib(node, key);
17276                                  else
17277                                      value = getStyle(node, key);
17278  
17279                                  if (similar && !value && !format.exact)
17280                                      return;
17281  
17282                                  if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
17283                                      return;
17284                              }
17285                          }
17286                      } else {
17287                          // Only one match needed for indexed arrays
17288                          for (i = 0; i < items.length; i++) {
17289                              if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
17290                                  return format;
17291                          }
17292                      }
17293                  }
17294  
17295                  return format;
17296              };
17297  
17298              if (formatList && node) {
17299                  // Check each format in list
17300                  for (i = 0; i < formatList.length; i++) {
17301                      format = formatList[i];
17302  
17303                      // Name name, attributes, styles and classes
17304                      if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
17305                          // Match classes
17306                          if (classes = format.classes) {
17307                              for (i = 0; i < classes.length; i++) {
17308                                  if (!dom.hasClass(node, classes[i]))
17309                                      return;
17310                              }
17311                          }
17312  
17313                          return format;
17314                      }
17315                  }
17316              }
17317          };
17318  
17319  		function match(name, vars, node) {
17320              var startNode;
17321  
17322  			function matchParents(node) {
17323                  // Find first node with similar format settings
17324                  node = dom.getParent(node, function(node) {
17325                      return !!matchNode(node, name, vars, true);
17326                  });
17327  
17328                  // Do an exact check on the similar format element
17329                  return matchNode(node, name, vars);
17330              };
17331  
17332              // Check specified node
17333              if (node)
17334                  return matchParents(node);
17335  
17336              // Check selected node
17337              node = selection.getNode();
17338              if (matchParents(node))
17339                  return TRUE;
17340  
17341              // Check start node if it's different
17342              startNode = selection.getStart();
17343              if (startNode != node) {
17344                  if (matchParents(startNode))
17345                      return TRUE;
17346              }
17347  
17348              return FALSE;
17349          };
17350  
17351  		function matchAll(names, vars) {
17352              var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
17353  
17354              // Check start of selection for formats
17355              startElement = selection.getStart();
17356              dom.getParent(startElement, function(node) {
17357                  var i, name;
17358  
17359                  for (i = 0; i < names.length; i++) {
17360                      name = names[i];
17361  
17362                      if (!checkedMap[name] && matchNode(node, name, vars)) {
17363                          checkedMap[name] = true;
17364                          matchedFormatNames.push(name);
17365                      }
17366                  }
17367              }, dom.getRoot());
17368  
17369              return matchedFormatNames;
17370          };
17371  
17372  		function canApply(name) {
17373              var formatList = get(name), startNode, parents, i, x, selector;
17374  
17375              if (formatList) {
17376                  startNode = selection.getStart();
17377                  parents = getParents(startNode);
17378  
17379                  for (x = formatList.length - 1; x >= 0; x--) {
17380                      selector = formatList[x].selector;
17381  
17382                      // Format is not selector based, then always return TRUE
17383                      if (!selector)
17384                          return TRUE;
17385  
17386                      for (i = parents.length - 1; i >= 0; i--) {
17387                          if (dom.is(parents[i], selector))
17388                              return TRUE;
17389                      }
17390                  }
17391              }
17392  
17393              return FALSE;
17394          };
17395  
17396  		function formatChanged(formats, callback, similar) {
17397              var currentFormats;
17398  
17399              // Setup format node change logic
17400              if (!formatChangeData) {
17401                  formatChangeData = {};
17402                  currentFormats = {};
17403  
17404                  ed.onNodeChange.addToTop(function(ed, cm, node) {
17405                      var parents = getParents(node), matchedFormats = {};
17406  
17407                      // Check for new formats
17408                      each(formatChangeData, function(callbacks, format) {
17409                          each(parents, function(node) {
17410                              if (matchNode(node, format, {}, callbacks.similar)) {
17411                                  if (!currentFormats[format]) {
17412                                      // Execute callbacks
17413                                      each(callbacks, function(callback) {
17414                                          callback(true, {node: node, format: format, parents: parents});
17415                                      });
17416  
17417                                      currentFormats[format] = callbacks;
17418                                  }
17419  
17420                                  matchedFormats[format] = callbacks;
17421                                  return false;
17422                              }
17423                          });
17424                      });
17425  
17426                      // Check if current formats still match
17427                      each(currentFormats, function(callbacks, format) {
17428                          if (!matchedFormats[format]) {
17429                              delete currentFormats[format];
17430  
17431                              each(callbacks, function(callback) {
17432                                  callback(false, {node: node, format: format, parents: parents});
17433                              });
17434                          }
17435                      });
17436                  });
17437              }
17438  
17439              // Add format listeners
17440              each(formats.split(','), function(format) {
17441                  if (!formatChangeData[format]) {
17442                      formatChangeData[format] = [];
17443                      formatChangeData[format].similar = similar;
17444                  }
17445  
17446                  formatChangeData[format].push(callback);
17447              });
17448  
17449              return this;
17450          };
17451  
17452          // Expose to public
17453          tinymce.extend(this, {
17454              get : get,
17455              register : register,
17456              apply : apply,
17457              remove : remove,
17458              toggle : toggle,
17459              match : match,
17460              matchAll : matchAll,
17461              matchNode : matchNode,
17462              canApply : canApply,
17463              formatChanged: formatChanged
17464          });
17465  
17466          // Initialize
17467          defaultFormats();
17468          addKeyboardShortcuts();
17469  
17470          // Private functions
17471  
17472  		function matchName(node, format) {
17473              // Check for inline match
17474              if (isEq(node, format.inline))
17475                  return TRUE;
17476  
17477              // Check for block match
17478              if (isEq(node, format.block))
17479                  return TRUE;
17480  
17481              // Check for selector match
17482              if (format.selector)
17483                  return dom.is(node, format.selector);
17484          };
17485  
17486  		function isEq(str1, str2) {
17487              str1 = str1 || '';
17488              str2 = str2 || '';
17489  
17490              str1 = '' + (str1.nodeName || str1);
17491              str2 = '' + (str2.nodeName || str2);
17492  
17493              return str1.toLowerCase() == str2.toLowerCase();
17494          };
17495  
17496  		function getStyle(node, name) {
17497              var styleVal = dom.getStyle(node, name);
17498  
17499              // Force the format to hex
17500              if (name == 'color' || name == 'backgroundColor')
17501                  styleVal = dom.toHex(styleVal);
17502  
17503              // Opera will return bold as 700
17504              if (name == 'fontWeight' && styleVal == 700)
17505                  styleVal = 'bold';
17506  
17507              return '' + styleVal;
17508          };
17509  
17510  		function replaceVars(value, vars) {
17511              if (typeof(value) != "string")
17512                  value = value(vars);
17513              else if (vars) {
17514                  value = value.replace(/%(\w+)/g, function(str, name) {
17515                      return vars[name] || str;
17516                  });
17517              }
17518  
17519              return value;
17520          };
17521  
17522  		function isWhiteSpaceNode(node) {
17523              return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
17524          };
17525  
17526  		function wrap(node, name, attrs) {
17527              var wrapper = dom.create(name, attrs);
17528  
17529              node.parentNode.insertBefore(wrapper, node);
17530              wrapper.appendChild(node);
17531  
17532              return wrapper;
17533          };
17534  
17535  		function expandRng(rng, format, remove) {
17536              var sibling, lastIdx, leaf, endPoint,
17537                  startContainer = rng.startContainer,
17538                  startOffset = rng.startOffset,
17539                  endContainer = rng.endContainer,
17540                  endOffset = rng.endOffset;
17541  
17542              // This function walks up the tree if there is no siblings before/after the node
17543  			function findParentContainer(start) {
17544                  var container, parent, child, sibling, siblingName, root;
17545  
17546                  container = parent = start ? startContainer : endContainer;
17547                  siblingName = start ? 'previousSibling' : 'nextSibling';
17548                  root = dom.getRoot();
17549  
17550  				function isBogusBr(node) {
17551                      return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;
17552                  };
17553  
17554                  // If it's a text node and the offset is inside the text
17555                  if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
17556                      if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
17557                          return container;
17558                      }
17559                  }
17560  
17561                  for (;;) {
17562                      // Stop expanding on block elements
17563                      if (!format[0].block_expand && isBlock(parent))
17564                          return parent;
17565  
17566                      // Walk left/right
17567                      for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
17568                          if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {
17569                              return parent;
17570                          }
17571                      }
17572  
17573                      // Check if we can move up are we at root level or body level
17574                      if (parent.parentNode == root) {
17575                          container = parent;
17576                          break;
17577                      }
17578  
17579                      parent = parent.parentNode;
17580                  }
17581  
17582                  return container;
17583              };
17584  
17585              // This function walks down the tree to find the leaf at the selection.
17586              // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
17587  			function findLeaf(node, offset) {
17588                  if (offset === undef)
17589                      offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17590                  while (node && node.hasChildNodes()) {
17591                      node = node.childNodes[offset];
17592                      if (node)
17593                          offset = node.nodeType === 3 ? node.length : node.childNodes.length;
17594                  }
17595                  return { node: node, offset: offset };
17596              }
17597  
17598              // If index based start position then resolve it
17599              if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
17600                  lastIdx = startContainer.childNodes.length - 1;
17601                  startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
17602  
17603                  if (startContainer.nodeType == 3)
17604                      startOffset = 0;
17605              }
17606  
17607              // If index based end position then resolve it
17608              if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
17609                  lastIdx = endContainer.childNodes.length - 1;
17610                  endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
17611  
17612                  if (endContainer.nodeType == 3)
17613                      endOffset = endContainer.nodeValue.length;
17614              }
17615  
17616              // Expands the node to the closes contentEditable false element if it exists
17617  			function findParentContentEditable(node) {
17618                  var parent = node;
17619  
17620                  while (parent) {
17621                      if (parent.nodeType === 1 && getContentEditable(parent)) {
17622                          return getContentEditable(parent) === "false" ? parent : node;
17623                      }
17624  
17625                      parent = parent.parentNode;
17626                  }
17627  
17628                  return node;
17629              };
17630  
17631  			function findWordEndPoint(container, offset, start) {
17632                  var walker, node, pos, lastTextNode;
17633  
17634  				function findSpace(node, offset) {
17635                      var pos, pos2, str = node.nodeValue;
17636  
17637                      if (typeof(offset) == "undefined") {
17638                          offset = start ? str.length : 0;
17639                      }
17640  
17641                      if (start) {
17642                          pos = str.lastIndexOf(' ', offset);
17643                          pos2 = str.lastIndexOf('\u00a0', offset);
17644                          pos = pos > pos2 ? pos : pos2;
17645  
17646                          // Include the space on remove to avoid tag soup
17647                          if (pos !== -1 && !remove) {
17648                              pos++;
17649                          }
17650                      } else {
17651                          pos = str.indexOf(' ', offset);
17652                          pos2 = str.indexOf('\u00a0', offset);
17653                          pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
17654                      }
17655  
17656                      return pos;
17657                  };
17658  
17659                  if (container.nodeType === 3) {
17660                      pos = findSpace(container, offset);
17661  
17662                      if (pos !== -1) {
17663                          return {container : container, offset : pos};
17664                      }
17665  
17666                      lastTextNode = container;
17667                  }
17668  
17669                  // Walk the nodes inside the block
17670                  walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
17671                  while (node = walker[start ? 'prev' : 'next']()) {
17672                      if (node.nodeType === 3) {
17673                          lastTextNode = node;
17674                          pos = findSpace(node);
17675  
17676                          if (pos !== -1) {
17677                              return {container : node, offset : pos};
17678                          }
17679                      } else if (isBlock(node)) {
17680                          break;
17681                      }
17682                  }
17683  
17684                  if (lastTextNode) {
17685                      if (start) {
17686                          offset = 0;
17687                      } else {
17688                          offset = lastTextNode.length;
17689                      }
17690  
17691                      return {container: lastTextNode, offset: offset};
17692                  }
17693              };
17694  
17695  			function findSelectorEndPoint(container, sibling_name) {
17696                  var parents, i, y, curFormat;
17697  
17698                  if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
17699                      container = container[sibling_name];
17700  
17701                  parents = getParents(container);
17702                  for (i = 0; i < parents.length; i++) {
17703                      for (y = 0; y < format.length; y++) {
17704                          curFormat = format[y];
17705  
17706                          // If collapsed state is set then skip formats that doesn't match that
17707                          if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
17708                              continue;
17709  
17710                          if (dom.is(parents[i], curFormat.selector))
17711                              return parents[i];
17712                      }
17713                  }
17714  
17715                  return container;
17716              };
17717  
17718  			function findBlockEndPoint(container, sibling_name, sibling_name2) {
17719                  var node;
17720  
17721                  // Expand to block of similar type
17722                  if (!format[0].wrapper)
17723                      node = dom.getParent(container, format[0].block);
17724  
17725                  // Expand to first wrappable block element or any block element
17726                  if (!node)
17727                      node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock);
17728  
17729                  // Exclude inner lists from wrapping
17730                  if (node && format[0].wrapper)
17731                      node = getParents(node, 'ul,ol').reverse()[0] || node;
17732  
17733                  // Didn't find a block element look for first/last wrappable element
17734                  if (!node) {
17735                      node = container;
17736  
17737                      while (node[sibling_name] && !isBlock(node[sibling_name])) {
17738                          node = node[sibling_name];
17739  
17740                          // Break on BR but include it will be removed later on
17741                          // we can't remove it now since we need to check if it can be wrapped
17742                          if (isEq(node, 'br'))
17743                              break;
17744                      }
17745                  }
17746  
17747                  return node || container;
17748              };
17749  
17750              // Expand to closest contentEditable element
17751              startContainer = findParentContentEditable(startContainer);
17752              endContainer = findParentContentEditable(endContainer);
17753  
17754              // Exclude bookmark nodes if possible
17755              if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
17756                  startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
17757                  startContainer = startContainer.nextSibling || startContainer;
17758  
17759                  if (startContainer.nodeType == 3)
17760                      startOffset = 0;
17761              }
17762  
17763              if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
17764                  endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
17765                  endContainer = endContainer.previousSibling || endContainer;
17766  
17767                  if (endContainer.nodeType == 3)
17768                      endOffset = endContainer.length;
17769              }
17770  
17771              if (format[0].inline) {
17772                  if (rng.collapsed) {
17773                      // Expand left to closest word boundery
17774                      endPoint = findWordEndPoint(startContainer, startOffset, true);
17775                      if (endPoint) {
17776                          startContainer = endPoint.container;
17777                          startOffset = endPoint.offset;
17778                      }
17779  
17780                      // Expand right to closest word boundery
17781                      endPoint = findWordEndPoint(endContainer, endOffset);
17782                      if (endPoint) {
17783                          endContainer = endPoint.container;
17784                          endOffset = endPoint.offset;
17785                      }
17786                  }
17787  
17788                  // Avoid applying formatting to a trailing space.
17789                  leaf = findLeaf(endContainer, endOffset);
17790                  if (leaf.node) {
17791                      while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
17792                          leaf = findLeaf(leaf.node.previousSibling);
17793  
17794                      if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
17795                              leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
17796  
17797                          if (leaf.offset > 1) {
17798                              endContainer = leaf.node;
17799                              endContainer.splitText(leaf.offset - 1);
17800                          }
17801                      }
17802                  }
17803              }
17804  
17805              // Move start/end point up the tree if the leaves are sharp and if we are in different containers
17806              // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
17807              // This will reduce the number of wrapper elements that needs to be created
17808              // Move start point up the tree
17809              if (format[0].inline || format[0].block_expand) {
17810                  if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
17811                      startContainer = findParentContainer(true);
17812                  }
17813  
17814                  if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
17815                      endContainer = findParentContainer();
17816                  }
17817              }
17818  
17819              // Expand start/end container to matching selector
17820              if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
17821                  // Find new startContainer/endContainer if there is better one
17822                  startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
17823                  endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
17824              }
17825  
17826              // Expand start/end container to matching block element or text node
17827              if (format[0].block || format[0].selector) {
17828                  // Find new startContainer/endContainer if there is better one
17829                  startContainer = findBlockEndPoint(startContainer, 'previousSibling');
17830                  endContainer = findBlockEndPoint(endContainer, 'nextSibling');
17831  
17832                  // Non block element then try to expand up the leaf
17833                  if (format[0].block) {
17834                      if (!isBlock(startContainer))
17835                          startContainer = findParentContainer(true);
17836  
17837                      if (!isBlock(endContainer))
17838                          endContainer = findParentContainer();
17839                  }
17840              }
17841  
17842              // Setup index for startContainer
17843              if (startContainer.nodeType == 1) {
17844                  startOffset = nodeIndex(startContainer);
17845                  startContainer = startContainer.parentNode;
17846              }
17847  
17848              // Setup index for endContainer
17849              if (endContainer.nodeType == 1) {
17850                  endOffset = nodeIndex(endContainer) + 1;
17851                  endContainer = endContainer.parentNode;
17852              }
17853  
17854              // Return new range like object
17855              return {
17856                  startContainer : startContainer,
17857                  startOffset : startOffset,
17858                  endContainer : endContainer,
17859                  endOffset : endOffset
17860              };
17861          }
17862  
17863  		function removeFormat(format, vars, node, compare_node) {
17864              var i, attrs, stylesModified;
17865  
17866              // Check if node matches format
17867              if (!matchName(node, format))
17868                  return FALSE;
17869  
17870              // Should we compare with format attribs and styles
17871              if (format.remove != 'all') {
17872                  // Remove styles
17873                  each(format.styles, function(value, name) {
17874                      value = replaceVars(value, vars);
17875  
17876                      // Indexed array
17877                      if (typeof(name) === 'number') {
17878                          name = value;
17879                          compare_node = 0;
17880                      }
17881  
17882                      if (!compare_node || isEq(getStyle(compare_node, name), value))
17883                          dom.setStyle(node, name, '');
17884  
17885                      stylesModified = 1;
17886                  });
17887  
17888                  // Remove style attribute if it's empty
17889                  if (stylesModified && dom.getAttrib(node, 'style') == '') {
17890                      node.removeAttribute('style');
17891                      node.removeAttribute('data-mce-style');
17892                  }
17893  
17894                  // Remove attributes
17895                  each(format.attributes, function(value, name) {
17896                      var valueOut;
17897  
17898                      value = replaceVars(value, vars);
17899  
17900                      // Indexed array
17901                      if (typeof(name) === 'number') {
17902                          name = value;
17903                          compare_node = 0;
17904                      }
17905  
17906                      if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
17907                          // Keep internal classes
17908                          if (name == 'class') {
17909                              value = dom.getAttrib(node, name);
17910                              if (value) {
17911                                  // Build new class value where everything is removed except the internal prefixed classes
17912                                  valueOut = '';
17913                                  each(value.split(/\s+/), function(cls) {
17914                                      if (/mce\w+/.test(cls))
17915                                          valueOut += (valueOut ? ' ' : '') + cls;
17916                                  });
17917  
17918                                  // We got some internal classes left
17919                                  if (valueOut) {
17920                                      dom.setAttrib(node, name, valueOut);
17921                                      return;
17922                                  }
17923                              }
17924                          }
17925  
17926                          // IE6 has a bug where the attribute doesn't get removed correctly
17927                          if (name == "class")
17928                              node.removeAttribute('className');
17929  
17930                          // Remove mce prefixed attributes
17931                          if (MCE_ATTR_RE.test(name))
17932                              node.removeAttribute('data-mce-' + name);
17933  
17934                          node.removeAttribute(name);
17935                      }
17936                  });
17937  
17938                  // Remove classes
17939                  each(format.classes, function(value) {
17940                      value = replaceVars(value, vars);
17941  
17942                      if (!compare_node || dom.hasClass(compare_node, value))
17943                          dom.removeClass(node, value);
17944                  });
17945  
17946                  // Check for non internal attributes
17947                  attrs = dom.getAttribs(node);
17948                  for (i = 0; i < attrs.length; i++) {
17949                      if (attrs[i].nodeName.indexOf('_') !== 0)
17950                          return FALSE;
17951                  }
17952              }
17953  
17954              // Remove the inline child if it's empty for example <b> or <span>
17955              if (format.remove != 'none') {
17956                  removeNode(node, format);
17957                  return TRUE;
17958              }
17959          };
17960  
17961  		function removeNode(node, format) {
17962              var parentNode = node.parentNode, rootBlockElm;
17963  
17964  			function find(node, next, inc) {
17965                  node = getNonWhiteSpaceSibling(node, next, inc);
17966  
17967                  return !node || (node.nodeName == 'BR' || isBlock(node));
17968              };
17969  
17970              if (format.block) {
17971                  if (!forcedRootBlock) {
17972                      // Append BR elements if needed before we remove the block
17973                      if (isBlock(node) && !isBlock(parentNode)) {
17974                          if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
17975                              node.insertBefore(dom.create('br'), node.firstChild);
17976  
17977                          if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
17978                              node.appendChild(dom.create('br'));
17979                      }
17980                  } else {
17981                      // Wrap the block in a forcedRootBlock if we are at the root of document
17982                      if (parentNode == dom.getRoot()) {
17983                          if (!format.list_block || !isEq(node, format.list_block)) {
17984                              each(tinymce.grep(node.childNodes), function(node) {
17985                                  if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
17986                                      if (!rootBlockElm)
17987                                          rootBlockElm = wrap(node, forcedRootBlock);
17988                                      else
17989                                          rootBlockElm.appendChild(node);
17990                                  } else
17991                                      rootBlockElm = 0;
17992                              });
17993                          }
17994                      }
17995                  }
17996              }
17997  
17998              // Never remove nodes that isn't the specified inline element if a selector is specified too
17999              if (format.selector && format.inline && !isEq(format.inline, node))
18000                  return;
18001  
18002              dom.remove(node, 1);
18003          };
18004  
18005  		function getNonWhiteSpaceSibling(node, next, inc) {
18006              if (node) {
18007                  next = next ? 'nextSibling' : 'previousSibling';
18008  
18009                  for (node = inc ? node : node[next]; node; node = node[next]) {
18010                      if (node.nodeType == 1 || !isWhiteSpaceNode(node))
18011                          return node;
18012                  }
18013              }
18014          };
18015  
18016  		function isBookmarkNode(node) {
18017              return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
18018          };
18019  
18020  		function mergeSiblings(prev, next) {
18021              var marker, sibling, tmpSibling;
18022  
18023  			function compareElements(node1, node2) {
18024                  // Not the same name
18025                  if (node1.nodeName != node2.nodeName)
18026                      return FALSE;
18027  
18028  				function getAttribs(node) {
18029                      var attribs = {};
18030  
18031                      each(dom.getAttribs(node), function(attr) {
18032                          var name = attr.nodeName.toLowerCase();
18033  
18034                          // Don't compare internal attributes or style
18035                          if (name.indexOf('_') !== 0 && name !== 'style')
18036                              attribs[name] = dom.getAttrib(node, name);
18037                      });
18038  
18039                      return attribs;
18040                  };
18041  
18042  				function compareObjects(obj1, obj2) {
18043                      var value, name;
18044  
18045                      for (name in obj1) {
18046                          // Obj1 has item obj2 doesn't have
18047                          if (obj1.hasOwnProperty(name)) {
18048                              value = obj2[name];
18049  
18050                              // Obj2 doesn't have obj1 item
18051                              if (value === undef)
18052                                  return FALSE;
18053  
18054                              // Obj2 item has a different value
18055                              if (obj1[name] != value)
18056                                  return FALSE;
18057  
18058                              // Delete similar value
18059                              delete obj2[name];
18060                          }
18061                      }
18062  
18063                      // Check if obj 2 has something obj 1 doesn't have
18064                      for (name in obj2) {
18065                          // Obj2 has item obj1 doesn't have
18066                          if (obj2.hasOwnProperty(name))
18067                              return FALSE;
18068                      }
18069  
18070                      return TRUE;
18071                  };
18072  
18073                  // Attribs are not the same
18074                  if (!compareObjects(getAttribs(node1), getAttribs(node2)))
18075                      return FALSE;
18076  
18077                  // Styles are not the same
18078                  if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
18079                      return FALSE;
18080  
18081                  return TRUE;
18082              };
18083  
18084  			function findElementSibling(node, sibling_name) {
18085                  for (sibling = node; sibling; sibling = sibling[sibling_name]) {
18086                      if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
18087                          return node;
18088  
18089                      if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
18090                          return sibling;
18091                  }
18092  
18093                  return node;
18094              };
18095  
18096              // Check if next/prev exists and that they are elements
18097              if (prev && next) {
18098                  // If previous sibling is empty then jump over it
18099                  prev = findElementSibling(prev, 'previousSibling');
18100                  next = findElementSibling(next, 'nextSibling');
18101  
18102                  // Compare next and previous nodes
18103                  if (compareElements(prev, next)) {
18104                      // Append nodes between
18105                      for (sibling = prev.nextSibling; sibling && sibling != next;) {
18106                          tmpSibling = sibling;
18107                          sibling = sibling.nextSibling;
18108                          prev.appendChild(tmpSibling);
18109                      }
18110  
18111                      // Remove next node
18112                      dom.remove(next);
18113  
18114                      // Move children into prev node
18115                      each(tinymce.grep(next.childNodes), function(node) {
18116                          prev.appendChild(node);
18117                      });
18118  
18119                      return prev;
18120                  }
18121              }
18122  
18123              return next;
18124          };
18125  
18126  		function getContainer(rng, start) {
18127              var container, offset, lastIdx, walker;
18128  
18129              container = rng[start ? 'startContainer' : 'endContainer'];
18130              offset = rng[start ? 'startOffset' : 'endOffset'];
18131  
18132              if (container.nodeType == 1) {
18133                  lastIdx = container.childNodes.length - 1;
18134  
18135                  if (!start && offset)
18136                      offset--;
18137  
18138                  container = container.childNodes[offset > lastIdx ? lastIdx : offset];
18139              }
18140  
18141              // If start text node is excluded then walk to the next node
18142              if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
18143                  container = new TreeWalker(container, ed.getBody()).next() || container;
18144              }
18145  
18146              // If end text node is excluded then walk to the previous node
18147              if (container.nodeType === 3 && !start && offset === 0) {
18148                  container = new TreeWalker(container, ed.getBody()).prev() || container;
18149              }
18150  
18151              return container;
18152          };
18153  
18154  		function performCaretAction(type, name, vars) {
18155              var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
18156  
18157              // Creates a caret container bogus element
18158  			function createCaretContainer(fill) {
18159                  var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
18160  
18161                  if (fill) {
18162                      caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
18163                  }
18164  
18165                  return caretContainer;
18166              };
18167  
18168  			function isCaretContainerEmpty(node, nodes) {
18169                  while (node) {
18170                      if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
18171                          return false;
18172                      }
18173  
18174                      // Collect nodes
18175                      if (nodes && node.nodeType === 1) {
18176                          nodes.push(node);
18177                      }
18178  
18179                      node = node.firstChild;
18180                  }
18181  
18182                  return true;
18183              };
18184              
18185              // Returns any parent caret container element
18186  			function getParentCaretContainer(node) {
18187                  while (node) {
18188                      if (node.id === caretContainerId) {
18189                          return node;
18190                      }
18191  
18192                      node = node.parentNode;
18193                  }
18194              };
18195  
18196              // Finds the first text node in the specified node
18197  			function findFirstTextNode(node) {
18198                  var walker;
18199  
18200                  if (node) {
18201                      walker = new TreeWalker(node, node);
18202  
18203                      for (node = walker.current(); node; node = walker.next()) {
18204                          if (node.nodeType === 3) {
18205                              return node;
18206                          }
18207                      }
18208                  }
18209              };
18210  
18211              // Removes the caret container for the specified node or all on the current document
18212  			function removeCaretContainer(node, move_caret) {
18213                  var child, rng;
18214  
18215                  if (!node) {
18216                      node = getParentCaretContainer(selection.getStart());
18217  
18218                      if (!node) {
18219                          while (node = dom.get(caretContainerId)) {
18220                              removeCaretContainer(node, false);
18221                          }
18222                      }
18223                  } else {
18224                      rng = selection.getRng(true);
18225  
18226                      if (isCaretContainerEmpty(node)) {
18227                          if (move_caret !== false) {
18228                              rng.setStartBefore(node);
18229                              rng.setEndBefore(node);
18230                          }
18231  
18232                          dom.remove(node);
18233                      } else {
18234                          child = findFirstTextNode(node);
18235  
18236                          if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
18237                              child = child.deleteData(0, 1);
18238                          }
18239  
18240                          dom.remove(node, 1);
18241                      }
18242  
18243                      selection.setRng(rng);
18244                  }
18245              };
18246              
18247              // Applies formatting to the caret postion
18248  			function applyCaretFormat() {
18249                  var rng, caretContainer, textNode, offset, bookmark, container, text;
18250  
18251                  rng = selection.getRng(true);
18252                  offset = rng.startOffset;
18253                  container = rng.startContainer;
18254                  text = container.nodeValue;
18255  
18256                  caretContainer = getParentCaretContainer(selection.getStart());
18257                  if (caretContainer) {
18258                      textNode = findFirstTextNode(caretContainer);
18259                  }
18260  
18261                  // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
18262                  if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
18263                      // Get bookmark of caret position
18264                      bookmark = selection.getBookmark();
18265  
18266                      // Collapse bookmark range (WebKit)
18267                      rng.collapse(true);
18268  
18269                      // Expand the range to the closest word and split it at those points
18270                      rng = expandRng(rng, get(name));
18271                      rng = rangeUtils.split(rng);
18272  
18273                      // Apply the format to the range
18274                      apply(name, vars, rng);
18275  
18276                      // Move selection back to caret position
18277                      selection.moveToBookmark(bookmark);
18278                  } else {
18279                      if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
18280                          caretContainer = createCaretContainer(true);
18281                          textNode = caretContainer.firstChild;
18282  
18283                          rng.insertNode(caretContainer);
18284                          offset = 1;
18285  
18286                          apply(name, vars, caretContainer);
18287                      } else {
18288                          apply(name, vars, caretContainer);
18289                      }
18290  
18291                      // Move selection to text node
18292                      selection.setCursorLocation(textNode, offset);
18293                  }
18294              };
18295  
18296  			function removeCaretFormat() {
18297                  var rng = selection.getRng(true), container, offset, bookmark,
18298                      hasContentAfter, node, formatNode, parents = [], i, caretContainer;
18299  
18300                  container = rng.startContainer;
18301                  offset = rng.startOffset;
18302                  node = container;
18303  
18304                  if (container.nodeType == 3) {
18305                      if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
18306                          hasContentAfter = true;
18307                      }
18308  
18309                      node = node.parentNode;
18310                  }
18311  
18312                  while (node) {
18313                      if (matchNode(node, name, vars)) {
18314                          formatNode = node;
18315                          break;
18316                      }
18317  
18318                      if (node.nextSibling) {
18319                          hasContentAfter = true;
18320                      }
18321  
18322                      parents.push(node);
18323                      node = node.parentNode;
18324                  }
18325  
18326                  // Node doesn't have the specified format
18327                  if (!formatNode) {
18328                      return;
18329                  }
18330  
18331                  // Is there contents after the caret then remove the format on the element
18332                  if (hasContentAfter) {
18333                      // Get bookmark of caret position
18334                      bookmark = selection.getBookmark();
18335  
18336                      // Collapse bookmark range (WebKit)
18337                      rng.collapse(true);
18338  
18339                      // Expand the range to the closest word and split it at those points
18340                      rng = expandRng(rng, get(name), true);
18341                      rng = rangeUtils.split(rng);
18342  
18343                      // Remove the format from the range
18344                      remove(name, vars, rng);
18345  
18346                      // Move selection back to caret position
18347                      selection.moveToBookmark(bookmark);
18348                  } else {
18349                      caretContainer = createCaretContainer();
18350  
18351                      node = caretContainer;
18352                      for (i = parents.length - 1; i >= 0; i--) {
18353                          node.appendChild(dom.clone(parents[i], false));
18354                          node = node.firstChild;
18355                      }
18356  
18357                      // Insert invisible character into inner most format element
18358                      node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
18359                      node = node.firstChild;
18360  
18361                      var block = dom.getParent(formatNode, isTextBlock);
18362  
18363                      if (block && dom.isEmpty(block)) {
18364                          // Replace formatNode with caretContainer when removing format from empty block like <p><b>|</b></p>
18365                          formatNode.parentNode.replaceChild(caretContainer, formatNode);
18366                      } else {
18367                          // Insert caret container after the formated node
18368                          dom.insertAfter(caretContainer, formatNode);
18369                      }
18370  
18371                      // Move selection to text node
18372                      selection.setCursorLocation(node, 1);
18373  
18374                      // If the formatNode is empty, we can remove it safely. 
18375                      if (dom.isEmpty(formatNode)) {
18376                          dom.remove(formatNode);
18377                      }
18378                  }
18379              };
18380  
18381              // Checks if the parent caret container node isn't empty if that is the case it
18382              // will remove the bogus state on all children that isn't empty
18383  			function unmarkBogusCaretParents() {
18384                  var i, caretContainer, node;
18385  
18386                  caretContainer = getParentCaretContainer(selection.getStart());
18387                  if (caretContainer && !dom.isEmpty(caretContainer)) {
18388                      tinymce.walk(caretContainer, function(node) {
18389                          if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
18390                              dom.setAttrib(node, 'data-mce-bogus', null);
18391                          }
18392                      }, 'childNodes');
18393                  }
18394              };
18395  
18396              // Only bind the caret events once
18397              if (!self._hasCaretEvents) {
18398                  // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
18399                  ed.onBeforeGetContent.addToTop(function() {
18400                      var nodes = [], i;
18401  
18402                      if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
18403                          // Mark children
18404                          i = nodes.length;
18405                          while (i--) {
18406                              dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
18407                          }
18408                      }
18409                  });
18410  
18411                  // Remove caret container on mouse up and on key up
18412                  tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
18413                      ed[name].addToTop(function() {
18414                          removeCaretContainer();
18415                          unmarkBogusCaretParents();
18416                      });
18417                  });
18418  
18419                  // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
18420                  ed.onKeyDown.addToTop(function(ed, e) {
18421                      var keyCode = e.keyCode;
18422  
18423                      if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
18424                          removeCaretContainer(getParentCaretContainer(selection.getStart()));
18425                      }
18426  
18427                      unmarkBogusCaretParents();
18428                  });
18429  
18430                  // Remove bogus state if they got filled by contents using editor.selection.setContent
18431                  selection.onSetContent.add(unmarkBogusCaretParents);
18432  
18433                  self._hasCaretEvents = true;
18434              }
18435  
18436              // Do apply or remove caret format
18437              if (type == "apply") {
18438                  applyCaretFormat();
18439              } else {
18440                  removeCaretFormat();
18441              }
18442          };
18443  
18444  		function moveStart(rng) {
18445              var container = rng.startContainer,
18446                      offset = rng.startOffset, isAtEndOfText,
18447                      walker, node, nodes, tmpNode;
18448  
18449              // Convert text node into index if possible
18450              if (container.nodeType == 3 && offset >= container.nodeValue.length) {
18451                  // Get the parent container location and walk from there
18452                  offset = nodeIndex(container);
18453                  container = container.parentNode;
18454                  isAtEndOfText = true;
18455              }
18456  
18457              // Move startContainer/startOffset in to a suitable node
18458              if (container.nodeType == 1) {
18459                  nodes = container.childNodes;
18460                  container = nodes[Math.min(offset, nodes.length - 1)];
18461                  walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
18462  
18463                  // If offset is at end of the parent node walk to the next one
18464                  if (offset > nodes.length - 1 || isAtEndOfText)
18465                      walker.next();
18466  
18467                  for (node = walker.current(); node; node = walker.next()) {
18468                      if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
18469                          // IE has a "neat" feature where it moves the start node into the closest element
18470                          // we can avoid this by inserting an element before it and then remove it after we set the selection
18471                          tmpNode = dom.create('a', null, INVISIBLE_CHAR);
18472                          node.parentNode.insertBefore(tmpNode, node);
18473  
18474                          // Set selection and remove tmpNode
18475                          rng.setStart(node, 0);
18476                          selection.setRng(rng);
18477                          dom.remove(tmpNode);
18478  
18479                          return;
18480                      }
18481                  }
18482              }
18483          };
18484      };
18485  })(tinymce);
18486  tinymce.onAddEditor.add(function(tinymce, ed) {
18487      var filters, fontSizes, dom, settings = ed.settings;
18488  
18489  	function replaceWithSpan(node, styles) {
18490          tinymce.each(styles, function(value, name) {
18491              if (value)
18492                  dom.setStyle(node, name, value);
18493          });
18494  
18495          dom.rename(node, 'span');
18496      };
18497  
18498  	function convert(editor, params) {
18499          dom = editor.dom;
18500  
18501          if (settings.convert_fonts_to_spans) {
18502              tinymce.each(dom.select('font,u,strike', params.node), function(node) {
18503                  filters[node.nodeName.toLowerCase()](ed.dom, node);
18504              });
18505          }
18506      };
18507  
18508      if (settings.inline_styles) {
18509          fontSizes = tinymce.explode(settings.font_size_legacy_values);
18510  
18511          filters = {
18512              font : function(dom, node) {
18513                  replaceWithSpan(node, {
18514                      backgroundColor : node.style.backgroundColor,
18515                      color : node.color,
18516                      fontFamily : node.face,
18517                      fontSize : fontSizes[parseInt(node.size, 10) - 1]
18518                  });
18519              },
18520  
18521              u : function(dom, node) {
18522                  replaceWithSpan(node, {
18523                      textDecoration : 'underline'
18524                  });
18525              },
18526  
18527              strike : function(dom, node) {
18528                  replaceWithSpan(node, {
18529                      textDecoration : 'line-through'
18530                  });
18531              }
18532          };
18533  
18534          ed.onPreProcess.add(convert);
18535          ed.onSetContent.add(convert);
18536  
18537          ed.onInit.add(function() {
18538              ed.selection.onSetContent.add(convert);
18539          });
18540      }
18541  });
18542  (function(tinymce) {
18543      var TreeWalker = tinymce.dom.TreeWalker;
18544  
18545      tinymce.EnterKey = function(editor) {
18546          var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
18547  
18548  		function handleEnterKey(evt) {
18549              var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
18550                  newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
18551  
18552              // Returns true if the block can be split into two blocks or not
18553  			function canSplitBlock(node) {
18554                  return node &&
18555                      dom.isBlock(node) &&
18556                      !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
18557                      !/^(fixed|absolute)/i.test(node.style.position) && 
18558                      dom.getContentEditable(node) !== "true";
18559              };
18560  
18561              // Renders empty block on IE
18562  			function renderBlockOnIE(block) {
18563                  var oldRng;
18564  
18565                  if (tinymce.isIE && !tinymce.isIE11 && dom.isBlock(block)) {
18566                      oldRng = selection.getRng();
18567                      block.appendChild(dom.create('span', null, '\u00a0'));
18568                      selection.select(block);
18569                      block.lastChild.outerHTML = '';
18570                      selection.setRng(oldRng);
18571                  }
18572              };
18573  
18574              // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
18575  			function trimInlineElementsOnLeftSideOfBlock(block) {
18576                  var node = block, firstChilds = [], i;
18577  
18578                  // Find inner most first child ex: <p><i><b>*</b></i></p>
18579                  while (node = node.firstChild) {
18580                      if (dom.isBlock(node)) {
18581                          return;
18582                      }
18583  
18584                      if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18585                          firstChilds.push(node);
18586                      }
18587                  }
18588  
18589                  i = firstChilds.length;
18590                  while (i--) {
18591                      node = firstChilds[i];
18592                      if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
18593                          dom.remove(node);
18594                      } else {
18595                          // Remove <a> </a> see #5381
18596                          if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {
18597                              dom.remove(node);
18598                          }
18599                      }
18600                  }
18601              };
18602              
18603              // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
18604  			function moveToCaretPosition(root) {
18605                  var walker, node, rng, y, viewPort, lastNode = root, tempElm;
18606  
18607                  rng = dom.createRng();
18608  
18609                  if (root.hasChildNodes()) {
18610                      walker = new TreeWalker(root, root);
18611  
18612                      while (node = walker.current()) {
18613                          if (node.nodeType == 3) {
18614                              rng.setStart(node, 0);
18615                              rng.setEnd(node, 0);
18616                              break;
18617                          }
18618  
18619                          if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
18620                              rng.setStartBefore(node);
18621                              rng.setEndBefore(node);
18622                              break;
18623                          }
18624  
18625                          lastNode = node;
18626                          node = walker.next();
18627                      }
18628  
18629                      if (!node) {
18630                          rng.setStart(lastNode, 0);
18631                          rng.setEnd(lastNode, 0);
18632                      }
18633                  } else {
18634                      if (root.nodeName == 'BR') {
18635                          if (root.nextSibling && dom.isBlock(root.nextSibling)) {
18636                              // Trick on older IE versions to render the caret before the BR between two lists
18637                              if (!documentMode || documentMode < 9) {
18638                                  tempElm = dom.create('br');
18639                                  root.parentNode.insertBefore(tempElm, root);
18640                              }
18641  
18642                              rng.setStartBefore(root);
18643                              rng.setEndBefore(root);
18644                          } else {
18645                              rng.setStartAfter(root);
18646                              rng.setEndAfter(root);
18647                          }
18648                      } else {
18649                          rng.setStart(root, 0);
18650                          rng.setEnd(root, 0);
18651                      }
18652                  }
18653  
18654                  selection.setRng(rng);
18655  
18656                  // Remove tempElm created for old IE:s
18657                  dom.remove(tempElm);
18658  
18659                  viewPort = dom.getViewPort(editor.getWin());
18660  
18661                  // scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
18662                  y = dom.getPos(root).y;
18663                  if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
18664                      editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
18665                  }
18666              };
18667  
18668              // Creates a new block element by cloning the current one or creating a new one if the name is specified
18669              // This function will also copy any text formatting from the parent block and add it to the new one
18670  			function createNewBlock(name) {
18671                  var node = container, block, clonedNode, caretNode;
18672  
18673                  block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
18674                  caretNode = block;
18675  
18676                  // Clone any parent styles
18677                  if (settings.keep_styles !== false) {
18678                      do {
18679                          if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
18680                              // Never clone a caret containers
18681                              if (node.id == '_mce_caret') {
18682                                  continue;
18683                              }
18684  
18685                              clonedNode = node.cloneNode(false);
18686                              dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
18687  
18688                              if (block.hasChildNodes()) {
18689                                  clonedNode.appendChild(block.firstChild);
18690                                  block.appendChild(clonedNode);
18691                              } else {
18692                                  caretNode = clonedNode;
18693                                  block.appendChild(clonedNode);
18694                              }
18695                          }
18696                      } while (node = node.parentNode);
18697                  }
18698  
18699                  // BR is needed in empty blocks on non IE browsers
18700                  if (!tinymce.isIE || tinymce.isIE11) {
18701                      caretNode.innerHTML = '<br data-mce-bogus="1">';
18702                  }
18703  
18704                  return block;
18705              };
18706  
18707              // Returns true/false if the caret is at the start/end of the parent block element
18708  			function isCaretAtStartOrEndOfBlock(start) {
18709                  var walker, node, name;
18710  
18711                  // Caret is in the middle of a text node like "a|b"
18712                  if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
18713                      return false;
18714                  }
18715  
18716                  // If after the last element in block node edge case for #5091
18717                  if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
18718                      return true;
18719                  }
18720  
18721                  // If the caret if before the first element in parentBlock
18722                  if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
18723                      return true;
18724                  }
18725  
18726                  // Caret can be before/after a table
18727                  if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
18728                      return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
18729                  }
18730  
18731                  // Walk the DOM and look for text nodes or non empty elements
18732                  walker = new TreeWalker(container, parentBlock);
18733      
18734                  // If caret is in beginning or end of a text block then jump to the next/previous node
18735                  if (container.nodeType == 3) {
18736                      if (start && offset == 0) {
18737                          walker.prev();
18738                      } else if (!start && offset == container.nodeValue.length) {
18739                          walker.next();
18740                      }
18741                  }
18742  
18743                  while (node = walker.current()) {
18744                      if (node.nodeType === 1) {
18745                          // Ignore bogus elements
18746                          if (!node.getAttribute('data-mce-bogus')) {
18747                              // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
18748                              name = node.nodeName.toLowerCase();
18749                              if (nonEmptyElementsMap[name] && name !== 'br') {
18750                                  return false;
18751                              }
18752                          }
18753                      } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
18754                          return false;
18755                      }
18756  
18757                      if (start) {
18758                          walker.prev();
18759                      } else {
18760                          walker.next();
18761                      }
18762                  }
18763  
18764                  return true;
18765              };
18766  
18767              // Wraps any text nodes or inline elements in the specified forced root block name
18768  			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
18769                  var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
18770  
18771                  // Not in a block element or in a table cell or caption
18772                  parentBlock = dom.getParent(container, dom.isBlock);
18773                  if (!parentBlock || !canSplitBlock(parentBlock)) {
18774                      parentBlock = parentBlock || editableRoot;
18775  
18776                      if (!parentBlock.hasChildNodes()) {
18777                          newBlock = dom.create(blockName);
18778                          parentBlock.appendChild(newBlock);
18779                          rng.setStart(newBlock, 0);
18780                          rng.setEnd(newBlock, 0);
18781                          return newBlock;
18782                      }
18783  
18784                      // Find parent that is the first child of parentBlock
18785                      node = container;
18786                      while (node.parentNode != parentBlock) {
18787                          node = node.parentNode;
18788                      }
18789  
18790                      // Loop left to find start node start wrapping at
18791                      while (node && !dom.isBlock(node)) {
18792                          startNode = node;
18793                          node = node.previousSibling;
18794                      }
18795  
18796                      if (startNode) {
18797                          newBlock = dom.create(blockName);
18798                          startNode.parentNode.insertBefore(newBlock, startNode);
18799  
18800                          // Start wrapping until we hit a block
18801                          node = startNode;
18802                          while (node && !dom.isBlock(node)) {
18803                              next = node.nextSibling;
18804                              newBlock.appendChild(node);
18805                              node = next;
18806                          }
18807  
18808                          // Restore range to it's past location
18809                          rng.setStart(container, offset);
18810                          rng.setEnd(container, offset);
18811                      }
18812                  }
18813  
18814                  return container;
18815              };
18816  
18817              // Inserts a block or br before/after or in the middle of a split list of the LI is empty
18818  			function handleEmptyListItem() {
18819  				function isFirstOrLastLi(first) {
18820                      var node = containerBlock[first ? 'firstChild' : 'lastChild'];
18821  
18822                      // Find first/last element since there might be whitespace there
18823                      while (node) {
18824                          if (node.nodeType == 1) {
18825                              break;
18826                          }
18827  
18828                          node = node[first ? 'nextSibling' : 'previousSibling'];
18829                      }
18830  
18831                      return node === parentBlock;
18832                  };
18833  
18834                  newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
18835  
18836                  if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
18837                      // Is first and last list item then replace the OL/UL with a text block
18838                      dom.replace(newBlock, containerBlock);
18839                  } else if (isFirstOrLastLi(true)) {
18840                      // First LI in list then remove LI and add text block before list
18841                      containerBlock.parentNode.insertBefore(newBlock, containerBlock);
18842                  } else if (isFirstOrLastLi()) {
18843                      // Last LI in list then temove LI and add text block after list
18844                      dom.insertAfter(newBlock, containerBlock);
18845                      renderBlockOnIE(newBlock);
18846                  } else {
18847                      // Middle LI in list the split the list and insert a text block in the middle
18848                      // Extract after fragment and insert it after the current block
18849                      tmpRng = rng.cloneRange();
18850                      tmpRng.setStartAfter(parentBlock);
18851                      tmpRng.setEndAfter(containerBlock);
18852                      fragment = tmpRng.extractContents();
18853                      dom.insertAfter(fragment, containerBlock);
18854                      dom.insertAfter(newBlock, containerBlock);
18855                  }
18856  
18857                  dom.remove(parentBlock);
18858                  moveToCaretPosition(newBlock);
18859                  undoManager.add();
18860              };
18861  
18862              // Walks the parent block to the right and look for any contents
18863  			function hasRightSideContent() {
18864                  var walker = new TreeWalker(container, parentBlock), node;
18865  
18866                  while (node = walker.next()) {
18867                      if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || node.length > 0) {
18868                          return true;
18869                      }
18870                  }
18871              }
18872  
18873              // Inserts a BR element if the forced_root_block option is set to false or empty string
18874  			function insertBr() {
18875                  var brElm, extraBr, marker;
18876  
18877                  if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
18878                      // Insert extra BR element at the end block elements
18879                      if ((!tinymce.isIE || tinymce.isIE11) && !hasRightSideContent()) {
18880                          brElm = dom.create('br');
18881                          rng.insertNode(brElm);
18882                          rng.setStartAfter(brElm);
18883                          rng.setEndAfter(brElm);
18884                          extraBr = true;
18885                      }
18886                  }
18887  
18888                  brElm = dom.create('br');
18889                  rng.insertNode(brElm);
18890  
18891                  // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
18892                  if ((tinymce.isIE && !tinymce.isIE11) && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
18893                      brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
18894                  }
18895  
18896                  // Insert temp marker and scroll to that
18897                  marker = dom.create('span', {}, '&nbsp;');
18898                  brElm.parentNode.insertBefore(marker, brElm);
18899                  selection.scrollIntoView(marker);
18900                  dom.remove(marker);
18901  
18902                  if (!extraBr) {
18903                      rng.setStartAfter(brElm);
18904                      rng.setEndAfter(brElm);
18905                  } else {
18906                      rng.setStartBefore(brElm);
18907                      rng.setEndBefore(brElm);
18908                  }
18909  
18910                  selection.setRng(rng);
18911                  undoManager.add();
18912              };
18913  
18914              // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
18915  			function trimLeadingLineBreaks(node) {
18916                  do {
18917                      if (node.nodeType === 3) {
18918                          node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
18919                      }
18920  
18921                      node = node.firstChild;
18922                  } while (node);
18923              };
18924  
18925  			function getEditableRoot(node) {
18926                  var root = dom.getRoot(), parent, editableRoot;
18927  
18928                  // Get all parents until we hit a non editable parent or the root
18929                  parent = node;
18930                  while (parent !== root && dom.getContentEditable(parent) !== "false") {
18931                      if (dom.getContentEditable(parent) === "true") {
18932                          editableRoot = parent;
18933                      }
18934  
18935                      parent = parent.parentNode;
18936                  }
18937                  
18938                  return parent !== root ? editableRoot : root;
18939              };
18940  
18941              // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
18942  			function addBrToBlockIfNeeded(block) {
18943                  var lastChild;
18944  
18945                  // IE will render the blocks correctly other browsers needs a BR
18946                  if (!tinymce.isIE || tinymce.isIE11) {
18947                      block.normalize(); // Remove empty text nodes that got left behind by the extract
18948  
18949                      // Check if the block is empty or contains a floated last child
18950                      lastChild = block.lastChild;
18951                      if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
18952                          dom.add(block, 'br');
18953                      }
18954                  }
18955              };
18956  
18957              // Delete any selected contents
18958              if (!rng.collapsed) {
18959                  editor.execCommand('Delete');
18960                  return;
18961              }
18962  
18963              // Event is blocked by some other handler for example the lists plugin
18964              if (evt.isDefaultPrevented()) {
18965                  return;
18966              }
18967  
18968              // Setup range items and newBlockName
18969              container = rng.startContainer;
18970              offset = rng.startOffset;
18971              newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
18972              newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
18973              documentMode = dom.doc.documentMode;
18974              shiftKey = evt.shiftKey;
18975  
18976              // Resolve node index
18977              if (container.nodeType == 1 && container.hasChildNodes()) {
18978                  isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
18979                  container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
18980                  if (isAfterLastNodeInContainer && container.nodeType == 3) {
18981                      offset = container.nodeValue.length;
18982                  } else {
18983                      offset = 0;
18984                  }
18985              }
18986  
18987              // Get editable root node normaly the body element but sometimes a div or span
18988              editableRoot = getEditableRoot(container);
18989  
18990              // If there is no editable root then enter is done inside a contentEditable false element
18991              if (!editableRoot) {
18992                  return;
18993              }
18994  
18995              undoManager.beforeChange();
18996  
18997              // If editable root isn't block nor the root of the editor
18998              if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
18999                  if (!newBlockName || shiftKey) {
19000                      insertBr();
19001                  }
19002  
19003                  return;
19004              }
19005  
19006              // Wrap the current node and it's sibling in a default block if it's needed.
19007              // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
19008              // This won't happen if root blocks are disabled or the shiftKey is pressed
19009              if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {
19010                  container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
19011              }
19012  
19013              // Find parent block and setup empty block paddings
19014              parentBlock = dom.getParent(container, dom.isBlock);
19015              containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
19016  
19017              // Setup block names
19018              parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
19019              containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
19020  
19021              // Enter inside block contained within a LI then split or insert before/after LI
19022              if (containerBlockName == 'LI' && !evt.ctrlKey) {
19023                  parentBlock = containerBlock;
19024                  parentBlockName = containerBlockName;
19025              }
19026  
19027              // Handle enter in LI
19028              if (parentBlockName == 'LI') {
19029                  if (!newBlockName && shiftKey) {
19030                      insertBr();
19031                      return;
19032                  }
19033  
19034                  // Handle enter inside an empty list item
19035                  if (dom.isEmpty(parentBlock)) {
19036                      // Let the list plugin or browser handle nested lists for now
19037                      if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
19038                          return false;
19039                      }
19040  
19041                      handleEmptyListItem();
19042                      return;
19043                  }
19044              }
19045  
19046              // Don't split PRE tags but insert a BR instead easier when writing code samples etc
19047              if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
19048                  if (!shiftKey) {
19049                      insertBr();
19050                      return;
19051                  }
19052              } else {
19053                  // If no root block is configured then insert a BR by default or if the shiftKey is pressed
19054                  if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {
19055                      insertBr();
19056                      return;
19057                  }
19058              }
19059  
19060              // Default block name if it's not configured
19061              newBlockName = newBlockName || 'P';
19062  
19063              // Insert new block before/after the parent block depending on caret location
19064              if (isCaretAtStartOrEndOfBlock()) {
19065                  // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
19066                  if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
19067                      newBlock = createNewBlock(newBlockName);
19068                  } else {
19069                      newBlock = createNewBlock();
19070                  }
19071  
19072                  // Split the current container block element if enter is pressed inside an empty inner block element
19073                  if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
19074                      // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
19075                      newBlock = dom.split(containerBlock, parentBlock);
19076                  } else {
19077                      dom.insertAfter(newBlock, parentBlock);
19078                  }
19079  
19080                  moveToCaretPosition(newBlock);
19081              } else if (isCaretAtStartOrEndOfBlock(true)) {
19082                  // Insert new block before
19083                  newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
19084                  renderBlockOnIE(newBlock);
19085              } else {
19086                  // Extract after fragment and insert it after the current block
19087                  tmpRng = rng.cloneRange();
19088                  tmpRng.setEndAfter(parentBlock);
19089                  fragment = tmpRng.extractContents();
19090                  trimLeadingLineBreaks(fragment);
19091                  newBlock = fragment.firstChild;
19092                  dom.insertAfter(fragment, parentBlock);
19093                  trimInlineElementsOnLeftSideOfBlock(newBlock);
19094                  addBrToBlockIfNeeded(parentBlock);
19095                  moveToCaretPosition(newBlock);
19096              }
19097  
19098              dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
19099              undoManager.add();
19100          }
19101  
19102          editor.onKeyDown.add(function(ed, evt) {
19103              if (evt.keyCode == 13) {
19104                  if (handleEnterKey(evt) !== false) {
19105                      evt.preventDefault();
19106                  }
19107              }
19108          });
19109      };
19110  })(tinymce);


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1