Updated tiny-mce to most recent 3.4 version
[citadel.git] / webcit / tiny_mce / tiny_mce_src.js
1 (function(win) {\r
2         var whiteSpaceRe = /^\s*|\s*$/g,\r
3                 undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';\r
4 \r
5         var tinymce = {\r
6                 majorVersion : '3',\r
7 \r
8                 minorVersion : '4.9',\r
9 \r
10                 releaseDate : '2012-02-23',\r
11 \r
12                 _init : function() {\r
13                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;\r
14 \r
15                         t.isOpera = win.opera && opera.buildNumber;\r
16 \r
17                         t.isWebKit = /WebKit/.test(ua);\r
18 \r
19                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);\r
20 \r
21                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);\r
22 \r
23                         t.isIE7 = t.isIE && /MSIE [7]/.test(ua);\r
24 \r
25                         t.isIE8 = t.isIE && /MSIE [8]/.test(ua);\r
26 \r
27                         t.isIE9 = t.isIE && /MSIE [9]/.test(ua);\r
28 \r
29                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);\r
30 \r
31                         t.isMac = ua.indexOf('Mac') != -1;\r
32 \r
33                         t.isAir = /adobeair/i.test(ua);\r
34 \r
35                         t.isIDevice = /(iPad|iPhone)/.test(ua);\r
36                         \r
37                         t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;\r
38 \r
39                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE\r
40                         if (win.tinyMCEPreInit) {\r
41                                 t.suffix = tinyMCEPreInit.suffix;\r
42                                 t.baseURL = tinyMCEPreInit.base;\r
43                                 t.query = tinyMCEPreInit.query;\r
44                                 return;\r
45                         }\r
46 \r
47                         // Get suffix and base\r
48                         t.suffix = '';\r
49 \r
50                         // If base element found, add that infront of baseURL\r
51                         nl = d.getElementsByTagName('base');\r
52                         for (i=0; i<nl.length; i++) {\r
53                                 if (v = nl[i].href) {\r
54                                         // Host only value like http://site.com or http://site.com:8008\r
55                                         if (/^https?:\/\/[^\/]+$/.test(v))\r
56                                                 v += '/';\r
57 \r
58                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory\r
59                                 }\r
60                         }\r
61 \r
62                         function getBase(n) {\r
63                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {\r
64                                         if (/_(src|dev)\.js/g.test(n.src))\r
65                                                 t.suffix = '_src';\r
66 \r
67                                         if ((p = n.src.indexOf('?')) != -1)\r
68                                                 t.query = n.src.substring(p + 1);\r
69 \r
70                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));\r
71 \r
72                                         // If path to script is relative and a base href was found add that one infront\r
73                                         // the src property will always be an absolute one on non IE browsers and IE 8\r
74                                         // so this logic will basically only be executed on older IE versions\r
75                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)\r
76                                                 t.baseURL = base + t.baseURL;\r
77 \r
78                                         return t.baseURL;\r
79                                 }\r
80 \r
81                                 return null;\r
82                         };\r
83 \r
84                         // Check document\r
85                         nl = d.getElementsByTagName('script');\r
86                         for (i=0; i<nl.length; i++) {\r
87                                 if (getBase(nl[i]))\r
88                                         return;\r
89                         }\r
90 \r
91                         // Check head\r
92                         n = d.getElementsByTagName('head')[0];\r
93                         if (n) {\r
94                                 nl = n.getElementsByTagName('script');\r
95                                 for (i=0; i<nl.length; i++) {\r
96                                         if (getBase(nl[i]))\r
97                                                 return;\r
98                                 }\r
99                         }\r
100 \r
101                         return;\r
102                 },\r
103 \r
104                 is : function(o, t) {\r
105                         if (!t)\r
106                                 return o !== undefined;\r
107 \r
108                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))\r
109                                 return true;\r
110 \r
111                         return typeof(o) == t;\r
112                 },\r
113 \r
114                 makeMap : function(items, delim, map) {\r
115                         var i;\r
116 \r
117                         items = items || [];\r
118                         delim = delim || ',';\r
119 \r
120                         if (typeof(items) == "string")\r
121                                 items = items.split(delim);\r
122 \r
123                         map = map || {};\r
124 \r
125                         i = items.length;\r
126                         while (i--)\r
127                                 map[items[i]] = {};\r
128 \r
129                         return map;\r
130                 },\r
131 \r
132                 each : function(o, cb, s) {\r
133                         var n, l;\r
134 \r
135                         if (!o)\r
136                                 return 0;\r
137 \r
138                         s = s || o;\r
139 \r
140                         if (o.length !== undefined) {\r
141                                 // Indexed arrays, needed for Safari\r
142                                 for (n=0, l = o.length; n < l; n++) {\r
143                                         if (cb.call(s, o[n], n, o) === false)\r
144                                                 return 0;\r
145                                 }\r
146                         } else {\r
147                                 // Hashtables\r
148                                 for (n in o) {\r
149                                         if (o.hasOwnProperty(n)) {\r
150                                                 if (cb.call(s, o[n], n, o) === false)\r
151                                                         return 0;\r
152                                         }\r
153                                 }\r
154                         }\r
155 \r
156                         return 1;\r
157                 },\r
158 \r
159 \r
160                 map : function(a, f) {\r
161                         var o = [];\r
162 \r
163                         tinymce.each(a, function(v) {\r
164                                 o.push(f(v));\r
165                         });\r
166 \r
167                         return o;\r
168                 },\r
169 \r
170                 grep : function(a, f) {\r
171                         var o = [];\r
172 \r
173                         tinymce.each(a, function(v) {\r
174                                 if (!f || f(v))\r
175                                         o.push(v);\r
176                         });\r
177 \r
178                         return o;\r
179                 },\r
180 \r
181                 inArray : function(a, v) {\r
182                         var i, l;\r
183 \r
184                         if (a) {\r
185                                 for (i = 0, l = a.length; i < l; i++) {\r
186                                         if (a[i] === v)\r
187                                                 return i;\r
188                                 }\r
189                         }\r
190 \r
191                         return -1;\r
192                 },\r
193 \r
194                 extend : function(o, e) {\r
195                         var i, l, a = arguments;\r
196 \r
197                         for (i = 1, l = a.length; i < l; i++) {\r
198                                 e = a[i];\r
199 \r
200                                 tinymce.each(e, function(v, n) {\r
201                                         if (v !== undefined)\r
202                                                 o[n] = v;\r
203                                 });\r
204                         }\r
205 \r
206                         return o;\r
207                 },\r
208 \r
209 \r
210                 trim : function(s) {\r
211                         return (s ? '' + s : '').replace(whiteSpaceRe, '');\r
212                 },\r
213 \r
214                 create : function(s, p, root) {\r
215                         var t = this, sp, ns, cn, scn, c, de = 0;\r
216 \r
217                         // Parse : <prefix> <class>:<super class>\r
218                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);\r
219                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name\r
220 \r
221                         // Create namespace for new class\r
222                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);\r
223 \r
224                         // Class already exists\r
225                         if (ns[cn])\r
226                                 return;\r
227 \r
228                         // Make pure static class\r
229                         if (s[2] == 'static') {\r
230                                 ns[cn] = p;\r
231 \r
232                                 if (this.onCreate)\r
233                                         this.onCreate(s[2], s[3], ns[cn]);\r
234 \r
235                                 return;\r
236                         }\r
237 \r
238                         // Create default constructor\r
239                         if (!p[cn]) {\r
240                                 p[cn] = function() {};\r
241                                 de = 1;\r
242                         }\r
243 \r
244                         // Add constructor and methods\r
245                         ns[cn] = p[cn];\r
246                         t.extend(ns[cn].prototype, p);\r
247 \r
248                         // Extend\r
249                         if (s[5]) {\r
250                                 sp = t.resolve(s[5]).prototype;\r
251                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name\r
252 \r
253                                 // Extend constructor\r
254                                 c = ns[cn];\r
255                                 if (de) {\r
256                                         // Add passthrough constructor\r
257                                         ns[cn] = function() {\r
258                                                 return sp[scn].apply(this, arguments);\r
259                                         };\r
260                                 } else {\r
261                                         // Add inherit constructor\r
262                                         ns[cn] = function() {\r
263                                                 this.parent = sp[scn];\r
264                                                 return c.apply(this, arguments);\r
265                                         };\r
266                                 }\r
267                                 ns[cn].prototype[cn] = ns[cn];\r
268 \r
269                                 // Add super methods\r
270                                 t.each(sp, function(f, n) {\r
271                                         ns[cn].prototype[n] = sp[n];\r
272                                 });\r
273 \r
274                                 // Add overridden methods\r
275                                 t.each(p, function(f, n) {\r
276                                         // Extend methods if needed\r
277                                         if (sp[n]) {\r
278                                                 ns[cn].prototype[n] = function() {\r
279                                                         this.parent = sp[n];\r
280                                                         return f.apply(this, arguments);\r
281                                                 };\r
282                                         } else {\r
283                                                 if (n != cn)\r
284                                                         ns[cn].prototype[n] = f;\r
285                                         }\r
286                                 });\r
287                         }\r
288 \r
289                         // Add static methods\r
290                         t.each(p['static'], function(f, n) {\r
291                                 ns[cn][n] = f;\r
292                         });\r
293 \r
294                         if (this.onCreate)\r
295                                 this.onCreate(s[2], s[3], ns[cn].prototype);\r
296                 },\r
297 \r
298                 walk : function(o, f, n, s) {\r
299                         s = s || this;\r
300 \r
301                         if (o) {\r
302                                 if (n)\r
303                                         o = o[n];\r
304 \r
305                                 tinymce.each(o, function(o, i) {\r
306                                         if (f.call(s, o, i, n) === false)\r
307                                                 return false;\r
308 \r
309                                         tinymce.walk(o, f, n, s);\r
310                                 });\r
311                         }\r
312                 },\r
313 \r
314                 createNS : function(n, o) {\r
315                         var i, v;\r
316 \r
317                         o = o || win;\r
318 \r
319                         n = n.split('.');\r
320                         for (i=0; i<n.length; i++) {\r
321                                 v = n[i];\r
322 \r
323                                 if (!o[v])\r
324                                         o[v] = {};\r
325 \r
326                                 o = o[v];\r
327                         }\r
328 \r
329                         return o;\r
330                 },\r
331 \r
332                 resolve : function(n, o) {\r
333                         var i, l;\r
334 \r
335                         o = o || win;\r
336 \r
337                         n = n.split('.');\r
338                         for (i = 0, l = n.length; i < l; i++) {\r
339                                 o = o[n[i]];\r
340 \r
341                                 if (!o)\r
342                                         break;\r
343                         }\r
344 \r
345                         return o;\r
346                 },\r
347 \r
348                 addUnload : function(f, s) {\r
349                         var t = this;\r
350 \r
351                         f = {func : f, scope : s || this};\r
352 \r
353                         if (!t.unloads) {\r
354                                 function unload() {\r
355                                         var li = t.unloads, o, n;\r
356 \r
357                                         if (li) {\r
358                                                 // Call unload handlers\r
359                                                 for (n in li) {\r
360                                                         o = li[n];\r
361 \r
362                                                         if (o && o.func)\r
363                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy\r
364                                                 }\r
365 \r
366                                                 // Detach unload function\r
367                                                 if (win.detachEvent) {\r
368                                                         win.detachEvent('onbeforeunload', fakeUnload);\r
369                                                         win.detachEvent('onunload', unload);\r
370                                                 } else if (win.removeEventListener)\r
371                                                         win.removeEventListener('unload', unload, false);\r
372 \r
373                                                 // Destroy references\r
374                                                 t.unloads = o = li = w = unload = 0;\r
375 \r
376                                                 // Run garbarge collector on IE\r
377                                                 if (win.CollectGarbage)\r
378                                                         CollectGarbage();\r
379                                         }\r
380                                 };\r
381 \r
382                                 function fakeUnload() {\r
383                                         var d = document;\r
384 \r
385                                         // Is there things still loading, then do some magic\r
386                                         if (d.readyState == 'interactive') {\r
387                                                 function stop() {\r
388                                                         // Prevent memory leak\r
389                                                         d.detachEvent('onstop', stop);\r
390 \r
391                                                         // Call unload handler\r
392                                                         if (unload)\r
393                                                                 unload();\r
394 \r
395                                                         d = 0;\r
396                                                 };\r
397 \r
398                                                 // Fire unload when the currently loading page is stopped\r
399                                                 if (d)\r
400                                                         d.attachEvent('onstop', stop);\r
401 \r
402                                                 // Remove onstop listener after a while to prevent the unload function\r
403                                                 // to execute if the user presses cancel in an onbeforeunload\r
404                                                 // confirm dialog and then presses the browser stop button\r
405                                                 win.setTimeout(function() {\r
406                                                         if (d)\r
407                                                                 d.detachEvent('onstop', stop);\r
408                                                 }, 0);\r
409                                         }\r
410                                 };\r
411 \r
412                                 // Attach unload handler\r
413                                 if (win.attachEvent) {\r
414                                         win.attachEvent('onunload', unload);\r
415                                         win.attachEvent('onbeforeunload', fakeUnload);\r
416                                 } else if (win.addEventListener)\r
417                                         win.addEventListener('unload', unload, false);\r
418 \r
419                                 // Setup initial unload handler array\r
420                                 t.unloads = [f];\r
421                         } else\r
422                                 t.unloads.push(f);\r
423 \r
424                         return f;\r
425                 },\r
426 \r
427                 removeUnload : function(f) {\r
428                         var u = this.unloads, r = null;\r
429 \r
430                         tinymce.each(u, function(o, i) {\r
431                                 if (o && o.func == f) {\r
432                                         u.splice(i, 1);\r
433                                         r = f;\r
434                                         return false;\r
435                                 }\r
436                         });\r
437 \r
438                         return r;\r
439                 },\r
440 \r
441                 explode : function(s, d) {\r
442                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;\r
443                 },\r
444 \r
445                 _addVer : function(u) {\r
446                         var v;\r
447 \r
448                         if (!this.query)\r
449                                 return u;\r
450 \r
451                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;\r
452 \r
453                         if (u.indexOf('#') == -1)\r
454                                 return u + v;\r
455 \r
456                         return u.replace('#', v + '#');\r
457                 },\r
458 \r
459                 // Fix function for IE 9 where regexps isn't working correctly\r
460                 // Todo: remove me once MS fixes the bug\r
461                 _replace : function(find, replace, str) {\r
462                         // On IE9 we have to fake $x replacement\r
463                         if (isRegExpBroken) {\r
464                                 return str.replace(find, function() {\r
465                                         var val = replace, args = arguments, i;\r
466 \r
467                                         for (i = 0; i < args.length - 2; i++) {\r
468                                                 if (args[i] === undefined) {\r
469                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');\r
470                                                 } else {\r
471                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);\r
472                                                 }\r
473                                         }\r
474 \r
475                                         return val;\r
476                                 });\r
477                         }\r
478 \r
479                         return str.replace(find, replace);\r
480                 }\r
481 \r
482                 };\r
483 \r
484         // Initialize the API\r
485         tinymce._init();\r
486 \r
487         // Expose tinymce namespace to the global namespace (window)\r
488         win.tinymce = win.tinyMCE = tinymce;\r
489 \r
490         // Describe the different namespaces\r
491 \r
492         })(window);\r
493 \r
494 \r
495 \r
496 tinymce.create('tinymce.util.Dispatcher', {\r
497         scope : null,\r
498         listeners : null,\r
499 \r
500         Dispatcher : function(s) {\r
501                 this.scope = s || this;\r
502                 this.listeners = [];\r
503         },\r
504 \r
505         add : function(cb, s) {\r
506                 this.listeners.push({cb : cb, scope : s || this.scope});\r
507 \r
508                 return cb;\r
509         },\r
510 \r
511         addToTop : function(cb, s) {\r
512                 this.listeners.unshift({cb : cb, scope : s || this.scope});\r
513 \r
514                 return cb;\r
515         },\r
516 \r
517         remove : function(cb) {\r
518                 var l = this.listeners, o = null;\r
519 \r
520                 tinymce.each(l, function(c, i) {\r
521                         if (cb == c.cb) {\r
522                                 o = cb;\r
523                                 l.splice(i, 1);\r
524                                 return false;\r
525                         }\r
526                 });\r
527 \r
528                 return o;\r
529         },\r
530 \r
531         dispatch : function() {\r
532                 var s, a = arguments, i, li = this.listeners, c;\r
533 \r
534                 // Needs to be a real loop since the listener count might change while looping\r
535                 // And this is also more efficient\r
536                 for (i = 0; i<li.length; i++) {\r
537                         c = li[i];\r
538                         s = c.cb.apply(c.scope, a.length > 0 ? a : [c.scope]);\r
539 \r
540                         if (s === false)\r
541                                 break;\r
542                 }\r
543 \r
544                 return s;\r
545         }\r
546 \r
547         });\r
548 \r
549 (function() {\r
550         var each = tinymce.each;\r
551 \r
552         tinymce.create('tinymce.util.URI', {\r
553                 URI : function(u, s) {\r
554                         var t = this, o, a, b, base_url;\r
555 \r
556                         // Trim whitespace\r
557                         u = tinymce.trim(u);\r
558 \r
559                         // Default settings\r
560                         s = t.settings = s || {};\r
561 \r
562                         // Strange app protocol that isn't http/https or local anchor\r
563                         // For example: mailto,skype,tel etc.\r
564                         if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {\r
565                                 t.source = u;\r
566                                 return;\r
567                         }\r
568 \r
569                         // Absolute path with no host, fake host and protocol\r
570                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)\r
571                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;\r
572 \r
573                         // Relative path http:// or protocol relative //path\r
574                         if (!/^[\w-]*:?\/\//.test(u)) {\r
575                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;\r
576                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);\r
577                         }\r
578 \r
579                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)\r
580                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something\r
581                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);\r
582                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {\r
583                                 var s = u[i];\r
584 \r
585                                 // Zope 3 workaround, they use @@something\r
586                                 if (s)\r
587                                         s = s.replace(/\(mce_at\)/g, '@@');\r
588 \r
589                                 t[v] = s;\r
590                         });\r
591 \r
592                         if (b = s.base_uri) {\r
593                                 if (!t.protocol)\r
594                                         t.protocol = b.protocol;\r
595 \r
596                                 if (!t.userInfo)\r
597                                         t.userInfo = b.userInfo;\r
598 \r
599                                 if (!t.port && t.host == 'mce_host')\r
600                                         t.port = b.port;\r
601 \r
602                                 if (!t.host || t.host == 'mce_host')\r
603                                         t.host = b.host;\r
604 \r
605                                 t.source = '';\r
606                         }\r
607 \r
608                         //t.path = t.path || '/';\r
609                 },\r
610 \r
611                 setPath : function(p) {\r
612                         var t = this;\r
613 \r
614                         p = /^(.*?)\/?(\w+)?$/.exec(p);\r
615 \r
616                         // Update path parts\r
617                         t.path = p[0];\r
618                         t.directory = p[1];\r
619                         t.file = p[2];\r
620 \r
621                         // Rebuild source\r
622                         t.source = '';\r
623                         t.getURI();\r
624                 },\r
625 \r
626                 toRelative : function(u) {\r
627                         var t = this, o;\r
628 \r
629                         if (u === "./")\r
630                                 return u;\r
631 \r
632                         u = new tinymce.util.URI(u, {base_uri : t});\r
633 \r
634                         // Not on same domain/port or protocol\r
635                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)\r
636                                 return u.getURI();\r
637 \r
638                         o = t.toRelPath(t.path, u.path);\r
639 \r
640                         // Add query\r
641                         if (u.query)\r
642                                 o += '?' + u.query;\r
643 \r
644                         // Add anchor\r
645                         if (u.anchor)\r
646                                 o += '#' + u.anchor;\r
647 \r
648                         return o;\r
649                 },\r
650         \r
651                 toAbsolute : function(u, nh) {\r
652                         var u = new tinymce.util.URI(u, {base_uri : this});\r
653 \r
654                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);\r
655                 },\r
656 \r
657                 toRelPath : function(base, path) {\r
658                         var items, bp = 0, out = '', i, l;\r
659 \r
660                         // Split the paths\r
661                         base = base.substring(0, base.lastIndexOf('/'));\r
662                         base = base.split('/');\r
663                         items = path.split('/');\r
664 \r
665                         if (base.length >= items.length) {\r
666                                 for (i = 0, l = base.length; i < l; i++) {\r
667                                         if (i >= items.length || base[i] != items[i]) {\r
668                                                 bp = i + 1;\r
669                                                 break;\r
670                                         }\r
671                                 }\r
672                         }\r
673 \r
674                         if (base.length < items.length) {\r
675                                 for (i = 0, l = items.length; i < l; i++) {\r
676                                         if (i >= base.length || base[i] != items[i]) {\r
677                                                 bp = i + 1;\r
678                                                 break;\r
679                                         }\r
680                                 }\r
681                         }\r
682 \r
683                         if (bp == 1)\r
684                                 return path;\r
685 \r
686                         for (i = 0, l = base.length - (bp - 1); i < l; i++)\r
687                                 out += "../";\r
688 \r
689                         for (i = bp - 1, l = items.length; i < l; i++) {\r
690                                 if (i != bp - 1)\r
691                                         out += "/" + items[i];\r
692                                 else\r
693                                         out += items[i];\r
694                         }\r
695 \r
696                         return out;\r
697                 },\r
698 \r
699                 toAbsPath : function(base, path) {\r
700                         var i, nb = 0, o = [], tr, outPath;\r
701 \r
702                         // Split paths\r
703                         tr = /\/$/.test(path) ? '/' : '';\r
704                         base = base.split('/');\r
705                         path = path.split('/');\r
706 \r
707                         // Remove empty chunks\r
708                         each(base, function(k) {\r
709                                 if (k)\r
710                                         o.push(k);\r
711                         });\r
712 \r
713                         base = o;\r
714 \r
715                         // Merge relURLParts chunks\r
716                         for (i = path.length - 1, o = []; i >= 0; i--) {\r
717                                 // Ignore empty or .\r
718                                 if (path[i].length == 0 || path[i] == ".")\r
719                                         continue;\r
720 \r
721                                 // Is parent\r
722                                 if (path[i] == '..') {\r
723                                         nb++;\r
724                                         continue;\r
725                                 }\r
726 \r
727                                 // Move up\r
728                                 if (nb > 0) {\r
729                                         nb--;\r
730                                         continue;\r
731                                 }\r
732 \r
733                                 o.push(path[i]);\r
734                         }\r
735 \r
736                         i = base.length - nb;\r
737 \r
738                         // If /a/b/c or /\r
739                         if (i <= 0)\r
740                                 outPath = o.reverse().join('/');\r
741                         else\r
742                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');\r
743 \r
744                         // Add front / if it's needed\r
745                         if (outPath.indexOf('/') !== 0)\r
746                                 outPath = '/' + outPath;\r
747 \r
748                         // Add traling / if it's needed\r
749                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)\r
750                                 outPath += tr;\r
751 \r
752                         return outPath;\r
753                 },\r
754 \r
755                 getURI : function(nh) {\r
756                         var s, t = this;\r
757 \r
758                         // Rebuild source\r
759                         if (!t.source || nh) {\r
760                                 s = '';\r
761 \r
762                                 if (!nh) {\r
763                                         if (t.protocol)\r
764                                                 s += t.protocol + '://';\r
765 \r
766                                         if (t.userInfo)\r
767                                                 s += t.userInfo + '@';\r
768 \r
769                                         if (t.host)\r
770                                                 s += t.host;\r
771 \r
772                                         if (t.port)\r
773                                                 s += ':' + t.port;\r
774                                 }\r
775 \r
776                                 if (t.path)\r
777                                         s += t.path;\r
778 \r
779                                 if (t.query)\r
780                                         s += '?' + t.query;\r
781 \r
782                                 if (t.anchor)\r
783                                         s += '#' + t.anchor;\r
784 \r
785                                 t.source = s;\r
786                         }\r
787 \r
788                         return t.source;\r
789                 }\r
790         });\r
791 })();\r
792 \r
793 (function() {\r
794         var each = tinymce.each;\r
795 \r
796         tinymce.create('static tinymce.util.Cookie', {\r
797                 getHash : function(n) {\r
798                         var v = this.get(n), h;\r
799 \r
800                         if (v) {\r
801                                 each(v.split('&'), function(v) {\r
802                                         v = v.split('=');\r
803                                         h = h || {};\r
804                                         h[unescape(v[0])] = unescape(v[1]);\r
805                                 });\r
806                         }\r
807 \r
808                         return h;\r
809                 },\r
810 \r
811                 setHash : function(n, v, e, p, d, s) {\r
812                         var o = '';\r
813 \r
814                         each(v, function(v, k) {\r
815                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);\r
816                         });\r
817 \r
818                         this.set(n, o, e, p, d, s);\r
819                 },\r
820 \r
821                 get : function(n) {\r
822                         var c = document.cookie, e, p = n + "=", b;\r
823 \r
824                         // Strict mode\r
825                         if (!c)\r
826                                 return;\r
827 \r
828                         b = c.indexOf("; " + p);\r
829 \r
830                         if (b == -1) {\r
831                                 b = c.indexOf(p);\r
832 \r
833                                 if (b != 0)\r
834                                         return null;\r
835                         } else\r
836                                 b += 2;\r
837 \r
838                         e = c.indexOf(";", b);\r
839 \r
840                         if (e == -1)\r
841                                 e = c.length;\r
842 \r
843                         return unescape(c.substring(b + p.length, e));\r
844                 },\r
845 \r
846                 set : function(n, v, e, p, d, s) {\r
847                         document.cookie = n + "=" + escape(v) +\r
848                                 ((e) ? "; expires=" + e.toGMTString() : "") +\r
849                                 ((p) ? "; path=" + escape(p) : "") +\r
850                                 ((d) ? "; domain=" + d : "") +\r
851                                 ((s) ? "; secure" : "");\r
852                 },\r
853 \r
854                 remove : function(n, p) {\r
855                         var d = new Date();\r
856 \r
857                         d.setTime(d.getTime() - 1000);\r
858 \r
859                         this.set(n, '', d, p, d);\r
860                 }\r
861         });\r
862 })();\r
863 \r
864 (function() {\r
865         function serialize(o, quote) {\r
866                 var i, v, t;\r
867 \r
868                 quote = quote || '"';\r
869 \r
870                 if (o == null)\r
871                         return 'null';\r
872 \r
873                 t = typeof o;\r
874 \r
875                 if (t == 'string') {\r
876                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';\r
877 \r
878                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {\r
879                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility\r
880                                 if (quote === '"' && a === "'")\r
881                                         return a;\r
882 \r
883                                 i = v.indexOf(b);\r
884 \r
885                                 if (i + 1)\r
886                                         return '\\' + v.charAt(i + 1);\r
887 \r
888                                 a = b.charCodeAt().toString(16);\r
889 \r
890                                 return '\\u' + '0000'.substring(a.length) + a;\r
891                         }) + quote;\r
892                 }\r
893 \r
894                 if (t == 'object') {\r
895                         if (o.hasOwnProperty && o instanceof Array) {\r
896                                         for (i=0, v = '['; i<o.length; i++)\r
897                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);\r
898 \r
899                                         return v + ']';\r
900                                 }\r
901 \r
902                                 v = '{';\r
903 \r
904                                 for (i in o) {\r
905                                         if (o.hasOwnProperty(i)) {\r
906                                                 v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';\r
907                                         }\r
908                                 }\r
909 \r
910                                 return v + '}';\r
911                 }\r
912 \r
913                 return '' + o;\r
914         };\r
915 \r
916         tinymce.util.JSON = {\r
917                 serialize: serialize,\r
918 \r
919                 parse: function(s) {\r
920                         try {\r
921                                 return eval('(' + s + ')');\r
922                         } catch (ex) {\r
923                                 // Ignore\r
924                         }\r
925                 }\r
926 \r
927                 };\r
928 })();\r
929 \r
930 tinymce.create('static tinymce.util.XHR', {\r
931         send : function(o) {\r
932                 var x, t, w = window, c = 0;\r
933 \r
934                 // Default settings\r
935                 o.scope = o.scope || this;\r
936                 o.success_scope = o.success_scope || o.scope;\r
937                 o.error_scope = o.error_scope || o.scope;\r
938                 o.async = o.async === false ? false : true;\r
939                 o.data = o.data || '';\r
940 \r
941                 function get(s) {\r
942                         x = 0;\r
943 \r
944                         try {\r
945                                 x = new ActiveXObject(s);\r
946                         } catch (ex) {\r
947                         }\r
948 \r
949                         return x;\r
950                 };\r
951 \r
952                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');\r
953 \r
954                 if (x) {\r
955                         if (x.overrideMimeType)\r
956                                 x.overrideMimeType(o.content_type);\r
957 \r
958                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);\r
959 \r
960                         if (o.content_type)\r
961                                 x.setRequestHeader('Content-Type', o.content_type);\r
962 \r
963                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r
964 \r
965                         x.send(o.data);\r
966 \r
967                         function ready() {\r
968                                 if (!o.async || x.readyState == 4 || c++ > 10000) {\r
969                                         if (o.success && c < 10000 && x.status == 200)\r
970                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);\r
971                                         else if (o.error)\r
972                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);\r
973 \r
974                                         x = null;\r
975                                 } else\r
976                                         w.setTimeout(ready, 10);\r
977                         };\r
978 \r
979                         // Syncronous request\r
980                         if (!o.async)\r
981                                 return ready();\r
982 \r
983                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE\r
984                         t = w.setTimeout(ready, 10);\r
985                 }\r
986         }\r
987 });\r
988 \r
989 (function() {\r
990         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;\r
991 \r
992         tinymce.create('tinymce.util.JSONRequest', {\r
993                 JSONRequest : function(s) {\r
994                         this.settings = extend({\r
995                         }, s);\r
996                         this.count = 0;\r
997                 },\r
998 \r
999                 send : function(o) {\r
1000                         var ecb = o.error, scb = o.success;\r
1001 \r
1002                         o = extend(this.settings, o);\r
1003 \r
1004                         o.success = function(c, x) {\r
1005                                 c = JSON.parse(c);\r
1006 \r
1007                                 if (typeof(c) == 'undefined') {\r
1008                                         c = {\r
1009                                                 error : 'JSON Parse error.'\r
1010                                         };\r
1011                                 }\r
1012 \r
1013                                 if (c.error)\r
1014                                         ecb.call(o.error_scope || o.scope, c.error, x);\r
1015                                 else\r
1016                                         scb.call(o.success_scope || o.scope, c.result);\r
1017                         };\r
1018 \r
1019                         o.error = function(ty, x) {\r
1020                                 if (ecb)\r
1021                                         ecb.call(o.error_scope || o.scope, ty, x);\r
1022                         };\r
1023 \r
1024                         o.data = JSON.serialize({\r
1025                                 id : o.id || 'c' + (this.count++),\r
1026                                 method : o.method,\r
1027                                 params : o.params\r
1028                         });\r
1029 \r
1030                         // JSON content type for Ruby on rails. Bug: #1883287\r
1031                         o.content_type = 'application/json';\r
1032 \r
1033                         XHR.send(o);\r
1034                 },\r
1035 \r
1036                 'static' : {\r
1037                         sendRPC : function(o) {\r
1038                                 return new tinymce.util.JSONRequest().send(o);\r
1039                         }\r
1040                 }\r
1041         });\r
1042 }());\r
1043 (function(tinymce){\r
1044         tinymce.VK = {\r
1045                 DELETE: 46,\r
1046                 BACKSPACE: 8,\r
1047                 ENTER: 13,\r
1048                 TAB: 9,\r
1049         SPACEBAR: 32,\r
1050                 UP: 38,\r
1051                 DOWN: 40,\r
1052                 modifierPressed: function (e) {\r
1053                         return e.shiftKey || e.ctrlKey || e.altKey;\r
1054                 }\r
1055         }\r
1056 })(tinymce);\r
1057 \r
1058 (function(tinymce) {\r
1059         var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;\r
1060 \r
1061         function cleanupStylesWhenDeleting(ed) {\r
1062                 var dom = ed.dom, selection = ed.selection;\r
1063 \r
1064                 ed.onKeyDown.add(function(ed, e) {\r
1065                         var rng, blockElm, node, clonedSpan, isDelete;\r
1066 \r
1067                         isDelete = e.keyCode == DELETE;\r
1068                         if ((isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {\r
1069                                 e.preventDefault();\r
1070                                 rng = selection.getRng();\r
1071 \r
1072                                 // Find root block\r
1073                                 blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1074 \r
1075                                 // On delete clone the root span of the next block element\r
1076                                 if (isDelete)\r
1077                                         blockElm = dom.getNext(blockElm, dom.isBlock);\r
1078 \r
1079                                 // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace\r
1080                                 if (blockElm) {\r
1081                                         node = blockElm.firstChild;\r
1082 \r
1083                                         // Ignore empty text nodes\r
1084                                         while (node && node.nodeType == 3 && node.nodeValue.length == 0)\r
1085                                                 node = node.nextSibling;\r
1086 \r
1087                                         if (node && node.nodeName === 'SPAN') {\r
1088                                                 clonedSpan = node.cloneNode(false);\r
1089                                         }\r
1090                                 }\r
1091 \r
1092                                 // Do the backspace/delete action\r
1093                                 ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
1094 \r
1095                                 // Find all odd apple-style-spans\r
1096                                 blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1097                                 tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {\r
1098                                         var bm = selection.getBookmark();\r
1099 \r
1100                                         if (clonedSpan) {\r
1101                                                 dom.replace(clonedSpan.cloneNode(false), span, true);\r
1102                                         } else {\r
1103                                                 dom.remove(span, true);\r
1104                                         }\r
1105 \r
1106                                         // Restore the selection\r
1107                                         selection.moveToBookmark(bm);\r
1108                                 });\r
1109                         }\r
1110                 });\r
1111         };\r
1112 \r
1113         function emptyEditorWhenDeleting(ed) {\r
1114 \r
1115                 function serializeRng(rng) {\r
1116                         var body = ed.dom.create("body");\r
1117                         var contents = rng.cloneContents();\r
1118                         body.appendChild(contents);\r
1119                         return ed.selection.serializer.serialize(body, {format: 'html'});\r
1120                 }\r
1121 \r
1122                 function allContentsSelected(rng) {\r
1123                         var selection = serializeRng(rng);\r
1124 \r
1125                         var allRng = ed.dom.createRng();\r
1126                         allRng.selectNode(ed.getBody());\r
1127 \r
1128                         var allSelection = serializeRng(allRng);\r
1129                         return selection === allSelection;\r
1130                 }\r
1131 \r
1132                 ed.onKeyDown.addToTop(function(ed, e) {\r
1133                         var keyCode = e.keyCode;\r
1134                         if (keyCode == DELETE || keyCode == BACKSPACE) {\r
1135                                 var rng = ed.selection.getRng(true);\r
1136                                 if (!rng.collapsed && allContentsSelected(rng)) {\r
1137                                         ed.setContent('', {format : 'raw'});\r
1138                                         ed.nodeChanged();\r
1139                                         e.preventDefault();\r
1140                                 }\r
1141                         }\r
1142                 });\r
1143 \r
1144         };\r
1145 \r
1146         function inputMethodFocus(ed) {\r
1147                 ed.dom.bind(ed.getDoc(), 'focusin', function() {\r
1148                         ed.selection.setRng(ed.selection.getRng());\r
1149                 });\r
1150         };\r
1151 \r
1152         function removeHrOnBackspace(ed) {\r
1153                 ed.onKeyDown.add(function(ed, e) {\r
1154                         if (e.keyCode === BACKSPACE) {\r
1155                                 if (ed.selection.isCollapsed() && ed.selection.getRng(true).startOffset === 0) {\r
1156                                         var node = ed.selection.getNode();\r
1157                                         var previousSibling = node.previousSibling;\r
1158                                         if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {\r
1159                                                 ed.dom.remove(previousSibling);\r
1160                                                 tinymce.dom.Event.cancel(e);\r
1161                                         }\r
1162                                 }\r
1163                         }\r
1164                 })\r
1165         }\r
1166 \r
1167         function focusBody(ed) {\r
1168                 // Fix for a focus bug in FF 3.x where the body element\r
1169                 // wouldn't get proper focus if the user clicked on the HTML element\r
1170                 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4\r
1171                         ed.onMouseDown.add(function(ed, e) {\r
1172                                 if (e.target.nodeName === "HTML") {\r
1173                                         var body = ed.getBody();\r
1174 \r
1175                                         // Blur the body it's focused but not correctly focused\r
1176                                         body.blur();\r
1177 \r
1178                                         // Refocus the body after a little while\r
1179                                         setTimeout(function() {\r
1180                                                 body.focus();\r
1181                                         }, 0);\r
1182                                 }\r
1183                         });\r
1184                 }\r
1185         };\r
1186 \r
1187         function selectControlElements(ed) {\r
1188                 ed.onClick.add(function(ed, e) {\r
1189                         e = e.target;\r
1190 \r
1191                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250\r
1192                         // WebKit can't even do simple things like selecting an image\r
1193                         // Needs tobe the setBaseAndExtend or it will fail to select floated images\r
1194                         if (/^(IMG|HR)$/.test(e.nodeName))\r
1195                                 ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);\r
1196 \r
1197                         if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))\r
1198                                 ed.selection.select(e);\r
1199 \r
1200                         ed.nodeChanged();\r
1201                 });\r
1202         };\r
1203 \r
1204         function removeStylesOnPTagsInheritedFromHeadingTag(ed) {\r
1205                 ed.onKeyDown.add(function(ed, event) {\r
1206                         function checkInHeadingTag(ed) {\r
1207                                 var currentNode = ed.selection.getNode();\r
1208                                 var headingTags = 'h1,h2,h3,h4,h5,h6';\r
1209                                 return ed.dom.is(currentNode, headingTags) || ed.dom.getParent(currentNode, headingTags) !== null;\r
1210                         }\r
1211 \r
1212                         if (event.keyCode === VK.ENTER && !VK.modifierPressed(event) && checkInHeadingTag(ed)) {\r
1213                                 setTimeout(function() {\r
1214                                         var currentNode = ed.selection.getNode();\r
1215                                         if (ed.dom.is(currentNode, 'p')) {\r
1216                                                 ed.dom.setAttrib(currentNode, 'style', null);\r
1217                                                 // While tiny's content is correct after this method call, the content shown is not representative of it and needs to be 'repainted'\r
1218                                                 ed.execCommand('mceCleanup');\r
1219                                         }\r
1220                                 }, 0);\r
1221                         }\r
1222                 });\r
1223         }\r
1224         function selectionChangeNodeChanged(ed) {\r
1225                 var lastRng, selectionTimer;\r
1226 \r
1227                 ed.dom.bind(ed.getDoc(), 'selectionchange', function() {\r
1228                         if (selectionTimer) {\r
1229                                 clearTimeout(selectionTimer);\r
1230                                 selectionTimer = 0;\r
1231                         }\r
1232 \r
1233                         selectionTimer = window.setTimeout(function() {\r
1234                                 var rng = ed.selection.getRng();\r
1235 \r
1236                                 // Compare the ranges to see if it was a real change or not\r
1237                                 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {\r
1238                                         ed.nodeChanged();\r
1239                                         lastRng = rng;\r
1240                                 }\r
1241                         }, 50);\r
1242                 });\r
1243         }\r
1244 \r
1245         function ensureBodyHasRoleApplication(ed) {\r
1246                 document.body.setAttribute("role", "application");\r
1247         }\r
1248         \r
1249         tinymce.create('tinymce.util.Quirks', {\r
1250                 Quirks: function(ed) {\r
1251                         // WebKit\r
1252                         if (tinymce.isWebKit) {\r
1253                                 cleanupStylesWhenDeleting(ed);\r
1254                                 emptyEditorWhenDeleting(ed);\r
1255                                 inputMethodFocus(ed);\r
1256                                 selectControlElements(ed);\r
1257 \r
1258                                 // iOS\r
1259                                 if (tinymce.isIDevice) {\r
1260                                         selectionChangeNodeChanged(ed);\r
1261                                 }\r
1262                         }\r
1263 \r
1264                         // IE\r
1265                         if (tinymce.isIE) {\r
1266                                 removeHrOnBackspace(ed);\r
1267                                 emptyEditorWhenDeleting(ed);\r
1268                                 ensureBodyHasRoleApplication(ed);\r
1269                                 removeStylesOnPTagsInheritedFromHeadingTag(ed)\r
1270                         }\r
1271 \r
1272                         // Gecko\r
1273                         if (tinymce.isGecko) {\r
1274                                 removeHrOnBackspace(ed);\r
1275                                 focusBody(ed);\r
1276                         }\r
1277                 }\r
1278         });\r
1279 })(tinymce);\r
1280 \r
1281 (function(tinymce) {\r
1282         var namedEntities, baseEntities, reverseEntities,\r
1283                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1284                 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1285                 rawCharsRegExp = /[<>&\"\']/g,\r
1286                 entityRegExp = /&(#x|#)?([\w]+);/g,\r
1287                 asciiMap = {\r
1288                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",\r
1289                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",\r
1290                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",\r
1291                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",\r
1292                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"\r
1293                 };\r
1294 \r
1295         // Raw entities\r
1296         baseEntities = {\r
1297                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code\r
1298                 "'" : '&#39;',\r
1299                 '<' : '&lt;',\r
1300                 '>' : '&gt;',\r
1301                 '&' : '&amp;'\r
1302         };\r
1303 \r
1304         // Reverse lookup table for raw entities\r
1305         reverseEntities = {\r
1306                 '&lt;' : '<',\r
1307                 '&gt;' : '>',\r
1308                 '&amp;' : '&',\r
1309                 '&quot;' : '"',\r
1310                 '&apos;' : "'"\r
1311         };\r
1312 \r
1313         // Decodes text by using the browser\r
1314         function nativeDecode(text) {\r
1315                 var elm;\r
1316 \r
1317                 elm = document.createElement("div");\r
1318                 elm.innerHTML = text;\r
1319 \r
1320                 return elm.textContent || elm.innerText || text;\r
1321         };\r
1322 \r
1323         // Build a two way lookup table for the entities\r
1324         function buildEntitiesLookup(items, radix) {\r
1325                 var i, chr, entity, lookup = {};\r
1326 \r
1327                 if (items) {\r
1328                         items = items.split(',');\r
1329                         radix = radix || 10;\r
1330 \r
1331                         // Build entities lookup table\r
1332                         for (i = 0; i < items.length; i += 2) {\r
1333                                 chr = String.fromCharCode(parseInt(items[i], radix));\r
1334 \r
1335                                 // Only add non base entities\r
1336                                 if (!baseEntities[chr]) {\r
1337                                         entity = '&' + items[i + 1] + ';';\r
1338                                         lookup[chr] = entity;\r
1339                                         lookup[entity] = chr;\r
1340                                 }\r
1341                         }\r
1342 \r
1343                         return lookup;\r
1344                 }\r
1345         };\r
1346 \r
1347         // Unpack entities lookup where the numbers are in radix 32 to reduce the size\r
1348         namedEntities = buildEntitiesLookup(\r
1349                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +\r
1350                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +\r
1351                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +\r
1352                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +\r
1353                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +\r
1354                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +\r
1355                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +\r
1356                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +\r
1357                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +\r
1358                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +\r
1359                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +\r
1360                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +\r
1361                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +\r
1362                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +\r
1363                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +\r
1364                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +\r
1365                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +\r
1366                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +\r
1367                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +\r
1368                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +\r
1369                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +\r
1370                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +\r
1371                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +\r
1372                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +\r
1373                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'\r
1374         , 32);\r
1375 \r
1376         tinymce.html = tinymce.html || {};\r
1377 \r
1378         tinymce.html.Entities = {\r
1379                 encodeRaw : function(text, attr) {\r
1380                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1381                                 return baseEntities[chr] || chr;\r
1382                         });\r
1383                 },\r
1384 \r
1385                 encodeAllRaw : function(text) {\r
1386                         return ('' + text).replace(rawCharsRegExp, function(chr) {\r
1387                                 return baseEntities[chr] || chr;\r
1388                         });\r
1389                 },\r
1390 \r
1391                 encodeNumeric : function(text, attr) {\r
1392                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1393                                 // Multi byte sequence convert it to a single entity\r
1394                                 if (chr.length > 1)\r
1395                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';\r
1396 \r
1397                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';\r
1398                         });\r
1399                 },\r
1400 \r
1401                 encodeNamed : function(text, attr, entities) {\r
1402                         entities = entities || namedEntities;\r
1403 \r
1404                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1405                                 return baseEntities[chr] || entities[chr] || chr;\r
1406                         });\r
1407                 },\r
1408 \r
1409                 getEncodeFunc : function(name, entities) {\r
1410                         var Entities = tinymce.html.Entities;\r
1411 \r
1412                         entities = buildEntitiesLookup(entities) || namedEntities;\r
1413 \r
1414                         function encodeNamedAndNumeric(text, attr) {\r
1415                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1416                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;\r
1417                                 });\r
1418                         };\r
1419 \r
1420                         function encodeCustomNamed(text, attr) {\r
1421                                 return Entities.encodeNamed(text, attr, entities);\r
1422                         };\r
1423 \r
1424                         // Replace + with , to be compatible with previous TinyMCE versions\r
1425                         name = tinymce.makeMap(name.replace(/\+/g, ','));\r
1426 \r
1427                         // Named and numeric encoder\r
1428                         if (name.named && name.numeric)\r
1429                                 return encodeNamedAndNumeric;\r
1430 \r
1431                         // Named encoder\r
1432                         if (name.named) {\r
1433                                 // Custom names\r
1434                                 if (entities)\r
1435                                         return encodeCustomNamed;\r
1436 \r
1437                                 return Entities.encodeNamed;\r
1438                         }\r
1439 \r
1440                         // Numeric\r
1441                         if (name.numeric)\r
1442                                 return Entities.encodeNumeric;\r
1443 \r
1444                         // Raw encoder\r
1445                         return Entities.encodeRaw;\r
1446                 },\r
1447 \r
1448                 decode : function(text) {\r
1449                         return text.replace(entityRegExp, function(all, numeric, value) {\r
1450                                 if (numeric) {\r
1451                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);\r
1452 \r
1453                                         // Support upper UTF\r
1454                                         if (value > 0xFFFF) {\r
1455                                                 value -= 0x10000;\r
1456 \r
1457                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));\r
1458                                         } else\r
1459                                                 return asciiMap[value] || String.fromCharCode(value);\r
1460                                 }\r
1461 \r
1462                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);\r
1463                         });\r
1464                 }\r
1465         };\r
1466 })(tinymce);\r
1467 \r
1468 tinymce.html.Styles = function(settings, schema) {\r
1469         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,\r
1470                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,\r
1471                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,\r
1472                 trimRightRegExp = /\s+$/,\r
1473                 urlColorRegExp = /rgb/,\r
1474                 undef, i, encodingLookup = {}, encodingItems;\r
1475 \r
1476         settings = settings || {};\r
1477 \r
1478         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');\r
1479         for (i = 0; i < encodingItems.length; i++) {\r
1480                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;\r
1481                 encodingLookup['\uFEFF' + i] = encodingItems[i];\r
1482         }\r
1483 \r
1484         function toHex(match, r, g, b) {\r
1485                 function hex(val) {\r
1486                         val = parseInt(val).toString(16);\r
1487 \r
1488                         return val.length > 1 ? val : '0' + val; // 0 -> 00\r
1489                 };\r
1490 \r
1491                 return '#' + hex(r) + hex(g) + hex(b);\r
1492         };\r
1493 \r
1494         return {\r
1495                 toHex : function(color) {\r
1496                         return color.replace(rgbRegExp, toHex);\r
1497                 },\r
1498 \r
1499                 parse : function(css) {\r
1500                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;\r
1501 \r
1502                         function compress(prefix, suffix) {\r
1503                                 var top, right, bottom, left;\r
1504 \r
1505                                 // Get values and check it it needs compressing\r
1506                                 top = styles[prefix + '-top' + suffix];\r
1507                                 if (!top)\r
1508                                         return;\r
1509 \r
1510                                 right = styles[prefix + '-right' + suffix];\r
1511                                 if (top != right)\r
1512                                         return;\r
1513 \r
1514                                 bottom = styles[prefix + '-bottom' + suffix];\r
1515                                 if (right != bottom)\r
1516                                         return;\r
1517 \r
1518                                 left = styles[prefix + '-left' + suffix];\r
1519                                 if (bottom != left)\r
1520                                         return;\r
1521 \r
1522                                 // Compress\r
1523                                 styles[prefix + suffix] = left;\r
1524                                 delete styles[prefix + '-top' + suffix];\r
1525                                 delete styles[prefix + '-right' + suffix];\r
1526                                 delete styles[prefix + '-bottom' + suffix];\r
1527                                 delete styles[prefix + '-left' + suffix];\r
1528                         };\r
1529 \r
1530                         function canCompress(key) {\r
1531                                 var value = styles[key], i;\r
1532 \r
1533                                 if (!value || value.indexOf(' ') < 0)\r
1534                                         return;\r
1535 \r
1536                                 value = value.split(' ');\r
1537                                 i = value.length;\r
1538                                 while (i--) {\r
1539                                         if (value[i] !== value[0])\r
1540                                                 return false;\r
1541                                 }\r
1542 \r
1543                                 styles[key] = value[0];\r
1544 \r
1545                                 return true;\r
1546                         };\r
1547 \r
1548                         function compress2(target, a, b, c) {\r
1549                                 if (!canCompress(a))\r
1550                                         return;\r
1551 \r
1552                                 if (!canCompress(b))\r
1553                                         return;\r
1554 \r
1555                                 if (!canCompress(c))\r
1556                                         return;\r
1557 \r
1558                                 // Compress\r
1559                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];\r
1560                                 delete styles[a];\r
1561                                 delete styles[b];\r
1562                                 delete styles[c];\r
1563                         };\r
1564 \r
1565                         // Encodes the specified string by replacing all \" \' ; : with _<num>\r
1566                         function encode(str) {\r
1567                                 isEncoded = true;\r
1568 \r
1569                                 return encodingLookup[str];\r
1570                         };\r
1571 \r
1572                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc\r
1573                         // It will also decode the \" \' if keep_slashes is set to fale or omitted\r
1574                         function decode(str, keep_slashes) {\r
1575                                 if (isEncoded) {\r
1576                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {\r
1577                                                 return encodingLookup[str];\r
1578                                         });\r
1579                                 }\r
1580 \r
1581                                 if (!keep_slashes)\r
1582                                         str = str.replace(/\\([\'\";:])/g, "$1");\r
1583 \r
1584                                 return str;\r
1585                         }\r
1586 \r
1587                         if (css) {\r
1588                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing\r
1589                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {\r
1590                                         return str.replace(/[;:]/g, encode);\r
1591                                 });\r
1592 \r
1593                                 // Parse styles\r
1594                                 while (matches = styleRegExp.exec(css)) {\r
1595                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();\r
1596                                         value = matches[2].replace(trimRightRegExp, '');\r
1597 \r
1598                                         if (name && value.length > 0) {\r
1599                                                 // Opera will produce 700 instead of bold in their style values\r
1600                                                 if (name === 'font-weight' && value === '700')\r
1601                                                         value = 'bold';\r
1602                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED\r
1603                                                         value = value.toLowerCase();            \r
1604 \r
1605                                                 // Convert RGB colors to HEX\r
1606                                                 value = value.replace(rgbRegExp, toHex);\r
1607 \r
1608                                                 // Convert URLs and force them into url('value') format\r
1609                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {\r
1610                                                         str = str || str2;\r
1611 \r
1612                                                         if (str) {\r
1613                                                                 str = decode(str);\r
1614 \r
1615                                                                 // Force strings into single quote format\r
1616                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";\r
1617                                                         }\r
1618 \r
1619                                                         url = decode(url || url2 || url3);\r
1620 \r
1621                                                         // Convert the URL to relative/absolute depending on config\r
1622                                                         if (urlConverter)\r
1623                                                                 url = urlConverter.call(urlConverterScope, url, 'style');\r
1624 \r
1625                                                         // Output new URL format\r
1626                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";\r
1627                                                 });\r
1628 \r
1629                                                 styles[name] = isEncoded ? decode(value, true) : value;\r
1630                                         }\r
1631 \r
1632                                         styleRegExp.lastIndex = matches.index + matches[0].length;\r
1633                                 }\r
1634 \r
1635                                 // Compress the styles to reduce it's size for example IE will expand styles\r
1636                                 compress("border", "");\r
1637                                 compress("border", "-width");\r
1638                                 compress("border", "-color");\r
1639                                 compress("border", "-style");\r
1640                                 compress("padding", "");\r
1641                                 compress("margin", "");\r
1642                                 compress2('border', 'border-width', 'border-style', 'border-color');\r
1643 \r
1644                                 // Remove pointless border, IE produces these\r
1645                                 if (styles.border === 'medium none')\r
1646                                         delete styles.border;\r
1647                         }\r
1648 \r
1649                         return styles;\r
1650                 },\r
1651 \r
1652                 serialize : function(styles, element_name) {\r
1653                         var css = '', name, value;\r
1654 \r
1655                         function serializeStyles(name) {\r
1656                                 var styleList, i, l, value;\r
1657 \r
1658                                 styleList = schema.styles[name];\r
1659                                 if (styleList) {\r
1660                                         for (i = 0, l = styleList.length; i < l; i++) {\r
1661                                                 name = styleList[i];\r
1662                                                 value = styles[name];\r
1663 \r
1664                                                 if (value !== undef && value.length > 0)\r
1665                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
1666                                         }\r
1667                                 }\r
1668                         };\r
1669 \r
1670                         // Serialize styles according to schema\r
1671                         if (element_name && schema && schema.styles) {\r
1672                                 // Serialize global styles and element specific styles\r
1673                                 serializeStyles('*');\r
1674                                 serializeStyles(element_name);\r
1675                         } else {\r
1676                                 // Output the styles in the order they are inside the object\r
1677                                 for (name in styles) {\r
1678                                         value = styles[name];\r
1679 \r
1680                                         if (value !== undef && value.length > 0)\r
1681                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
1682                                 }\r
1683                         }\r
1684 \r
1685                         return css;\r
1686                 }\r
1687         };\r
1688 };\r
1689 \r
1690 (function(tinymce) {\r
1691         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},\r
1692                 defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;\r
1693 \r
1694         function split(str, delim) {\r
1695                 return str.split(delim || ',');\r
1696         };\r
1697 \r
1698         function unpack(lookup, data) {\r
1699                 var key, elements = {};\r
1700 \r
1701                 function replace(value) {\r
1702                         return value.replace(/[A-Z]+/g, function(key) {\r
1703                                 return replace(lookup[key]);\r
1704                         });\r
1705                 };\r
1706 \r
1707                 // Unpack lookup\r
1708                 for (key in lookup) {\r
1709                         if (lookup.hasOwnProperty(key))\r
1710                                 lookup[key] = replace(lookup[key]);\r
1711                 }\r
1712 \r
1713                 // Unpack and parse data into object map\r
1714                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {\r
1715                         attributes = split(attributes, '|');\r
1716 \r
1717                         elements[name] = {\r
1718                                 attributes : makeMap(attributes),\r
1719                                 attributesOrder : attributes,\r
1720                                 children : makeMap(children, '|', {'#comment' : {}})\r
1721                         }\r
1722                 });\r
1723 \r
1724                 return elements;\r
1725         };\r
1726 \r
1727         // Build a lookup table for block elements both lowercase and uppercase\r
1728         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + \r
1729                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + \r
1730                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';\r
1731         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));\r
1732 \r
1733         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size\r
1734         transitional = unpack({\r
1735                 Z : 'H|K|N|O|P',\r
1736                 Y : 'X|form|R|Q',\r
1737                 ZG : 'E|span|width|align|char|charoff|valign',\r
1738                 X : 'p|T|div|U|W|isindex|fieldset|table',\r
1739                 ZF : 'E|align|char|charoff|valign',\r
1740                 W : 'pre|hr|blockquote|address|center|noframes',\r
1741                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',\r
1742                 ZD : '[E][S]',\r
1743                 U : 'ul|ol|dl|menu|dir',\r
1744                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',\r
1745                 T : 'h1|h2|h3|h4|h5|h6',\r
1746                 ZB : 'X|S|Q',\r
1747                 S : 'R|P',\r
1748                 ZA : 'a|G|J|M|O|P',\r
1749                 R : 'a|H|K|N|O',\r
1750                 Q : 'noscript|P',\r
1751                 P : 'ins|del|script',\r
1752                 O : 'input|select|textarea|label|button',\r
1753                 N : 'M|L',\r
1754                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',\r
1755                 L : 'sub|sup',\r
1756                 K : 'J|I',\r
1757                 J : 'tt|i|b|u|s|strike',\r
1758                 I : 'big|small|font|basefont',\r
1759                 H : 'G|F',\r
1760                 G : 'br|span|bdo',\r
1761                 F : 'object|applet|img|map|iframe',\r
1762                 E : 'A|B|C',\r
1763                 D : 'accesskey|tabindex|onfocus|onblur',\r
1764                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',\r
1765                 B : 'lang|xml:lang|dir',\r
1766                 A : 'id|class|style|title'\r
1767         }, 'script[id|charset|type|language|src|defer|xml:space][]' + \r
1768                 'style[B|id|type|media|title|xml:space][]' + \r
1769                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + \r
1770                 'param[id|name|value|valuetype|type][]' + \r
1771                 'p[E|align][#|S]' + \r
1772                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + \r
1773                 'br[A|clear][]' + \r
1774                 'span[E][#|S]' + \r
1775                 'bdo[A|C|B][#|S]' + \r
1776                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + \r
1777                 'h1[E|align][#|S]' + \r
1778                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + \r
1779                 'map[B|C|A|name][X|form|Q|area]' + \r
1780                 'h2[E|align][#|S]' + \r
1781                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + \r
1782                 'h3[E|align][#|S]' + \r
1783                 'tt[E][#|S]' + \r
1784                 'i[E][#|S]' + \r
1785                 'b[E][#|S]' + \r
1786                 'u[E][#|S]' + \r
1787                 's[E][#|S]' + \r
1788                 'strike[E][#|S]' + \r
1789                 'big[E][#|S]' + \r
1790                 'small[E][#|S]' + \r
1791                 'font[A|B|size|color|face][#|S]' + \r
1792                 'basefont[id|size|color|face][]' + \r
1793                 'em[E][#|S]' + \r
1794                 'strong[E][#|S]' + \r
1795                 'dfn[E][#|S]' + \r
1796                 'code[E][#|S]' + \r
1797                 'q[E|cite][#|S]' + \r
1798                 'samp[E][#|S]' + \r
1799                 'kbd[E][#|S]' + \r
1800                 'var[E][#|S]' + \r
1801                 'cite[E][#|S]' + \r
1802                 'abbr[E][#|S]' + \r
1803                 'acronym[E][#|S]' + \r
1804                 'sub[E][#|S]' + \r
1805                 'sup[E][#|S]' + \r
1806                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + \r
1807                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + \r
1808                 'optgroup[E|disabled|label][option]' + \r
1809                 'option[E|selected|disabled|label|value][]' + \r
1810                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + \r
1811                 'label[E|for|accesskey|onfocus|onblur][#|S]' + \r
1812                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + \r
1813                 'h4[E|align][#|S]' + \r
1814                 'ins[E|cite|datetime][#|Y]' + \r
1815                 'h5[E|align][#|S]' + \r
1816                 'del[E|cite|datetime][#|Y]' + \r
1817                 'h6[E|align][#|S]' + \r
1818                 'div[E|align][#|Y]' + \r
1819                 'ul[E|type|compact][li]' + \r
1820                 'li[E|type|value][#|Y]' + \r
1821                 'ol[E|type|compact|start][li]' + \r
1822                 'dl[E|compact][dt|dd]' + \r
1823                 'dt[E][#|S]' + \r
1824                 'dd[E][#|Y]' + \r
1825                 'menu[E|compact][li]' + \r
1826                 'dir[E|compact][li]' + \r
1827                 'pre[E|width|xml:space][#|ZA]' + \r
1828                 'hr[E|align|noshade|size|width][]' + \r
1829                 'blockquote[E|cite][#|Y]' + \r
1830                 'address[E][#|S|p]' + \r
1831                 'center[E][#|Y]' + \r
1832                 'noframes[E][#|Y]' + \r
1833                 'isindex[A|B|prompt][]' + \r
1834                 'fieldset[E][#|legend|Y]' + \r
1835                 'legend[E|accesskey|align][#|S]' + \r
1836                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + \r
1837                 'caption[E|align][#|S]' + \r
1838                 'col[ZG][]' + \r
1839                 'colgroup[ZG][col]' + \r
1840                 'thead[ZF][tr]' + \r
1841                 'tr[ZF|bgcolor][th|td]' + \r
1842                 'th[E|ZE][#|Y]' + \r
1843                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + \r
1844                 'noscript[E][#|Y]' + \r
1845                 'td[E|ZE][#|Y]' + \r
1846                 'tfoot[ZF][tr]' + \r
1847                 'tbody[ZF][tr]' + \r
1848                 'area[E|D|shape|coords|href|nohref|alt|target][]' + \r
1849                 'base[id|href|target][]' + \r
1850                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'\r
1851         );\r
1852 \r
1853         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');\r
1854         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');\r
1855         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);\r
1856         defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea');\r
1857         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');\r
1858 \r
1859         tinymce.html.Schema = function(settings) {\r
1860                 var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap;\r
1861 \r
1862                 settings = settings || {};\r
1863 \r
1864                 // Allow all elements and attributes if verify_html is set to false\r
1865                 if (settings.verify_html === false)\r
1866                         settings.valid_elements = '*[*]';\r
1867 \r
1868                 // Build styles list\r
1869                 if (settings.valid_styles) {\r
1870                         validStyles = {};\r
1871 \r
1872                         // Convert styles into a rule list\r
1873                         each(settings.valid_styles, function(value, key) {\r
1874                                 validStyles[key] = tinymce.explode(value);\r
1875                         });\r
1876                 }\r
1877 \r
1878                 whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap;\r
1879 \r
1880                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.\r
1881                 function patternToRegExp(str) {\r
1882                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');\r
1883                 };\r
1884 \r
1885                 // Parses the specified valid_elements string and adds to the current rules\r
1886                 // This function is a bit hard to read since it's heavily optimized for speed\r
1887                 function addValidElements(valid_elements) {\r
1888                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,\r
1889                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,\r
1890                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,\r
1891                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,\r
1892                                 hasPatternsRegExp = /[*?+]/;\r
1893 \r
1894                         if (valid_elements) {\r
1895                                 // Split valid elements into an array with rules\r
1896                                 valid_elements = split(valid_elements);\r
1897 \r
1898                                 if (elements['@']) {\r
1899                                         globalAttributes = elements['@'].attributes;\r
1900                                         globalAttributesOrder = elements['@'].attributesOrder;\r
1901                                 }\r
1902 \r
1903                                 // Loop all rules\r
1904                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {\r
1905                                         // Parse element rule\r
1906                                         matches = elementRuleRegExp.exec(valid_elements[ei]);\r
1907                                         if (matches) {\r
1908                                                 // Setup local names for matches\r
1909                                                 prefix = matches[1];\r
1910                                                 elementName = matches[2];\r
1911                                                 outputName = matches[3];\r
1912                                                 attrData = matches[4];\r
1913 \r
1914                                                 // Create new attributes and attributesOrder\r
1915                                                 attributes = {};\r
1916                                                 attributesOrder = [];\r
1917 \r
1918                                                 // Create the new element\r
1919                                                 element = {\r
1920                                                         attributes : attributes,\r
1921                                                         attributesOrder : attributesOrder\r
1922                                                 };\r
1923 \r
1924                                                 // Padd empty elements prefix\r
1925                                                 if (prefix === '#')\r
1926                                                         element.paddEmpty = true;\r
1927 \r
1928                                                 // Remove empty elements prefix\r
1929                                                 if (prefix === '-')\r
1930                                                         element.removeEmpty = true;\r
1931 \r
1932                                                 // Copy attributes from global rule into current rule\r
1933                                                 if (globalAttributes) {\r
1934                                                         for (key in globalAttributes)\r
1935                                                                 attributes[key] = globalAttributes[key];\r
1936 \r
1937                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);\r
1938                                                 }\r
1939 \r
1940                                                 // Attributes defined\r
1941                                                 if (attrData) {\r
1942                                                         attrData = split(attrData, '|');\r
1943                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {\r
1944                                                                 matches = attrRuleRegExp.exec(attrData[ai]);\r
1945                                                                 if (matches) {\r
1946                                                                         attr = {};\r
1947                                                                         attrType = matches[1];\r
1948                                                                         attrName = matches[2].replace(/::/g, ':');\r
1949                                                                         prefix = matches[3];\r
1950                                                                         value = matches[4];\r
1951 \r
1952                                                                         // Required\r
1953                                                                         if (attrType === '!') {\r
1954                                                                                 element.attributesRequired = element.attributesRequired || [];\r
1955                                                                                 element.attributesRequired.push(attrName);\r
1956                                                                                 attr.required = true;\r
1957                                                                         }\r
1958 \r
1959                                                                         // Denied from global\r
1960                                                                         if (attrType === '-') {\r
1961                                                                                 delete attributes[attrName];\r
1962                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);\r
1963                                                                                 continue;\r
1964                                                                         }\r
1965 \r
1966                                                                         // Default value\r
1967                                                                         if (prefix) {\r
1968                                                                                 // Default value\r
1969                                                                                 if (prefix === '=') {\r
1970                                                                                         element.attributesDefault = element.attributesDefault || [];\r
1971                                                                                         element.attributesDefault.push({name: attrName, value: value});\r
1972                                                                                         attr.defaultValue = value;\r
1973                                                                                 }\r
1974 \r
1975                                                                                 // Forced value\r
1976                                                                                 if (prefix === ':') {\r
1977                                                                                         element.attributesForced = element.attributesForced || [];\r
1978                                                                                         element.attributesForced.push({name: attrName, value: value});\r
1979                                                                                         attr.forcedValue = value;\r
1980                                                                                 }\r
1981 \r
1982                                                                                 // Required values\r
1983                                                                                 if (prefix === '<')\r
1984                                                                                         attr.validValues = makeMap(value, '?');\r
1985                                                                         }\r
1986 \r
1987                                                                         // Check for attribute patterns\r
1988                                                                         if (hasPatternsRegExp.test(attrName)) {\r
1989                                                                                 element.attributePatterns = element.attributePatterns || [];\r
1990                                                                                 attr.pattern = patternToRegExp(attrName);\r
1991                                                                                 element.attributePatterns.push(attr);\r
1992                                                                         } else {\r
1993                                                                                 // Add attribute to order list if it doesn't already exist\r
1994                                                                                 if (!attributes[attrName])\r
1995                                                                                         attributesOrder.push(attrName);\r
1996 \r
1997                                                                                 attributes[attrName] = attr;\r
1998                                                                         }\r
1999                                                                 }\r
2000                                                         }\r
2001                                                 }\r
2002 \r
2003                                                 // Global rule, store away these for later usage\r
2004                                                 if (!globalAttributes && elementName == '@') {\r
2005                                                         globalAttributes = attributes;\r
2006                                                         globalAttributesOrder = attributesOrder;\r
2007                                                 }\r
2008 \r
2009                                                 // Handle substitute elements such as b/strong\r
2010                                                 if (outputName) {\r
2011                                                         element.outputName = elementName;\r
2012                                                         elements[outputName] = element;\r
2013                                                 }\r
2014 \r
2015                                                 // Add pattern or exact element\r
2016                                                 if (hasPatternsRegExp.test(elementName)) {\r
2017                                                         element.pattern = patternToRegExp(elementName);\r
2018                                                         patternElements.push(element);\r
2019                                                 } else\r
2020                                                         elements[elementName] = element;\r
2021                                         }\r
2022                                 }\r
2023                         }\r
2024                 };\r
2025 \r
2026                 function setValidElements(valid_elements) {\r
2027                         elements = {};\r
2028                         patternElements = [];\r
2029 \r
2030                         addValidElements(valid_elements);\r
2031 \r
2032                         each(transitional, function(element, name) {\r
2033                                 children[name] = element.children;\r
2034                         });\r
2035                 };\r
2036 \r
2037                 // Adds custom non HTML elements to the schema\r
2038                 function addCustomElements(custom_elements) {\r
2039                         var customElementRegExp = /^(~)?(.+)$/;\r
2040 \r
2041                         if (custom_elements) {\r
2042                                 each(split(custom_elements), function(rule) {\r
2043                                         var matches = customElementRegExp.exec(rule),\r
2044                                                 inline = matches[1] === '~',\r
2045                                                 cloneName = inline ? 'span' : 'div',\r
2046                                                 name = matches[2];\r
2047 \r
2048                                         children[name] = children[cloneName];\r
2049                                         customElementsMap[name] = cloneName;\r
2050 \r
2051                                         // If it's not marked as inline then add it to valid block elements\r
2052                                         if (!inline)\r
2053                                                 blockElementsMap[name] = {};\r
2054 \r
2055                                         // Add custom elements at span/div positions\r
2056                                         each(children, function(element, child) {\r
2057                                                 if (element[cloneName])\r
2058                                                         element[name] = element[cloneName];\r
2059                                         });\r
2060                                 });\r
2061                         }\r
2062                 };\r
2063 \r
2064                 // Adds valid children to the schema object\r
2065                 function addValidChildren(valid_children) {\r
2066                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;\r
2067 \r
2068                         if (valid_children) {\r
2069                                 each(split(valid_children), function(rule) {\r
2070                                         var matches = childRuleRegExp.exec(rule), parent, prefix;\r
2071 \r
2072                                         if (matches) {\r
2073                                                 prefix = matches[1];\r
2074 \r
2075                                                 // Add/remove items from default\r
2076                                                 if (prefix)\r
2077                                                         parent = children[matches[2]];\r
2078                                                 else\r
2079                                                         parent = children[matches[2]] = {'#comment' : {}};\r
2080 \r
2081                                                 parent = children[matches[2]];\r
2082 \r
2083                                                 each(split(matches[3], '|'), function(child) {\r
2084                                                         if (prefix === '-')\r
2085                                                                 delete parent[child];\r
2086                                                         else\r
2087                                                                 parent[child] = {};\r
2088                                                 });\r
2089                                         }\r
2090                                 });\r
2091                         }\r
2092                 };\r
2093 \r
2094                 function getElementRule(name) {\r
2095                         var element = elements[name], i;\r
2096 \r
2097                         // Exact match found\r
2098                         if (element)\r
2099                                 return element;\r
2100 \r
2101                         // No exact match then try the patterns\r
2102                         i = patternElements.length;\r
2103                         while (i--) {\r
2104                                 element = patternElements[i];\r
2105 \r
2106                                 if (element.pattern.test(name))\r
2107                                         return element;\r
2108                         }\r
2109                 };\r
2110 \r
2111                 if (!settings.valid_elements) {\r
2112                         // No valid elements defined then clone the elements from the transitional spec\r
2113                         each(transitional, function(element, name) {\r
2114                                 elements[name] = {\r
2115                                         attributes : element.attributes,\r
2116                                         attributesOrder : element.attributesOrder\r
2117                                 };\r
2118 \r
2119                                 children[name] = element.children;\r
2120                         });\r
2121 \r
2122                         // Switch these\r
2123                         each(split('strong/b,em/i'), function(item) {\r
2124                                 item = split(item, '/');\r
2125                                 elements[item[1]].outputName = item[0];\r
2126                         });\r
2127 \r
2128                         // Add default alt attribute for images\r
2129                         elements.img.attributesDefault = [{name: 'alt', value: ''}];\r
2130 \r
2131                         // Remove these if they are empty by default\r
2132                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {\r
2133                                 elements[name].removeEmpty = true;\r
2134                         });\r
2135 \r
2136                         // Padd these by default\r
2137                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {\r
2138                                 elements[name].paddEmpty = true;\r
2139                         });\r
2140                 } else\r
2141                         setValidElements(settings.valid_elements);\r
2142 \r
2143                 addCustomElements(settings.custom_elements);\r
2144                 addValidChildren(settings.valid_children);\r
2145                 addValidElements(settings.extended_valid_elements);\r
2146 \r
2147                 // Todo: Remove this when we fix list handling to be valid\r
2148                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');\r
2149 \r
2150                 // If the user didn't allow span only allow internal spans\r
2151                 if (!getElementRule('span'))\r
2152                         addValidElements('span[!data-mce-type|*]');\r
2153 \r
2154                 // Delete invalid elements\r
2155                 if (settings.invalid_elements) {\r
2156                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {\r
2157                                 if (elements[item])\r
2158                                         delete elements[item];\r
2159                         });\r
2160                 }\r
2161 \r
2162                 self.children = children;\r
2163 \r
2164                 self.styles = validStyles;\r
2165 \r
2166                 self.getBoolAttrs = function() {\r
2167                         return boolAttrMap;\r
2168                 };\r
2169 \r
2170                 self.getBlockElements = function() {\r
2171                         return blockElementsMap;\r
2172                 };\r
2173 \r
2174                 self.getShortEndedElements = function() {\r
2175                         return shortEndedElementsMap;\r
2176                 };\r
2177 \r
2178                 self.getSelfClosingElements = function() {\r
2179                         return selfClosingElementsMap;\r
2180                 };\r
2181 \r
2182                 self.getNonEmptyElements = function() {\r
2183                         return nonEmptyElementsMap;\r
2184                 };\r
2185 \r
2186                 self.getWhiteSpaceElements = function() {\r
2187                         return whiteSpaceElementsMap;\r
2188                 };\r
2189 \r
2190                 self.isValidChild = function(name, child) {\r
2191                         var parent = children[name];\r
2192 \r
2193                         return !!(parent && parent[child]);\r
2194                 };\r
2195 \r
2196                 self.getElementRule = getElementRule;\r
2197 \r
2198                 self.getCustomElements = function() {\r
2199                         return customElementsMap;\r
2200                 };\r
2201 \r
2202                 self.addValidElements = addValidElements;\r
2203 \r
2204                 self.setValidElements = setValidElements;\r
2205 \r
2206                 self.addCustomElements = addCustomElements;\r
2207 \r
2208                 self.addValidChildren = addValidChildren;\r
2209         };\r
2210 \r
2211         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils\r
2212         tinymce.html.Schema.boolAttrMap = boolAttrMap;\r
2213         tinymce.html.Schema.blockElementsMap = blockElementsMap;\r
2214 })(tinymce);\r
2215 \r
2216 (function(tinymce) {\r
2217         tinymce.html.SaxParser = function(settings, schema) {\r
2218                 var self = this, noop = function() {};\r
2219 \r
2220                 settings = settings || {};\r
2221                 self.schema = schema = schema || new tinymce.html.Schema();\r
2222 \r
2223                 if (settings.fix_self_closing !== false)\r
2224                         settings.fix_self_closing = true;\r
2225 \r
2226                 // Add handler functions from settings and setup default handlers\r
2227                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {\r
2228                         if (name)\r
2229                                 self[name] = settings[name] || noop;\r
2230                 });\r
2231 \r
2232                 self.parse = function(html) {\r
2233                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,\r
2234                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,\r
2235                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,\r
2236                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;\r
2237 \r
2238                         function processEndTag(name) {\r
2239                                 var pos, i;\r
2240 \r
2241                                 // Find position of parent of the same type\r
2242                                 pos = stack.length;\r
2243                                 while (pos--) {\r
2244                                         if (stack[pos].name === name)\r
2245                                                 break;                                          \r
2246                                 }\r
2247 \r
2248                                 // Found parent\r
2249                                 if (pos >= 0) {\r
2250                                         // Close all the open elements\r
2251                                         for (i = stack.length - 1; i >= pos; i--) {\r
2252                                                 name = stack[i];\r
2253 \r
2254                                                 if (name.valid)\r
2255                                                         self.end(name.name);\r
2256                                         }\r
2257 \r
2258                                         // Remove the open elements from the stack\r
2259                                         stack.length = pos;\r
2260                                 }\r
2261                         };\r
2262 \r
2263                         // Precompile RegExps and map objects\r
2264                         tokenRegExp = new RegExp('<(?:' +\r
2265                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment\r
2266                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA\r
2267                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE\r
2268                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI\r
2269                                 '(?:\\/([^>]+)>)|' + // End element\r
2270                                 '(?:([^\\s\\/<>]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/)>)' + // Start element\r
2271                         ')', 'g');\r
2272 \r
2273                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;\r
2274                         specialElements = {\r
2275                                 'script' : /<\/script[^>]*>/gi,\r
2276                                 'style' : /<\/style[^>]*>/gi,\r
2277                                 'noscript' : /<\/noscript[^>]*>/gi\r
2278                         };\r
2279 \r
2280                         // Setup lookup tables for empty elements and boolean attributes\r
2281                         shortEndedElements = schema.getShortEndedElements();\r
2282                         selfClosing = schema.getSelfClosingElements();\r
2283                         fillAttrsMap = schema.getBoolAttrs();\r
2284                         validate = settings.validate;\r
2285                         removeInternalElements = settings.remove_internals;\r
2286                         fixSelfClosing = settings.fix_self_closing;\r
2287                         isIE = tinymce.isIE;\r
2288                         invalidPrefixRegExp = /^:/;\r
2289 \r
2290                         while (matches = tokenRegExp.exec(html)) {\r
2291                                 // Text\r
2292                                 if (index < matches.index)\r
2293                                         self.text(decode(html.substr(index, matches.index - index)));\r
2294 \r
2295                                 if (value = matches[6]) { // End element\r
2296                                         value = value.toLowerCase();\r
2297 \r
2298                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
2299                                         if (isIE && invalidPrefixRegExp.test(value))\r
2300                                                 value = value.substr(1);\r
2301 \r
2302                                         processEndTag(value);\r
2303                                 } else if (value = matches[7]) { // Start element\r
2304                                         value = value.toLowerCase();\r
2305 \r
2306                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
2307                                         if (isIE && invalidPrefixRegExp.test(value))\r
2308                                                 value = value.substr(1);\r
2309 \r
2310                                         isShortEnded = value in shortEndedElements;\r
2311 \r
2312                                         // Is self closing tag for example an <li> after an open <li>\r
2313                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)\r
2314                                                 processEndTag(value);\r
2315 \r
2316                                         // Validate element\r
2317                                         if (!validate || (elementRule = schema.getElementRule(value))) {\r
2318                                                 isValidElement = true;\r
2319 \r
2320                                                 // Grab attributes map and patters when validation is enabled\r
2321                                                 if (validate) {\r
2322                                                         validAttributesMap = elementRule.attributes;\r
2323                                                         validAttributePatterns = elementRule.attributePatterns;\r
2324                                                 }\r
2325 \r
2326                                                 // Parse attributes\r
2327                                                 if (attribsValue = matches[8]) {\r
2328                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element\r
2329 \r
2330                                                         // If the element has internal attributes then remove it if we are told to do so\r
2331                                                         if (isInternalElement && removeInternalElements)\r
2332                                                                 isValidElement = false;\r
2333 \r
2334                                                         attrList = [];\r
2335                                                         attrList.map = {};\r
2336 \r
2337                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {\r
2338                                                                 var attrRule, i;\r
2339 \r
2340                                                                 name = name.toLowerCase();\r
2341                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute\r
2342 \r
2343                                                                 // Validate name and value\r
2344                                                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {\r
2345                                                                         attrRule = validAttributesMap[name];\r
2346 \r
2347                                                                         // Find rule by pattern matching\r
2348                                                                         if (!attrRule && validAttributePatterns) {\r
2349                                                                                 i = validAttributePatterns.length;\r
2350                                                                                 while (i--) {\r
2351                                                                                         attrRule = validAttributePatterns[i];\r
2352                                                                                         if (attrRule.pattern.test(name))\r
2353                                                                                                 break;\r
2354                                                                                 }\r
2355 \r
2356                                                                                 // No rule matched\r
2357                                                                                 if (i === -1)\r
2358                                                                                         attrRule = null;\r
2359                                                                         }\r
2360 \r
2361                                                                         // No attribute rule found\r
2362                                                                         if (!attrRule)\r
2363                                                                                 return;\r
2364 \r
2365                                                                         // Validate value\r
2366                                                                         if (attrRule.validValues && !(value in attrRule.validValues))\r
2367                                                                                 return;\r
2368                                                                 }\r
2369 \r
2370                                                                 // Add attribute to list and map\r
2371                                                                 attrList.map[name] = value;\r
2372                                                                 attrList.push({\r
2373                                                                         name: name,\r
2374                                                                         value: value\r
2375                                                                 });\r
2376                                                         });\r
2377                                                 } else {\r
2378                                                         attrList = [];\r
2379                                                         attrList.map = {};\r
2380                                                 }\r
2381 \r
2382                                                 // Process attributes if validation is enabled\r
2383                                                 if (validate && !isInternalElement) {\r
2384                                                         attributesRequired = elementRule.attributesRequired;\r
2385                                                         attributesDefault = elementRule.attributesDefault;\r
2386                                                         attributesForced = elementRule.attributesForced;\r
2387 \r
2388                                                         // Handle forced attributes\r
2389                                                         if (attributesForced) {\r
2390                                                                 i = attributesForced.length;\r
2391                                                                 while (i--) {\r
2392                                                                         attr = attributesForced[i];\r
2393                                                                         name = attr.name;\r
2394                                                                         attrValue = attr.value;\r
2395 \r
2396                                                                         if (attrValue === '{$uid}')\r
2397                                                                                 attrValue = 'mce_' + idCount++;\r
2398 \r
2399                                                                         attrList.map[name] = attrValue;\r
2400                                                                         attrList.push({name: name, value: attrValue});\r
2401                                                                 }\r
2402                                                         }\r
2403 \r
2404                                                         // Handle default attributes\r
2405                                                         if (attributesDefault) {\r
2406                                                                 i = attributesDefault.length;\r
2407                                                                 while (i--) {\r
2408                                                                         attr = attributesDefault[i];\r
2409                                                                         name = attr.name;\r
2410 \r
2411                                                                         if (!(name in attrList.map)) {\r
2412                                                                                 attrValue = attr.value;\r
2413 \r
2414                                                                                 if (attrValue === '{$uid}')\r
2415                                                                                         attrValue = 'mce_' + idCount++;\r
2416 \r
2417                                                                                 attrList.map[name] = attrValue;\r
2418                                                                                 attrList.push({name: name, value: attrValue});\r
2419                                                                         }\r
2420                                                                 }\r
2421                                                         }\r
2422 \r
2423                                                         // Handle required attributes\r
2424                                                         if (attributesRequired) {\r
2425                                                                 i = attributesRequired.length;\r
2426                                                                 while (i--) {\r
2427                                                                         if (attributesRequired[i] in attrList.map)\r
2428                                                                                 break;\r
2429                                                                 }\r
2430 \r
2431                                                                 // None of the required attributes where found\r
2432                                                                 if (i === -1)\r
2433                                                                         isValidElement = false;\r
2434                                                         }\r
2435 \r
2436                                                         // Invalidate element if it's marked as bogus\r
2437                                                         if (attrList.map['data-mce-bogus'])\r
2438                                                                 isValidElement = false;\r
2439                                                 }\r
2440 \r
2441                                                 if (isValidElement)\r
2442                                                         self.start(value, attrList, isShortEnded);\r
2443                                         } else\r
2444                                                 isValidElement = false;\r
2445 \r
2446                                         // Treat script, noscript and style a bit different since they may include code that looks like elements\r
2447                                         if (endRegExp = specialElements[value]) {\r
2448                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;\r
2449 \r
2450                                                 if (matches = endRegExp.exec(html)) {\r
2451                                                         if (isValidElement)\r
2452                                                                 text = html.substr(index, matches.index - index);\r
2453 \r
2454                                                         index = matches.index + matches[0].length;\r
2455                                                 } else {\r
2456                                                         text = html.substr(index);\r
2457                                                         index = html.length;\r
2458                                                 }\r
2459 \r
2460                                                 if (isValidElement && text.length > 0)\r
2461                                                         self.text(text, true);\r
2462 \r
2463                                                 if (isValidElement)\r
2464                                                         self.end(value);\r
2465 \r
2466                                                 tokenRegExp.lastIndex = index;\r
2467                                                 continue;\r
2468                                         }\r
2469 \r
2470                                         // Push value on to stack\r
2471                                         if (!isShortEnded) {\r
2472                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)\r
2473                                                         stack.push({name: value, valid: isValidElement});\r
2474                                                 else if (isValidElement)\r
2475                                                         self.end(value);\r
2476                                         }\r
2477                                 } else if (value = matches[1]) { // Comment\r
2478                                         self.comment(value);\r
2479                                 } else if (value = matches[2]) { // CDATA\r
2480                                         self.cdata(value);\r
2481                                 } else if (value = matches[3]) { // DOCTYPE\r
2482                                         self.doctype(value);\r
2483                                 } else if (value = matches[4]) { // PI\r
2484                                         self.pi(value, matches[5]);\r
2485                                 }\r
2486 \r
2487                                 index = matches.index + matches[0].length;\r
2488                         }\r
2489 \r
2490                         // Text\r
2491                         if (index < html.length)\r
2492                                 self.text(decode(html.substr(index)));\r
2493 \r
2494                         // Close any open elements\r
2495                         for (i = stack.length - 1; i >= 0; i--) {\r
2496                                 value = stack[i];\r
2497 \r
2498                                 if (value.valid)\r
2499                                         self.end(value.name);\r
2500                         }\r
2501                 };\r
2502         }\r
2503 })(tinymce);\r
2504 \r
2505 (function(tinymce) {\r
2506         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {\r
2507                 '#text' : 3,\r
2508                 '#comment' : 8,\r
2509                 '#cdata' : 4,\r
2510                 '#pi' : 7,\r
2511                 '#doctype' : 10,\r
2512                 '#document-fragment' : 11\r
2513         };\r
2514 \r
2515         // Walks the tree left/right\r
2516         function walk(node, root_node, prev) {\r
2517                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';\r
2518 \r
2519                 // Walk into nodes if it has a start\r
2520                 if (node[startName])\r
2521                         return node[startName];\r
2522 \r
2523                 // Return the sibling if it has one\r
2524                 if (node !== root_node) {\r
2525                         sibling = node[siblingName];\r
2526 \r
2527                         if (sibling)\r
2528                                 return sibling;\r
2529 \r
2530                         // Walk up the parents to look for siblings\r
2531                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {\r
2532                                 sibling = parent[siblingName];\r
2533 \r
2534                                 if (sibling)\r
2535                                         return sibling;\r
2536                         }\r
2537                 }\r
2538         };\r
2539 \r
2540         function Node(name, type) {\r
2541                 this.name = name;\r
2542                 this.type = type;\r
2543 \r
2544                 if (type === 1) {\r
2545                         this.attributes = [];\r
2546                         this.attributes.map = {};\r
2547                 }\r
2548         }\r
2549 \r
2550         tinymce.extend(Node.prototype, {\r
2551                 replace : function(node) {\r
2552                         var self = this;\r
2553 \r
2554                         if (node.parent)\r
2555                                 node.remove();\r
2556 \r
2557                         self.insert(node, self);\r
2558                         self.remove();\r
2559 \r
2560                         return self;\r
2561                 },\r
2562 \r
2563                 attr : function(name, value) {\r
2564                         var self = this, attrs, i, undef;\r
2565 \r
2566                         if (typeof name !== "string") {\r
2567                                 for (i in name)\r
2568                                         self.attr(i, name[i]);\r
2569 \r
2570                                 return self;\r
2571                         }\r
2572 \r
2573                         if (attrs = self.attributes) {\r
2574                                 if (value !== undef) {\r
2575                                         // Remove attribute\r
2576                                         if (value === null) {\r
2577                                                 if (name in attrs.map) {\r
2578                                                         delete attrs.map[name];\r
2579 \r
2580                                                         i = attrs.length;\r
2581                                                         while (i--) {\r
2582                                                                 if (attrs[i].name === name) {\r
2583                                                                         attrs = attrs.splice(i, 1);\r
2584                                                                         return self;\r
2585                                                                 }\r
2586                                                         }\r
2587                                                 }\r
2588 \r
2589                                                 return self;\r
2590                                         }\r
2591 \r
2592                                         // Set attribute\r
2593                                         if (name in attrs.map) {\r
2594                                                 // Set attribute\r
2595                                                 i = attrs.length;\r
2596                                                 while (i--) {\r
2597                                                         if (attrs[i].name === name) {\r
2598                                                                 attrs[i].value = value;\r
2599                                                                 break;\r
2600                                                         }\r
2601                                                 }\r
2602                                         } else\r
2603                                                 attrs.push({name: name, value: value});\r
2604 \r
2605                                         attrs.map[name] = value;\r
2606 \r
2607                                         return self;\r
2608                                 } else {\r
2609                                         return attrs.map[name];\r
2610                                 }\r
2611                         }\r
2612                 },\r
2613 \r
2614                 clone : function() {\r
2615                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;\r
2616 \r
2617                         // Clone element attributes\r
2618                         if (selfAttrs = self.attributes) {\r
2619                                 cloneAttrs = [];\r
2620                                 cloneAttrs.map = {};\r
2621 \r
2622                                 for (i = 0, l = selfAttrs.length; i < l; i++) {\r
2623                                         selfAttr = selfAttrs[i];\r
2624 \r
2625                                         // Clone everything except id\r
2626                                         if (selfAttr.name !== 'id') {\r
2627                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};\r
2628                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;\r
2629                                         }\r
2630                                 }\r
2631 \r
2632                                 clone.attributes = cloneAttrs;\r
2633                         }\r
2634 \r
2635                         clone.value = self.value;\r
2636                         clone.shortEnded = self.shortEnded;\r
2637 \r
2638                         return clone;\r
2639                 },\r
2640 \r
2641                 wrap : function(wrapper) {\r
2642                         var self = this;\r
2643 \r
2644                         self.parent.insert(wrapper, self);\r
2645                         wrapper.append(self);\r
2646 \r
2647                         return self;\r
2648                 },\r
2649 \r
2650                 unwrap : function() {\r
2651                         var self = this, node, next;\r
2652 \r
2653                         for (node = self.firstChild; node; ) {\r
2654                                 next = node.next;\r
2655                                 self.insert(node, self, true);\r
2656                                 node = next;\r
2657                         }\r
2658 \r
2659                         self.remove();\r
2660                 },\r
2661 \r
2662                 remove : function() {\r
2663                         var self = this, parent = self.parent, next = self.next, prev = self.prev;\r
2664 \r
2665                         if (parent) {\r
2666                                 if (parent.firstChild === self) {\r
2667                                         parent.firstChild = next;\r
2668 \r
2669                                         if (next)\r
2670                                                 next.prev = null;\r
2671                                 } else {\r
2672                                         prev.next = next;\r
2673                                 }\r
2674 \r
2675                                 if (parent.lastChild === self) {\r
2676                                         parent.lastChild = prev;\r
2677 \r
2678                                         if (prev)\r
2679                                                 prev.next = null;\r
2680                                 } else {\r
2681                                         next.prev = prev;\r
2682                                 }\r
2683 \r
2684                                 self.parent = self.next = self.prev = null;\r
2685                         }\r
2686 \r
2687                         return self;\r
2688                 },\r
2689 \r
2690                 append : function(node) {\r
2691                         var self = this, last;\r
2692 \r
2693                         if (node.parent)\r
2694                                 node.remove();\r
2695 \r
2696                         last = self.lastChild;\r
2697                         if (last) {\r
2698                                 last.next = node;\r
2699                                 node.prev = last;\r
2700                                 self.lastChild = node;\r
2701                         } else\r
2702                                 self.lastChild = self.firstChild = node;\r
2703 \r
2704                         node.parent = self;\r
2705 \r
2706                         return node;\r
2707                 },\r
2708 \r
2709                 insert : function(node, ref_node, before) {\r
2710                         var parent;\r
2711 \r
2712                         if (node.parent)\r
2713                                 node.remove();\r
2714 \r
2715                         parent = ref_node.parent || this;\r
2716 \r
2717                         if (before) {\r
2718                                 if (ref_node === parent.firstChild)\r
2719                                         parent.firstChild = node;\r
2720                                 else\r
2721                                         ref_node.prev.next = node;\r
2722 \r
2723                                 node.prev = ref_node.prev;\r
2724                                 node.next = ref_node;\r
2725                                 ref_node.prev = node;\r
2726                         } else {\r
2727                                 if (ref_node === parent.lastChild)\r
2728                                         parent.lastChild = node;\r
2729                                 else\r
2730                                         ref_node.next.prev = node;\r
2731 \r
2732                                 node.next = ref_node.next;\r
2733                                 node.prev = ref_node;\r
2734                                 ref_node.next = node;\r
2735                         }\r
2736 \r
2737                         node.parent = parent;\r
2738 \r
2739                         return node;\r
2740                 },\r
2741 \r
2742                 getAll : function(name) {\r
2743                         var self = this, node, collection = [];\r
2744 \r
2745                         for (node = self.firstChild; node; node = walk(node, self)) {\r
2746                                 if (node.name === name)\r
2747                                         collection.push(node);\r
2748                         }\r
2749 \r
2750                         return collection;\r
2751                 },\r
2752 \r
2753                 empty : function() {\r
2754                         var self = this, nodes, i, node;\r
2755 \r
2756                         // Remove all children\r
2757                         if (self.firstChild) {\r
2758                                 nodes = [];\r
2759 \r
2760                                 // Collect the children\r
2761                                 for (node = self.firstChild; node; node = walk(node, self))\r
2762                                         nodes.push(node);\r
2763 \r
2764                                 // Remove the children\r
2765                                 i = nodes.length;\r
2766                                 while (i--) {\r
2767                                         node = nodes[i];\r
2768                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;\r
2769                                 }\r
2770                         }\r
2771 \r
2772                         self.firstChild = self.lastChild = null;\r
2773 \r
2774                         return self;\r
2775                 },\r
2776 \r
2777                 isEmpty : function(elements) {\r
2778                         var self = this, node = self.firstChild, i, name;\r
2779 \r
2780                         if (node) {\r
2781                                 do {\r
2782                                         if (node.type === 1) {\r
2783                                                 // Ignore bogus elements\r
2784                                                 if (node.attributes.map['data-mce-bogus'])\r
2785                                                         continue;\r
2786 \r
2787                                                 // Keep empty elements like <img />\r
2788                                                 if (elements[node.name])\r
2789                                                         return false;\r
2790 \r
2791                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>\r
2792                                                 i = node.attributes.length;\r
2793                                                 while (i--) {\r
2794                                                         name = node.attributes[i].name;\r
2795                                                         if (name === "name" || name.indexOf('data-') === 0)\r
2796                                                                 return false;\r
2797                                                 }\r
2798                                         }\r
2799 \r
2800                                         // Keep comments\r
2801                                         if (node.type === 8)\r
2802                                                 return false;\r
2803                                         \r
2804                                         // Keep non whitespace text nodes\r
2805                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))\r
2806                                                 return false;\r
2807                                 } while (node = walk(node, self));\r
2808                         }\r
2809 \r
2810                         return true;\r
2811                 },\r
2812 \r
2813                 walk : function(prev) {\r
2814                         return walk(this, null, prev);\r
2815                 }\r
2816         });\r
2817 \r
2818         tinymce.extend(Node, {\r
2819                 create : function(name, attrs) {\r
2820                         var node, attrName;\r
2821 \r
2822                         // Create node\r
2823                         node = new Node(name, typeLookup[name] || 1);\r
2824 \r
2825                         // Add attributes if needed\r
2826                         if (attrs) {\r
2827                                 for (attrName in attrs)\r
2828                                         node.attr(attrName, attrs[attrName]);\r
2829                         }\r
2830 \r
2831                         return node;\r
2832                 }\r
2833         });\r
2834 \r
2835         tinymce.html.Node = Node;\r
2836 })(tinymce);\r
2837 \r
2838 (function(tinymce) {\r
2839         var Node = tinymce.html.Node;\r
2840 \r
2841         tinymce.html.DomParser = function(settings, schema) {\r
2842                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};\r
2843 \r
2844                 settings = settings || {};\r
2845                 settings.validate = "validate" in settings ? settings.validate : true;\r
2846                 settings.root_name = settings.root_name || 'body';\r
2847                 self.schema = schema = schema || new tinymce.html.Schema();\r
2848 \r
2849                 function fixInvalidChildren(nodes) {\r
2850                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,\r
2851                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;\r
2852 \r
2853                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');\r
2854                         nonEmptyElements = schema.getNonEmptyElements();\r
2855 \r
2856                         for (ni = 0; ni < nodes.length; ni++) {\r
2857                                 node = nodes[ni];\r
2858 \r
2859                                 // Already removed\r
2860                                 if (!node.parent)\r
2861                                         continue;\r
2862 \r
2863                                 // Get list of all parent nodes until we find a valid parent to stick the child into\r
2864                                 parents = [node];\r
2865                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)\r
2866                                         parents.push(parent);\r
2867 \r
2868                                 // Found a suitable parent\r
2869                                 if (parent && parents.length > 1) {\r
2870                                         // Reverse the array since it makes looping easier\r
2871                                         parents.reverse();\r
2872 \r
2873                                         // Clone the related parent and insert that after the moved node\r
2874                                         newParent = currentNode = self.filterNode(parents[0].clone());\r
2875 \r
2876                                         // Start cloning and moving children on the left side of the target node\r
2877                                         for (i = 0; i < parents.length - 1; i++) {\r
2878                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {\r
2879                                                         tempNode = self.filterNode(parents[i].clone());\r
2880                                                         currentNode.append(tempNode);\r
2881                                                 } else\r
2882                                                         tempNode = currentNode;\r
2883 \r
2884                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {\r
2885                                                         nextNode = childNode.next;\r
2886                                                         tempNode.append(childNode);\r
2887                                                         childNode = nextNode;\r
2888                                                 }\r
2889 \r
2890                                                 currentNode = tempNode;\r
2891                                         }\r
2892 \r
2893                                         if (!newParent.isEmpty(nonEmptyElements)) {\r
2894                                                 parent.insert(newParent, parents[0], true);\r
2895                                                 parent.insert(node, newParent);\r
2896                                         } else {\r
2897                                                 parent.insert(node, parents[0], true);\r
2898                                         }\r
2899 \r
2900                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>\r
2901                                         parent = parents[0];\r
2902                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {\r
2903                                                 parent.empty().remove();\r
2904                                         }\r
2905                                 } else if (node.parent) {\r
2906                                         // If it's an LI try to find a UL/OL for it or wrap it\r
2907                                         if (node.name === 'li') {\r
2908                                                 sibling = node.prev;\r
2909                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
2910                                                         sibling.append(node);\r
2911                                                         continue;\r
2912                                                 }\r
2913 \r
2914                                                 sibling = node.next;\r
2915                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
2916                                                         sibling.insert(node, sibling.firstChild, true);\r
2917                                                         continue;\r
2918                                                 }\r
2919 \r
2920                                                 node.wrap(self.filterNode(new Node('ul', 1)));\r
2921                                                 continue;\r
2922                                         }\r
2923 \r
2924                                         // Try wrapping the element in a DIV\r
2925                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {\r
2926                                                 node.wrap(self.filterNode(new Node('div', 1)));\r
2927                                         } else {\r
2928                                                 // We failed wrapping it, then remove or unwrap it\r
2929                                                 if (node.name === 'style' || node.name === 'script')\r
2930                                                         node.empty().remove();\r
2931                                                 else\r
2932                                                         node.unwrap();\r
2933                                         }\r
2934                                 }\r
2935                         }\r
2936                 };\r
2937 \r
2938                 self.filterNode = function(node) {\r
2939                         var i, name, list;\r
2940 \r
2941                         // Run element filters\r
2942                         if (name in nodeFilters) {\r
2943                                 list = matchedNodes[name];\r
2944 \r
2945                                 if (list)\r
2946                                         list.push(node);\r
2947                                 else\r
2948                                         matchedNodes[name] = [node];\r
2949                         }\r
2950 \r
2951                         // Run attribute filters\r
2952                         i = attributeFilters.length;\r
2953                         while (i--) {\r
2954                                 name = attributeFilters[i].name;\r
2955 \r
2956                                 if (name in node.attributes.map) {\r
2957                                         list = matchedAttributes[name];\r
2958 \r
2959                                         if (list)\r
2960                                                 list.push(node);\r
2961                                         else\r
2962                                                 matchedAttributes[name] = [node];\r
2963                                 }\r
2964                         }\r
2965 \r
2966                         return node;\r
2967                 };\r
2968 \r
2969                 self.addNodeFilter = function(name, callback) {\r
2970                         tinymce.each(tinymce.explode(name), function(name) {\r
2971                                 var list = nodeFilters[name];\r
2972 \r
2973                                 if (!list)\r
2974                                         nodeFilters[name] = list = [];\r
2975 \r
2976                                 list.push(callback);\r
2977                         });\r
2978                 };\r
2979 \r
2980                 self.addAttributeFilter = function(name, callback) {\r
2981                         tinymce.each(tinymce.explode(name), function(name) {\r
2982                                 var i;\r
2983 \r
2984                                 for (i = 0; i < attributeFilters.length; i++) {\r
2985                                         if (attributeFilters[i].name === name) {\r
2986                                                 attributeFilters[i].callbacks.push(callback);\r
2987                                                 return;\r
2988                                         }\r
2989                                 }\r
2990 \r
2991                                 attributeFilters.push({name: name, callbacks: [callback]});\r
2992                         });\r
2993                 };\r
2994 \r
2995                 self.parse = function(html, args) {\r
2996                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,\r
2997                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],\r
2998                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;\r
2999 \r
3000                         args = args || {};\r
3001                         matchedNodes = {};\r
3002                         matchedAttributes = {};\r
3003                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());\r
3004                         nonEmptyElements = schema.getNonEmptyElements();\r
3005                         children = schema.children;\r
3006                         validate = settings.validate;\r
3007                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;\r
3008 \r
3009                         whiteSpaceElements = schema.getWhiteSpaceElements();\r
3010                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;\r
3011                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;\r
3012                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;\r
3013 \r
3014                         function addRootBlocks() {\r
3015                                 var node = rootNode.firstChild, next, rootBlockNode;\r
3016 \r
3017                                 while (node) {\r
3018                                         next = node.next;\r
3019 \r
3020                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {\r
3021                                                 if (!rootBlockNode) {\r
3022                                                         // Create a new root block element\r
3023                                                         rootBlockNode = createNode(rootBlockName, 1);\r
3024                                                         rootNode.insert(rootBlockNode, node);\r
3025                                                         rootBlockNode.append(node);\r
3026                                                 } else\r
3027                                                         rootBlockNode.append(node);\r
3028                                         } else {\r
3029                                                 rootBlockNode = null;\r
3030                                         }\r
3031 \r
3032                                         node = next;\r
3033                                 };\r
3034                         };\r
3035 \r
3036                         function createNode(name, type) {\r
3037                                 var node = new Node(name, type), list;\r
3038 \r
3039                                 if (name in nodeFilters) {\r
3040                                         list = matchedNodes[name];\r
3041 \r
3042                                         if (list)\r
3043                                                 list.push(node);\r
3044                                         else\r
3045                                                 matchedNodes[name] = [node];\r
3046                                 }\r
3047 \r
3048                                 return node;\r
3049                         };\r
3050 \r
3051                         function removeWhitespaceBefore(node) {\r
3052                                 var textNode, textVal, sibling;\r
3053 \r
3054                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {\r
3055                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');\r
3056 \r
3057                                         if (textVal.length > 0) {\r
3058                                                 textNode.value = textVal;\r
3059                                                 textNode = textNode.prev;\r
3060                                         } else {\r
3061                                                 sibling = textNode.prev;\r
3062                                                 textNode.remove();\r
3063                                                 textNode = sibling;\r
3064                                         }\r
3065                                 }\r
3066                         };\r
3067 \r
3068                         parser = new tinymce.html.SaxParser({\r
3069                                 validate : validate,\r
3070                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results\r
3071 \r
3072                                 cdata: function(text) {\r
3073                                         node.append(createNode('#cdata', 4)).value = text;\r
3074                                 },\r
3075 \r
3076                                 text: function(text, raw) {\r
3077                                         var textNode;\r
3078 \r
3079                                         // Trim all redundant whitespace on non white space elements\r
3080                                         if (!whiteSpaceElements[node.name]) {\r
3081                                                 text = text.replace(allWhiteSpaceRegExp, ' ');\r
3082 \r
3083                                                 if (node.lastChild && blockElements[node.lastChild.name])\r
3084                                                         text = text.replace(startWhiteSpaceRegExp, '');\r
3085                                         }\r
3086 \r
3087                                         // Do we need to create the node\r
3088                                         if (text.length !== 0) {\r
3089                                                 textNode = createNode('#text', 3);\r
3090                                                 textNode.raw = !!raw;\r
3091                                                 node.append(textNode).value = text;\r
3092                                         }\r
3093                                 },\r
3094 \r
3095                                 comment: function(text) {\r
3096                                         node.append(createNode('#comment', 8)).value = text;\r
3097                                 },\r
3098 \r
3099                                 pi: function(name, text) {\r
3100                                         node.append(createNode(name, 7)).value = text;\r
3101                                         removeWhitespaceBefore(node);\r
3102                                 },\r
3103 \r
3104                                 doctype: function(text) {\r
3105                                         var newNode;\r
3106                 \r
3107                                         newNode = node.append(createNode('#doctype', 10));\r
3108                                         newNode.value = text;\r
3109                                         removeWhitespaceBefore(node);\r
3110                                 },\r
3111 \r
3112                                 start: function(name, attrs, empty) {\r
3113                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;\r
3114 \r
3115                                         elementRule = validate ? schema.getElementRule(name) : {};\r
3116                                         if (elementRule) {\r
3117                                                 newNode = createNode(elementRule.outputName || name, 1);\r
3118                                                 newNode.attributes = attrs;\r
3119                                                 newNode.shortEnded = empty;\r
3120 \r
3121                                                 node.append(newNode);\r
3122 \r
3123                                                 // Check if node is valid child of the parent node is the child is\r
3124                                                 // unknown we don't collect it since it's probably a custom element\r
3125                                                 parent = children[node.name];\r
3126                                                 if (parent && children[newNode.name] && !parent[newNode.name])\r
3127                                                         invalidChildren.push(newNode);\r
3128 \r
3129                                                 attrFiltersLen = attributeFilters.length;\r
3130                                                 while (attrFiltersLen--) {\r
3131                                                         attrName = attributeFilters[attrFiltersLen].name;\r
3132 \r
3133                                                         if (attrName in attrs.map) {\r
3134                                                                 list = matchedAttributes[attrName];\r
3135 \r
3136                                                                 if (list)\r
3137                                                                         list.push(newNode);\r
3138                                                                 else\r
3139                                                                         matchedAttributes[attrName] = [newNode];\r
3140                                                         }\r
3141                                                 }\r
3142 \r
3143                                                 // Trim whitespace before block\r
3144                                                 if (blockElements[name])\r
3145                                                         removeWhitespaceBefore(newNode);\r
3146 \r
3147                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />\r
3148                                                 if (!empty)\r
3149                                                         node = newNode;\r
3150                                         }\r
3151                                 },\r
3152 \r
3153                                 end: function(name) {\r
3154                                         var textNode, elementRule, text, sibling, tempNode;\r
3155 \r
3156                                         elementRule = validate ? schema.getElementRule(name) : {};\r
3157                                         if (elementRule) {\r
3158                                                 if (blockElements[name]) {\r
3159                                                         if (!whiteSpaceElements[node.name]) {\r
3160                                                                 // Trim whitespace at beginning of block\r
3161                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {\r
3162                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
3163 \r
3164                                                                         if (text.length > 0) {\r
3165                                                                                 textNode.value = text;\r
3166                                                                                 textNode = textNode.next;\r
3167                                                                         } else {\r
3168                                                                                 sibling = textNode.next;\r
3169                                                                                 textNode.remove();\r
3170                                                                                 textNode = sibling;\r
3171                                                                         }\r
3172                                                                 }\r
3173 \r
3174                                                                 // Trim whitespace at end of block\r
3175                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {\r
3176                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');\r
3177 \r
3178                                                                         if (text.length > 0) {\r
3179                                                                                 textNode.value = text;\r
3180                                                                                 textNode = textNode.prev;\r
3181                                                                         } else {\r
3182                                                                                 sibling = textNode.prev;\r
3183                                                                                 textNode.remove();\r
3184                                                                                 textNode = sibling;\r
3185                                                                         }\r
3186                                                                 }\r
3187                                                         }\r
3188 \r
3189                                                         // Trim start white space\r
3190                                                         textNode = node.prev;\r
3191                                                         if (textNode && textNode.type === 3) {\r
3192                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
3193 \r
3194                                                                 if (text.length > 0)\r
3195                                                                         textNode.value = text;\r
3196                                                                 else\r
3197                                                                         textNode.remove();\r
3198                                                         }\r
3199                                                 }\r
3200 \r
3201                                                 // Handle empty nodes\r
3202                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {\r
3203                                                         if (node.isEmpty(nonEmptyElements)) {\r
3204                                                                 if (elementRule.paddEmpty)\r
3205                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';\r
3206                                                                 else {\r
3207                                                                         // Leave nodes that have a name like <a name="name">\r
3208                                                                         if (!node.attributes.map.name) {\r
3209                                                                                 tempNode = node.parent;\r
3210                                                                                 node.empty().remove();\r
3211                                                                                 node = tempNode;\r
3212                                                                                 return;\r
3213                                                                         }\r
3214                                                                 }\r
3215                                                         }\r
3216                                                 }\r
3217 \r
3218                                                 node = node.parent;\r
3219                                         }\r
3220                                 }\r
3221                         }, schema);\r
3222 \r
3223                         rootNode = node = new Node(args.context || settings.root_name, 11);\r
3224 \r
3225                         parser.parse(html);\r
3226 \r
3227                         // Fix invalid children or report invalid children in a contextual parsing\r
3228                         if (validate && invalidChildren.length) {\r
3229                                 if (!args.context)\r
3230                                         fixInvalidChildren(invalidChildren);\r
3231                                 else\r
3232                                         args.invalid = true;\r
3233                         }\r
3234 \r
3235                         // Wrap nodes in the root into block elements if the root is body\r
3236                         if (rootBlockName && rootNode.name == 'body')\r
3237                                 addRootBlocks();\r
3238 \r
3239                         // Run filters only when the contents is valid\r
3240                         if (!args.invalid) {\r
3241                                 // Run node filters\r
3242                                 for (name in matchedNodes) {\r
3243                                         list = nodeFilters[name];\r
3244                                         nodes = matchedNodes[name];\r
3245 \r
3246                                         // Remove already removed children\r
3247                                         fi = nodes.length;\r
3248                                         while (fi--) {\r
3249                                                 if (!nodes[fi].parent)\r
3250                                                         nodes.splice(fi, 1);\r
3251                                         }\r
3252 \r
3253                                         for (i = 0, l = list.length; i < l; i++)\r
3254                                                 list[i](nodes, name, args);\r
3255                                 }\r
3256 \r
3257                                 // Run attribute filters\r
3258                                 for (i = 0, l = attributeFilters.length; i < l; i++) {\r
3259                                         list = attributeFilters[i];\r
3260 \r
3261                                         if (list.name in matchedAttributes) {\r
3262                                                 nodes = matchedAttributes[list.name];\r
3263 \r
3264                                                 // Remove already removed children\r
3265                                                 fi = nodes.length;\r
3266                                                 while (fi--) {\r
3267                                                         if (!nodes[fi].parent)\r
3268                                                                 nodes.splice(fi, 1);\r
3269                                                 }\r
3270 \r
3271                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)\r
3272                                                         list.callbacks[fi](nodes, list.name, args);\r
3273                                         }\r
3274                                 }\r
3275                         }\r
3276 \r
3277                         return rootNode;\r
3278                 };\r
3279 \r
3280                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to\r
3281                 // make it possible to place the caret inside empty blocks. This logic tries to remove\r
3282                 // these elements and keep br elements that where intended to be there intact\r
3283                 if (settings.remove_trailing_brs) {\r
3284                         self.addNodeFilter('br', function(nodes, name) {\r
3285                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),\r
3286                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;\r
3287 \r
3288                                 // Remove brs from body element as well\r
3289                                 blockElements.body = 1;\r
3290 \r
3291                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>\r
3292                                 for (i = 0; i < l; i++) {\r
3293                                         node = nodes[i];\r
3294                                         parent = node.parent;\r
3295 \r
3296                                         if (blockElements[node.parent.name] && node === parent.lastChild) {\r
3297                                                 // Loop all nodes to the right of the current node and check for other BR elements\r
3298                                                 // excluding bookmarks since they are invisible\r
3299                                                 prev = node.prev;\r
3300                                                 while (prev) {\r
3301                                                         prevName = prev.name;\r
3302 \r
3303                                                         // Ignore bookmarks\r
3304                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {\r
3305                                                                 // Found a non BR element\r
3306                                                                 if (prevName !== "br")\r
3307                                                                         break;\r
3308         \r
3309                                                                 // Found another br it's a <br><br> structure then don't remove anything\r
3310                                                                 if (prevName === 'br') {\r
3311                                                                         node = null;\r
3312                                                                         break;\r
3313                                                                 }\r
3314                                                         }\r
3315 \r
3316                                                         prev = prev.prev;\r
3317                                                 }\r
3318 \r
3319                                                 if (node) {\r
3320                                                         node.remove();\r
3321 \r
3322                                                         // Is the parent to be considered empty after we removed the BR\r
3323                                                         if (parent.isEmpty(nonEmptyElements)) {\r
3324                                                                 elementRule = schema.getElementRule(parent.name);\r
3325 \r
3326                                                                 // Remove or padd the element depending on schema rule\r
3327                                                                 if (elementRule) {\r
3328                                                                   if (elementRule.removeEmpty)\r
3329                                                                           parent.remove();\r
3330                                                                   else if (elementRule.paddEmpty)\r
3331                                                                           parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';\r
3332                                                           }\r
3333               }\r
3334                                                 }\r
3335                                         }\r
3336                                 }\r
3337                         });\r
3338                 }\r
3339         }\r
3340 })(tinymce);\r
3341 \r
3342 tinymce.html.Writer = function(settings) {\r
3343         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;\r
3344 \r
3345         settings = settings || {};\r
3346         indent = settings.indent;\r
3347         indentBefore = tinymce.makeMap(settings.indent_before || '');\r
3348         indentAfter = tinymce.makeMap(settings.indent_after || '');\r
3349         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);\r
3350         htmlOutput = settings.element_format == "html";\r
3351 \r
3352         return {\r
3353                 start: function(name, attrs, empty) {\r
3354                         var i, l, attr, value;\r
3355 \r
3356                         if (indent && indentBefore[name] && html.length > 0) {\r
3357                                 value = html[html.length - 1];\r
3358 \r
3359                                 if (value.length > 0 && value !== '\n')\r
3360                                         html.push('\n');\r
3361                         }\r
3362 \r
3363                         html.push('<', name);\r
3364 \r
3365                         if (attrs) {\r
3366                                 for (i = 0, l = attrs.length; i < l; i++) {\r
3367                                         attr = attrs[i];\r
3368                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');\r
3369                                 }\r
3370                         }\r
3371 \r
3372                         if (!empty || htmlOutput)\r
3373                                 html[html.length] = '>';\r
3374                         else\r
3375                                 html[html.length] = ' />';\r
3376 \r
3377                         if (empty && indent && indentAfter[name] && html.length > 0) {\r
3378                                 value = html[html.length - 1];\r
3379 \r
3380                                 if (value.length > 0 && value !== '\n')\r
3381                                         html.push('\n');\r
3382                         }\r
3383                 },\r
3384 \r
3385                 end: function(name) {\r
3386                         var value;\r
3387 \r
3388                         /*if (indent && indentBefore[name] && html.length > 0) {\r
3389                                 value = html[html.length - 1];\r
3390 \r
3391                                 if (value.length > 0 && value !== '\n')\r
3392                                         html.push('\n');\r
3393                         }*/\r
3394 \r
3395                         html.push('</', name, '>');\r
3396 \r
3397                         if (indent && indentAfter[name] && html.length > 0) {\r
3398                                 value = html[html.length - 1];\r
3399 \r
3400                                 if (value.length > 0 && value !== '\n')\r
3401                                         html.push('\n');\r
3402                         }\r
3403                 },\r
3404 \r
3405                 text: function(text, raw) {\r
3406                         if (text.length > 0)\r
3407                                 html[html.length] = raw ? text : encode(text);\r
3408                 },\r
3409 \r
3410                 cdata: function(text) {\r
3411                         html.push('<![CDATA[', text, ']]>');\r
3412                 },\r
3413 \r
3414                 comment: function(text) {\r
3415                         html.push('<!--', text, '-->');\r
3416                 },\r
3417 \r
3418                 pi: function(name, text) {\r
3419                         if (text)\r
3420                                 html.push('<?', name, ' ', text, '?>');\r
3421                         else\r
3422                                 html.push('<?', name, '?>');\r
3423 \r
3424                         if (indent)\r
3425                                 html.push('\n');\r
3426                 },\r
3427 \r
3428                 doctype: function(text) {\r
3429                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');\r
3430                 },\r
3431 \r
3432                 reset: function() {\r
3433                         html.length = 0;\r
3434                 },\r
3435 \r
3436                 getContent: function() {\r
3437                         return html.join('').replace(/\n$/, '');\r
3438                 }\r
3439         };\r
3440 };\r
3441 \r
3442 (function(tinymce) {\r
3443         tinymce.html.Serializer = function(settings, schema) {\r
3444                 var self = this, writer = new tinymce.html.Writer(settings);\r
3445 \r
3446                 settings = settings || {};\r
3447                 settings.validate = "validate" in settings ? settings.validate : true;\r
3448 \r
3449                 self.schema = schema = schema || new tinymce.html.Schema();\r
3450                 self.writer = writer;\r
3451 \r
3452                 self.serialize = function(node) {\r
3453                         var handlers, validate;\r
3454 \r
3455                         validate = settings.validate;\r
3456 \r
3457                         handlers = {\r
3458                                 // #text\r
3459                                 3: function(node, raw) {\r
3460                                         writer.text(node.value, node.raw);\r
3461                                 },\r
3462 \r
3463                                 // #comment\r
3464                                 8: function(node) {\r
3465                                         writer.comment(node.value);\r
3466                                 },\r
3467 \r
3468                                 // Processing instruction\r
3469                                 7: function(node) {\r
3470                                         writer.pi(node.name, node.value);\r
3471                                 },\r
3472 \r
3473                                 // Doctype\r
3474                                 10: function(node) {\r
3475                                         writer.doctype(node.value);\r
3476                                 },\r
3477 \r
3478                                 // CDATA\r
3479                                 4: function(node) {\r
3480                                         writer.cdata(node.value);\r
3481                                 },\r
3482 \r
3483                                 // Document fragment\r
3484                                 11: function(node) {\r
3485                                         if ((node = node.firstChild)) {\r
3486                                                 do {\r
3487                                                         walk(node);\r
3488                                                 } while (node = node.next);\r
3489                                         }\r
3490                                 }\r
3491                         };\r
3492 \r
3493                         writer.reset();\r
3494 \r
3495                         function walk(node) {\r
3496                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;\r
3497 \r
3498                                 if (!handler) {\r
3499                                         name = node.name;\r
3500                                         isEmpty = node.shortEnded;\r
3501                                         attrs = node.attributes;\r
3502 \r
3503                                         // Sort attributes\r
3504                                         if (validate && attrs && attrs.length > 1) {\r
3505                                                 sortedAttrs = [];\r
3506                                                 sortedAttrs.map = {};\r
3507 \r
3508                                                 elementRule = schema.getElementRule(node.name);\r
3509                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {\r
3510                                                         attrName = elementRule.attributesOrder[i];\r
3511 \r
3512                                                         if (attrName in attrs.map) {\r
3513                                                                 attrValue = attrs.map[attrName];\r
3514                                                                 sortedAttrs.map[attrName] = attrValue;\r
3515                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
3516                                                         }\r
3517                                                 }\r
3518 \r
3519                                                 for (i = 0, l = attrs.length; i < l; i++) {\r
3520                                                         attrName = attrs[i].name;\r
3521 \r
3522                                                         if (!(attrName in sortedAttrs.map)) {\r
3523                                                                 attrValue = attrs.map[attrName];\r
3524                                                                 sortedAttrs.map[attrName] = attrValue;\r
3525                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
3526                                                         }\r
3527                                                 }\r
3528 \r
3529                                                 attrs = sortedAttrs;\r
3530                                         }\r
3531 \r
3532                                         writer.start(node.name, attrs, isEmpty);\r
3533 \r
3534                                         if (!isEmpty) {\r
3535                                                 if ((node = node.firstChild)) {\r
3536                                                         do {\r
3537                                                                 walk(node);\r
3538                                                         } while (node = node.next);\r
3539                                                 }\r
3540 \r
3541                                                 writer.end(name);\r
3542                                         }\r
3543                                 } else\r
3544                                         handler(node);\r
3545                         }\r
3546 \r
3547                         // Serialize element and treat all non elements as fragments\r
3548                         if (node.type == 1 && !settings.inner)\r
3549                                 walk(node);\r
3550                         else\r
3551                                 handlers[11](node);\r
3552 \r
3553                         return writer.getContent();\r
3554                 };\r
3555         }\r
3556 })(tinymce);\r
3557 \r
3558 (function(tinymce) {\r
3559         // Shorten names\r
3560         var each = tinymce.each,\r
3561                 is = tinymce.is,\r
3562                 isWebKit = tinymce.isWebKit,\r
3563                 isIE = tinymce.isIE,\r
3564                 Entities = tinymce.html.Entities,\r
3565                 simpleSelectorRe = /^([a-z0-9],?)+$/i,\r
3566                 blockElementsMap = tinymce.html.Schema.blockElementsMap,\r
3567                 whiteSpaceRegExp = /^[ \t\r\n]*$/;\r
3568 \r
3569         tinymce.create('tinymce.dom.DOMUtils', {\r
3570                 doc : null,\r
3571                 root : null,\r
3572                 files : null,\r
3573                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,\r
3574                 props : {\r
3575                         "for" : "htmlFor",\r
3576                         "class" : "className",\r
3577                         className : "className",\r
3578                         checked : "checked",\r
3579                         disabled : "disabled",\r
3580                         maxlength : "maxLength",\r
3581                         readonly : "readOnly",\r
3582                         selected : "selected",\r
3583                         value : "value",\r
3584                         id : "id",\r
3585                         name : "name",\r
3586                         type : "type"\r
3587                 },\r
3588 \r
3589                 DOMUtils : function(d, s) {\r
3590                         var t = this, globalStyle, name;\r
3591 \r
3592                         t.doc = d;\r
3593                         t.win = window;\r
3594                         t.files = {};\r
3595                         t.cssFlicker = false;\r
3596                         t.counter = 0;\r
3597                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;\r
3598                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;\r
3599                         t.hasOuterHTML = "outerHTML" in d.createElement("a");\r
3600 \r
3601                         t.settings = s = tinymce.extend({\r
3602                                 keep_values : false,\r
3603                                 hex_colors : 1\r
3604                         }, s);\r
3605                         \r
3606                         t.schema = s.schema;\r
3607                         t.styles = new tinymce.html.Styles({\r
3608                                 url_converter : s.url_converter,\r
3609                                 url_converter_scope : s.url_converter_scope\r
3610                         }, s.schema);\r
3611 \r
3612                         // Fix IE6SP2 flicker and check it failed for pre SP2\r
3613                         if (tinymce.isIE6) {\r
3614                                 try {\r
3615                                         d.execCommand('BackgroundImageCache', false, true);\r
3616                                 } catch (e) {\r
3617                                         t.cssFlicker = true;\r
3618                                 }\r
3619                         }\r
3620 \r
3621                         if (isIE && s.schema) {\r
3622                                 // Add missing HTML 4/5 elements to IE\r
3623                                 ('abbr article aside audio canvas ' +\r
3624                                 'details figcaption figure footer ' +\r
3625                                 'header hgroup mark menu meter nav ' +\r
3626                                 'output progress section summary ' +\r
3627                                 'time video').replace(/\w+/g, function(name) {\r
3628                                         d.createElement(name);\r
3629                                 });\r
3630 \r
3631                                 // Create all custom elements\r
3632                                 for (name in s.schema.getCustomElements()) {\r
3633                                         d.createElement(name);\r
3634                                 }\r
3635                         }\r
3636 \r
3637                         tinymce.addUnload(t.destroy, t);\r
3638                 },\r
3639 \r
3640                 getRoot : function() {\r
3641                         var t = this, s = t.settings;\r
3642 \r
3643                         return (s && t.get(s.root_element)) || t.doc.body;\r
3644                 },\r
3645 \r
3646                 getViewPort : function(w) {\r
3647                         var d, b;\r
3648 \r
3649                         w = !w ? this.win : w;\r
3650                         d = w.document;\r
3651                         b = this.boxModel ? d.documentElement : d.body;\r
3652 \r
3653                         // Returns viewport size excluding scrollbars\r
3654                         return {\r
3655                                 x : w.pageXOffset || b.scrollLeft,\r
3656                                 y : w.pageYOffset || b.scrollTop,\r
3657                                 w : w.innerWidth || b.clientWidth,\r
3658                                 h : w.innerHeight || b.clientHeight\r
3659                         };\r
3660                 },\r
3661 \r
3662                 getRect : function(e) {\r
3663                         var p, t = this, sr;\r
3664 \r
3665                         e = t.get(e);\r
3666                         p = t.getPos(e);\r
3667                         sr = t.getSize(e);\r
3668 \r
3669                         return {\r
3670                                 x : p.x,\r
3671                                 y : p.y,\r
3672                                 w : sr.w,\r
3673                                 h : sr.h\r
3674                         };\r
3675                 },\r
3676 \r
3677                 getSize : function(e) {\r
3678                         var t = this, w, h;\r
3679 \r
3680                         e = t.get(e);\r
3681                         w = t.getStyle(e, 'width');\r
3682                         h = t.getStyle(e, 'height');\r
3683 \r
3684                         // Non pixel value, then force offset/clientWidth\r
3685                         if (w.indexOf('px') === -1)\r
3686                                 w = 0;\r
3687 \r
3688                         // Non pixel value, then force offset/clientWidth\r
3689                         if (h.indexOf('px') === -1)\r
3690                                 h = 0;\r
3691 \r
3692                         return {\r
3693                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,\r
3694                                 h : parseInt(h) || e.offsetHeight || e.clientHeight\r
3695                         };\r
3696                 },\r
3697 \r
3698                 getParent : function(n, f, r) {\r
3699                         return this.getParents(n, f, r, false);\r
3700                 },\r
3701 \r
3702                 getParents : function(n, f, r, c) {\r
3703                         var t = this, na, se = t.settings, o = [];\r
3704 \r
3705                         n = t.get(n);\r
3706                         c = c === undefined;\r
3707 \r
3708                         if (se.strict_root)\r
3709                                 r = r || t.getRoot();\r
3710 \r
3711                         // Wrap node name as func\r
3712                         if (is(f, 'string')) {\r
3713                                 na = f;\r
3714 \r
3715                                 if (f === '*') {\r
3716                                         f = function(n) {return n.nodeType == 1;};\r
3717                                 } else {\r
3718                                         f = function(n) {\r
3719                                                 return t.is(n, na);\r
3720                                         };\r
3721                                 }\r
3722                         }\r
3723 \r
3724                         while (n) {\r
3725                                 if (n == r || !n.nodeType || n.nodeType === 9)\r
3726                                         break;\r
3727 \r
3728                                 if (!f || f(n)) {\r
3729                                         if (c)\r
3730                                                 o.push(n);\r
3731                                         else\r
3732                                                 return n;\r
3733                                 }\r
3734 \r
3735                                 n = n.parentNode;\r
3736                         }\r
3737 \r
3738                         return c ? o : null;\r
3739                 },\r
3740 \r
3741                 get : function(e) {\r
3742                         var n;\r
3743 \r
3744                         if (e && this.doc && typeof(e) == 'string') {\r
3745                                 n = e;\r
3746                                 e = this.doc.getElementById(e);\r
3747 \r
3748                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick\r
3749                                 if (e && e.id !== n)\r
3750                                         return this.doc.getElementsByName(n)[1];\r
3751                         }\r
3752 \r
3753                         return e;\r
3754                 },\r
3755 \r
3756                 getNext : function(node, selector) {\r
3757                         return this._findSib(node, selector, 'nextSibling');\r
3758                 },\r
3759 \r
3760                 getPrev : function(node, selector) {\r
3761                         return this._findSib(node, selector, 'previousSibling');\r
3762                 },\r
3763 \r
3764 \r
3765                 select : function(pa, s) {\r
3766                         var t = this;\r
3767 \r
3768                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);\r
3769                 },\r
3770 \r
3771                 is : function(n, selector) {\r
3772                         var i;\r
3773 \r
3774                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance\r
3775                         if (n.length === undefined) {\r
3776                                 // Simple all selector\r
3777                                 if (selector === '*')\r
3778                                         return n.nodeType == 1;\r
3779 \r
3780                                 // Simple selector just elements\r
3781                                 if (simpleSelectorRe.test(selector)) {\r
3782                                         selector = selector.toLowerCase().split(/,/);\r
3783                                         n = n.nodeName.toLowerCase();\r
3784 \r
3785                                         for (i = selector.length - 1; i >= 0; i--) {\r
3786                                                 if (selector[i] == n)\r
3787                                                         return true;\r
3788                                         }\r
3789 \r
3790                                         return false;\r
3791                                 }\r
3792                         }\r
3793 \r
3794                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;\r
3795                 },\r
3796 \r
3797 \r
3798                 add : function(p, n, a, h, c) {\r
3799                         var t = this;\r
3800 \r
3801                         return this.run(p, function(p) {\r
3802                                 var e, k;\r
3803 \r
3804                                 e = is(n, 'string') ? t.doc.createElement(n) : n;\r
3805                                 t.setAttribs(e, a);\r
3806 \r
3807                                 if (h) {\r
3808                                         if (h.nodeType)\r
3809                                                 e.appendChild(h);\r
3810                                         else\r
3811                                                 t.setHTML(e, h);\r
3812                                 }\r
3813 \r
3814                                 return !c ? p.appendChild(e) : e;\r
3815                         });\r
3816                 },\r
3817 \r
3818                 create : function(n, a, h) {\r
3819                         return this.add(this.doc.createElement(n), n, a, h, 1);\r
3820                 },\r
3821 \r
3822                 createHTML : function(n, a, h) {\r
3823                         var o = '', t = this, k;\r
3824 \r
3825                         o += '<' + n;\r
3826 \r
3827                         for (k in a) {\r
3828                                 if (a.hasOwnProperty(k))\r
3829                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';\r
3830                         }\r
3831 \r
3832                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime\r
3833                         if (typeof(h) != "undefined")\r
3834                                 return o + '>' + h + '</' + n + '>';\r
3835 \r
3836                         return o + ' />';\r
3837                 },\r
3838 \r
3839                 remove : function(node, keep_children) {\r
3840                         return this.run(node, function(node) {\r
3841                                 var child, parent = node.parentNode;\r
3842 \r
3843                                 if (!parent)\r
3844                                         return null;\r
3845 \r
3846                                 if (keep_children) {\r
3847                                         while (child = node.firstChild) {\r
3848                                                 // IE 8 will crash if you don't remove completely empty text nodes\r
3849                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)\r
3850                                                         parent.insertBefore(child, node);\r
3851                                                 else\r
3852                                                         node.removeChild(child);\r
3853                                         }\r
3854                                 }\r
3855 \r
3856                                 return parent.removeChild(node);\r
3857                         });\r
3858                 },\r
3859 \r
3860                 setStyle : function(n, na, v) {\r
3861                         var t = this;\r
3862 \r
3863                         return t.run(n, function(e) {\r
3864                                 var s, i;\r
3865 \r
3866                                 s = e.style;\r
3867 \r
3868                                 // Camelcase it, if needed\r
3869                                 na = na.replace(/-(\D)/g, function(a, b){\r
3870                                         return b.toUpperCase();\r
3871                                 });\r
3872 \r
3873                                 // Default px suffix on these\r
3874                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))\r
3875                                         v += 'px';\r
3876 \r
3877                                 switch (na) {\r
3878                                         case 'opacity':\r
3879                                                 // IE specific opacity\r
3880                                                 if (isIE) {\r
3881                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";\r
3882 \r
3883                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)\r
3884                                                                 s.display = 'inline-block';\r
3885                                                 }\r
3886 \r
3887                                                 // Fix for older browsers\r
3888                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';\r
3889                                                 break;\r
3890 \r
3891                                         case 'float':\r
3892                                                 isIE ? s.styleFloat = v : s.cssFloat = v;\r
3893                                                 break;\r
3894                                         \r
3895                                         default:\r
3896                                                 s[na] = v || '';\r
3897                                 }\r
3898 \r
3899                                 // Force update of the style data\r
3900                                 if (t.settings.update_styles)\r
3901                                         t.setAttrib(e, 'data-mce-style');\r
3902                         });\r
3903                 },\r
3904 \r
3905                 getStyle : function(n, na, c) {\r
3906                         n = this.get(n);\r
3907 \r
3908                         if (!n)\r
3909                                 return;\r
3910 \r
3911                         // Gecko\r
3912                         if (this.doc.defaultView && c) {\r
3913                                 // Remove camelcase\r
3914                                 na = na.replace(/[A-Z]/g, function(a){\r
3915                                         return '-' + a;\r
3916                                 });\r
3917 \r
3918                                 try {\r
3919                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);\r
3920                                 } catch (ex) {\r
3921                                         // Old safari might fail\r
3922                                         return null;\r
3923                                 }\r
3924                         }\r
3925 \r
3926                         // Camelcase it, if needed\r
3927                         na = na.replace(/-(\D)/g, function(a, b){\r
3928                                 return b.toUpperCase();\r
3929                         });\r
3930 \r
3931                         if (na == 'float')\r
3932                                 na = isIE ? 'styleFloat' : 'cssFloat';\r
3933 \r
3934                         // IE & Opera\r
3935                         if (n.currentStyle && c)\r
3936                                 return n.currentStyle[na];\r
3937 \r
3938                         return n.style ? n.style[na] : undefined;\r
3939                 },\r
3940 \r
3941                 setStyles : function(e, o) {\r
3942                         var t = this, s = t.settings, ol;\r
3943 \r
3944                         ol = s.update_styles;\r
3945                         s.update_styles = 0;\r
3946 \r
3947                         each(o, function(v, n) {\r
3948                                 t.setStyle(e, n, v);\r
3949                         });\r
3950 \r
3951                         // Update style info\r
3952                         s.update_styles = ol;\r
3953                         if (s.update_styles)\r
3954                                 t.setAttrib(e, s.cssText);\r
3955                 },\r
3956 \r
3957                 removeAllAttribs: function(e) {\r
3958                         return this.run(e, function(e) {\r
3959                                 var i, attrs = e.attributes;\r
3960                                 for (i = attrs.length - 1; i >= 0; i--) {\r
3961                                         e.removeAttributeNode(attrs.item(i));\r
3962                                 }\r
3963                         });\r
3964                 },\r
3965 \r
3966                 setAttrib : function(e, n, v) {\r
3967                         var t = this;\r
3968 \r
3969                         // Whats the point\r
3970                         if (!e || !n)\r
3971                                 return;\r
3972 \r
3973                         // Strict XML mode\r
3974                         if (t.settings.strict)\r
3975                                 n = n.toLowerCase();\r
3976 \r
3977                         return this.run(e, function(e) {\r
3978                                 var s = t.settings;\r
3979                                 var originalValue = e.getAttribute(n);\r
3980                                 if (v !== null) {\r
3981                                         switch (n) {\r
3982                                                 case "style":\r
3983                                                         if (!is(v, 'string')) {\r
3984                                                                 each(v, function(v, n) {\r
3985                                                                         t.setStyle(e, n, v);\r
3986                                                                 });\r
3987 \r
3988                                                                 return;\r
3989                                                         }\r
3990 \r
3991                                                         // No mce_style for elements with these since they might get resized by the user\r
3992                                                         if (s.keep_values) {\r
3993                                                                 if (v && !t._isRes(v))\r
3994                                                                         e.setAttribute('data-mce-style', v, 2);\r
3995                                                                 else\r
3996                                                                         e.removeAttribute('data-mce-style', 2);\r
3997                                                         }\r
3998 \r
3999                                                         e.style.cssText = v;\r
4000                                                         break;\r
4001 \r
4002                                                 case "class":\r
4003                                                         e.className = v || ''; // Fix IE null bug\r
4004                                                         break;\r
4005 \r
4006                                                 case "src":\r
4007                                                 case "href":\r
4008                                                         if (s.keep_values) {\r
4009                                                                 if (s.url_converter)\r
4010                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, e);\r
4011 \r
4012                                                                 t.setAttrib(e, 'data-mce-' + n, v, 2);\r
4013                                                         }\r
4014 \r
4015                                                         break;\r
4016 \r
4017                                                 case "shape":\r
4018                                                         e.setAttribute('data-mce-style', v);\r
4019                                                         break;\r
4020                                         }\r
4021                                 }\r
4022                                 if (is(v) && v !== null && v.length !== 0)\r
4023                                         e.setAttribute(n, '' + v, 2);\r
4024                                 else\r
4025                                         e.removeAttribute(n, 2);\r
4026 \r
4027                                 // fire onChangeAttrib event for attributes that have changed\r
4028                                 if (tinyMCE.activeEditor && originalValue != v) {\r
4029                                         var ed = tinyMCE.activeEditor;\r
4030                                         ed.onSetAttrib.dispatch(ed, e, n, v);\r
4031                                 }\r
4032                         });\r
4033                 },\r
4034 \r
4035                 setAttribs : function(e, o) {\r
4036                         var t = this;\r
4037 \r
4038                         return this.run(e, function(e) {\r
4039                                 each(o, function(v, n) {\r
4040                                         t.setAttrib(e, n, v);\r
4041                                 });\r
4042                         });\r
4043                 },\r
4044 \r
4045                 getAttrib : function(e, n, dv) {\r
4046                         var v, t = this, undef;\r
4047 \r
4048                         e = t.get(e);\r
4049 \r
4050                         if (!e || e.nodeType !== 1)\r
4051                                 return dv === undef ? false : dv;\r
4052 \r
4053                         if (!is(dv))\r
4054                                 dv = '';\r
4055 \r
4056                         // Try the mce variant for these\r
4057                         if (/^(src|href|style|coords|shape)$/.test(n)) {\r
4058                                 v = e.getAttribute("data-mce-" + n);\r
4059 \r
4060                                 if (v)\r
4061                                         return v;\r
4062                         }\r
4063 \r
4064                         if (isIE && t.props[n]) {\r
4065                                 v = e[t.props[n]];\r
4066                                 v = v && v.nodeValue ? v.nodeValue : v;\r
4067                         }\r
4068 \r
4069                         if (!v)\r
4070                                 v = e.getAttribute(n, 2);\r
4071 \r
4072                         // Check boolean attribs\r
4073                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {\r
4074                                 if (e[t.props[n]] === true && v === '')\r
4075                                         return n;\r
4076 \r
4077                                 return v ? n : '';\r
4078                         }\r
4079 \r
4080                         // Inner input elements will override attributes on form elements\r
4081                         if (e.nodeName === "FORM" && e.getAttributeNode(n))\r
4082                                 return e.getAttributeNode(n).nodeValue;\r
4083 \r
4084                         if (n === 'style') {\r
4085                                 v = v || e.style.cssText;\r
4086 \r
4087                                 if (v) {\r
4088                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);\r
4089 \r
4090                                         if (t.settings.keep_values && !t._isRes(v))\r
4091                                                 e.setAttribute('data-mce-style', v);\r
4092                                 }\r
4093                         }\r
4094 \r
4095                         // Remove Apple and WebKit stuff\r
4096                         if (isWebKit && n === "class" && v)\r
4097                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');\r
4098 \r
4099                         // Handle IE issues\r
4100                         if (isIE) {\r
4101                                 switch (n) {\r
4102                                         case 'rowspan':\r
4103                                         case 'colspan':\r
4104                                                 // IE returns 1 as default value\r
4105                                                 if (v === 1)\r
4106                                                         v = '';\r
4107 \r
4108                                                 break;\r
4109 \r
4110                                         case 'size':\r
4111                                                 // IE returns +0 as default value for size\r
4112                                                 if (v === '+0' || v === 20 || v === 0)\r
4113                                                         v = '';\r
4114 \r
4115                                                 break;\r
4116 \r
4117                                         case 'width':\r
4118                                         case 'height':\r
4119                                         case 'vspace':\r
4120                                         case 'checked':\r
4121                                         case 'disabled':\r
4122                                         case 'readonly':\r
4123                                                 if (v === 0)\r
4124                                                         v = '';\r
4125 \r
4126                                                 break;\r
4127 \r
4128                                         case 'hspace':\r
4129                                                 // IE returns -1 as default value\r
4130                                                 if (v === -1)\r
4131                                                         v = '';\r
4132 \r
4133                                                 break;\r
4134 \r
4135                                         case 'maxlength':\r
4136                                         case 'tabindex':\r
4137                                                 // IE returns default value\r
4138                                                 if (v === 32768 || v === 2147483647 || v === '32768')\r
4139                                                         v = '';\r
4140 \r
4141                                                 break;\r
4142 \r
4143                                         case 'multiple':\r
4144                                         case 'compact':\r
4145                                         case 'noshade':\r
4146                                         case 'nowrap':\r
4147                                                 if (v === 65535)\r
4148                                                         return n;\r
4149 \r
4150                                                 return dv;\r
4151 \r
4152                                         case 'shape':\r
4153                                                 v = v.toLowerCase();\r
4154                                                 break;\r
4155 \r
4156                                         default:\r
4157                                                 // IE has odd anonymous function for event attributes\r
4158                                                 if (n.indexOf('on') === 0 && v)\r
4159                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);\r
4160                                 }\r
4161                         }\r
4162 \r
4163                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;\r
4164                 },\r
4165 \r
4166                 getPos : function(n, ro) {\r
4167                         var t = this, x = 0, y = 0, e, d = t.doc, r;\r
4168 \r
4169                         n = t.get(n);\r
4170                         ro = ro || d.body;\r
4171 \r
4172                         if (n) {\r
4173                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes\r
4174                                 if (n.getBoundingClientRect) {\r
4175                                         n = n.getBoundingClientRect();\r
4176                                         e = t.boxModel ? d.documentElement : d.body;\r
4177 \r
4178                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit\r
4179                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position\r
4180                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;\r
4181                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;\r
4182 \r
4183                                         return {x : x, y : y};\r
4184                                 }\r
4185 \r
4186                                 r = n;\r
4187                                 while (r && r != ro && r.nodeType) {\r
4188                                         x += r.offsetLeft || 0;\r
4189                                         y += r.offsetTop || 0;\r
4190                                         r = r.offsetParent;\r
4191                                 }\r
4192 \r
4193                                 r = n.parentNode;\r
4194                                 while (r && r != ro && r.nodeType) {\r
4195                                         x -= r.scrollLeft || 0;\r
4196                                         y -= r.scrollTop || 0;\r
4197                                         r = r.parentNode;\r
4198                                 }\r
4199                         }\r
4200 \r
4201                         return {x : x, y : y};\r
4202                 },\r
4203 \r
4204                 parseStyle : function(st) {\r
4205                         return this.styles.parse(st);\r
4206                 },\r
4207 \r
4208                 serializeStyle : function(o, name) {\r
4209                         return this.styles.serialize(o, name);\r
4210                 },\r
4211 \r
4212                 loadCSS : function(u) {\r
4213                         var t = this, d = t.doc, head;\r
4214 \r
4215                         if (!u)\r
4216                                 u = '';\r
4217 \r
4218                         head = t.select('head')[0];\r
4219 \r
4220                         each(u.split(','), function(u) {\r
4221                                 var link;\r
4222 \r
4223                                 if (t.files[u])\r
4224                                         return;\r
4225 \r
4226                                 t.files[u] = true;\r
4227                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});\r
4228 \r
4229                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug\r
4230                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading\r
4231                                 // It's ugly but it seems to work fine.\r
4232                                 if (isIE && d.documentMode && d.recalc) {\r
4233                                         link.onload = function() {\r
4234                                                 if (d.recalc)\r
4235                                                         d.recalc();\r
4236 \r
4237                                                 link.onload = null;\r
4238                                         };\r
4239                                 }\r
4240 \r
4241                                 head.appendChild(link);\r
4242                         });\r
4243                 },\r
4244 \r
4245                 addClass : function(e, c) {\r
4246                         return this.run(e, function(e) {\r
4247                                 var o;\r
4248 \r
4249                                 if (!c)\r
4250                                         return 0;\r
4251 \r
4252                                 if (this.hasClass(e, c))\r
4253                                         return e.className;\r
4254 \r
4255                                 o = this.removeClass(e, c);\r
4256 \r
4257                                 return e.className = (o != '' ? (o + ' ') : '') + c;\r
4258                         });\r
4259                 },\r
4260 \r
4261                 removeClass : function(e, c) {\r
4262                         var t = this, re;\r
4263 \r
4264                         return t.run(e, function(e) {\r
4265                                 var v;\r
4266 \r
4267                                 if (t.hasClass(e, c)) {\r
4268                                         if (!re)\r
4269                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");\r
4270 \r
4271                                         v = e.className.replace(re, ' ');\r
4272                                         v = tinymce.trim(v != ' ' ? v : '');\r
4273 \r
4274                                         e.className = v;\r
4275 \r
4276                                         // Empty class attr\r
4277                                         if (!v) {\r
4278                                                 e.removeAttribute('class');\r
4279                                                 e.removeAttribute('className');\r
4280                                         }\r
4281 \r
4282                                         return v;\r
4283                                 }\r
4284 \r
4285                                 return e.className;\r
4286                         });\r
4287                 },\r
4288 \r
4289                 hasClass : function(n, c) {\r
4290                         n = this.get(n);\r
4291 \r
4292                         if (!n || !c)\r
4293                                 return false;\r
4294 \r
4295                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;\r
4296                 },\r
4297 \r
4298                 show : function(e) {\r
4299                         return this.setStyle(e, 'display', 'block');\r
4300                 },\r
4301 \r
4302                 hide : function(e) {\r
4303                         return this.setStyle(e, 'display', 'none');\r
4304                 },\r
4305 \r
4306                 isHidden : function(e) {\r
4307                         e = this.get(e);\r
4308 \r
4309                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';\r
4310                 },\r
4311 \r
4312                 uniqueId : function(p) {\r
4313                         return (!p ? 'mce_' : p) + (this.counter++);\r
4314                 },\r
4315 \r
4316                 setHTML : function(element, html) {\r
4317                         var self = this;\r
4318 \r
4319                         return self.run(element, function(element) {\r
4320                                 if (isIE) {\r
4321                                         // Remove all child nodes, IE keeps empty text nodes in DOM\r
4322                                         while (element.firstChild)\r
4323                                                 element.removeChild(element.firstChild);\r
4324 \r
4325                                         try {\r
4326                                                 // IE will remove comments from the beginning\r
4327                                                 // unless you padd the contents with something\r
4328                                                 element.innerHTML = '<br />' + html;\r
4329                                                 element.removeChild(element.firstChild);\r
4330                                         } catch (ex) {\r
4331                                                 // 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\r
4332                                                 // This seems to fix this problem\r
4333 \r
4334                                                 // Create new div with HTML contents and a BR infront to keep comments\r
4335                                                 element = self.create('div');\r
4336                                                 element.innerHTML = '<br />' + html;\r
4337 \r
4338                                                 // Add all children from div to target\r
4339                                                 each (element.childNodes, function(node, i) {\r
4340                                                         // Skip br element\r
4341                                                         if (i)\r
4342                                                                 element.appendChild(node);\r
4343                                                 });\r
4344                                         }\r
4345                                 } else\r
4346                                         element.innerHTML = html;\r
4347 \r
4348                                 return html;\r
4349                         });\r
4350                 },\r
4351 \r
4352                 getOuterHTML : function(elm) {\r
4353                         var doc, self = this;\r
4354 \r
4355                         elm = self.get(elm);\r
4356 \r
4357                         if (!elm)\r
4358                                 return null;\r
4359 \r
4360                         if (elm.nodeType === 1 && self.hasOuterHTML)\r
4361                                 return elm.outerHTML;\r
4362 \r
4363                         doc = (elm.ownerDocument || self.doc).createElement("body");\r
4364                         doc.appendChild(elm.cloneNode(true));\r
4365 \r
4366                         return doc.innerHTML;\r
4367                 },\r
4368 \r
4369                 setOuterHTML : function(e, h, d) {\r
4370                         var t = this;\r
4371 \r
4372                         function setHTML(e, h, d) {\r
4373                                 var n, tp;\r
4374 \r
4375                                 tp = d.createElement("body");\r
4376                                 tp.innerHTML = h;\r
4377 \r
4378                                 n = tp.lastChild;\r
4379                                 while (n) {\r
4380                                         t.insertAfter(n.cloneNode(true), e);\r
4381                                         n = n.previousSibling;\r
4382                                 }\r
4383 \r
4384                                 t.remove(e);\r
4385                         };\r
4386 \r
4387                         return this.run(e, function(e) {\r
4388                                 e = t.get(e);\r
4389 \r
4390                                 // Only set HTML on elements\r
4391                                 if (e.nodeType == 1) {\r
4392                                         d = d || e.ownerDocument || t.doc;\r
4393 \r
4394                                         if (isIE) {\r
4395                                                 try {\r
4396                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error\r
4397                                                         if (isIE && e.nodeType == 1)\r
4398                                                                 e.outerHTML = h;\r
4399                                                         else\r
4400                                                                 setHTML(e, h, d);\r
4401                                                 } catch (ex) {\r
4402                                                         // Fix for unknown runtime error\r
4403                                                         setHTML(e, h, d);\r
4404                                                 }\r
4405                                         } else\r
4406                                                 setHTML(e, h, d);\r
4407                                 }\r
4408                         });\r
4409                 },\r
4410 \r
4411                 decode : Entities.decode,\r
4412 \r
4413                 encode : Entities.encodeAllRaw,\r
4414 \r
4415                 insertAfter : function(node, reference_node) {\r
4416                         reference_node = this.get(reference_node);\r
4417 \r
4418                         return this.run(node, function(node) {\r
4419                                 var parent, nextSibling;\r
4420 \r
4421                                 parent = reference_node.parentNode;\r
4422                                 nextSibling = reference_node.nextSibling;\r
4423 \r
4424                                 if (nextSibling)\r
4425                                         parent.insertBefore(node, nextSibling);\r
4426                                 else\r
4427                                         parent.appendChild(node);\r
4428 \r
4429                                 return node;\r
4430                         });\r
4431                 },\r
4432 \r
4433                 isBlock : function(node) {\r
4434                         var type = node.nodeType;\r
4435 \r
4436                         // If it's a node then check the type and use the nodeName\r
4437                         if (type)\r
4438                                 return !!(type === 1 && blockElementsMap[node.nodeName]);\r
4439 \r
4440                         return !!blockElementsMap[node];\r
4441                 },\r
4442 \r
4443                 replace : function(n, o, k) {\r
4444                         var t = this;\r
4445 \r
4446                         if (is(o, 'array'))\r
4447                                 n = n.cloneNode(true);\r
4448 \r
4449                         return t.run(o, function(o) {\r
4450                                 if (k) {\r
4451                                         each(tinymce.grep(o.childNodes), function(c) {\r
4452                                                 n.appendChild(c);\r
4453                                         });\r
4454                                 }\r
4455 \r
4456                                 return o.parentNode.replaceChild(n, o);\r
4457                         });\r
4458                 },\r
4459 \r
4460                 rename : function(elm, name) {\r
4461                         var t = this, newElm;\r
4462 \r
4463                         if (elm.nodeName != name.toUpperCase()) {\r
4464                                 // Rename block element\r
4465                                 newElm = t.create(name);\r
4466 \r
4467                                 // Copy attribs to new block\r
4468                                 each(t.getAttribs(elm), function(attr_node) {\r
4469                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));\r
4470                                 });\r
4471 \r
4472                                 // Replace block\r
4473                                 t.replace(newElm, elm, 1);\r
4474                         }\r
4475 \r
4476                         return newElm || elm;\r
4477                 },\r
4478 \r
4479                 findCommonAncestor : function(a, b) {\r
4480                         var ps = a, pe;\r
4481 \r
4482                         while (ps) {\r
4483                                 pe = b;\r
4484 \r
4485                                 while (pe && ps != pe)\r
4486                                         pe = pe.parentNode;\r
4487 \r
4488                                 if (ps == pe)\r
4489                                         break;\r
4490 \r
4491                                 ps = ps.parentNode;\r
4492                         }\r
4493 \r
4494                         if (!ps && a.ownerDocument)\r
4495                                 return a.ownerDocument.documentElement;\r
4496 \r
4497                         return ps;\r
4498                 },\r
4499 \r
4500                 toHex : function(s) {\r
4501                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);\r
4502 \r
4503                         function hex(s) {\r
4504                                 s = parseInt(s).toString(16);\r
4505 \r
4506                                 return s.length > 1 ? s : '0' + s; // 0 -> 00\r
4507                         };\r
4508 \r
4509                         if (c) {\r
4510                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);\r
4511 \r
4512                                 return s;\r
4513                         }\r
4514 \r
4515                         return s;\r
4516                 },\r
4517 \r
4518                 getClasses : function() {\r
4519                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;\r
4520 \r
4521                         if (t.classes)\r
4522                                 return t.classes;\r
4523 \r
4524                         function addClasses(s) {\r
4525                                 // IE style imports\r
4526                                 each(s.imports, function(r) {\r
4527                                         addClasses(r);\r
4528                                 });\r
4529 \r
4530                                 each(s.cssRules || s.rules, function(r) {\r
4531                                         // Real type or fake it on IE\r
4532                                         switch (r.type || 1) {\r
4533                                                 // Rule\r
4534                                                 case 1:\r
4535                                                         if (r.selectorText) {\r
4536                                                                 each(r.selectorText.split(','), function(v) {\r
4537                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");\r
4538 \r
4539                                                                         // Is internal or it doesn't contain a class\r
4540                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))\r
4541                                                                                 return;\r
4542 \r
4543                                                                         // Remove everything but class name\r
4544                                                                         ov = v;\r
4545                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);\r
4546 \r
4547                                                                         // Filter classes\r
4548                                                                         if (f && !(v = f(v, ov)))\r
4549                                                                                 return;\r
4550 \r
4551                                                                         if (!lo[v]) {\r
4552                                                                                 cl.push({'class' : v});\r
4553                                                                                 lo[v] = 1;\r
4554                                                                         }\r
4555                                                                 });\r
4556                                                         }\r
4557                                                         break;\r
4558 \r
4559                                                 // Import\r
4560                                                 case 3:\r
4561                                                         addClasses(r.styleSheet);\r
4562                                                         break;\r
4563                                         }\r
4564                                 });\r
4565                         };\r
4566 \r
4567                         try {\r
4568                                 each(t.doc.styleSheets, addClasses);\r
4569                         } catch (ex) {\r
4570                                 // Ignore\r
4571                         }\r
4572 \r
4573                         if (cl.length > 0)\r
4574                                 t.classes = cl;\r
4575 \r
4576                         return cl;\r
4577                 },\r
4578 \r
4579                 run : function(e, f, s) {\r
4580                         var t = this, o;\r
4581 \r
4582                         if (t.doc && typeof(e) === 'string')\r
4583                                 e = t.get(e);\r
4584 \r
4585                         if (!e)\r
4586                                 return false;\r
4587 \r
4588                         s = s || this;\r
4589                         if (!e.nodeType && (e.length || e.length === 0)) {\r
4590                                 o = [];\r
4591 \r
4592                                 each(e, function(e, i) {\r
4593                                         if (e) {\r
4594                                                 if (typeof(e) == 'string')\r
4595                                                         e = t.doc.getElementById(e);\r
4596 \r
4597                                                 o.push(f.call(s, e, i));\r
4598                                         }\r
4599                                 });\r
4600 \r
4601                                 return o;\r
4602                         }\r
4603 \r
4604                         return f.call(s, e);\r
4605                 },\r
4606 \r
4607                 getAttribs : function(n) {\r
4608                         var o;\r
4609 \r
4610                         n = this.get(n);\r
4611 \r
4612                         if (!n)\r
4613                                 return [];\r
4614 \r
4615                         if (isIE) {\r
4616                                 o = [];\r
4617 \r
4618                                 // Object will throw exception in IE\r
4619                                 if (n.nodeName == 'OBJECT')\r
4620                                         return n.attributes;\r
4621 \r
4622                                 // IE doesn't keep the selected attribute if you clone option elements\r
4623                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))\r
4624                                         o.push({specified : 1, nodeName : 'selected'});\r
4625 \r
4626                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time\r
4627                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {\r
4628                                         o.push({specified : 1, nodeName : a});\r
4629                                 });\r
4630 \r
4631                                 return o;\r
4632                         }\r
4633 \r
4634                         return n.attributes;\r
4635                 },\r
4636 \r
4637                 isEmpty : function(node, elements) {\r
4638                         var self = this, i, attributes, type, walker, name, parentNode;\r
4639 \r
4640                         node = node.firstChild;\r
4641                         if (node) {\r
4642                                 walker = new tinymce.dom.TreeWalker(node);\r
4643                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;\r
4644 \r
4645                                 do {\r
4646                                         type = node.nodeType;\r
4647 \r
4648                                         if (type === 1) {\r
4649                                                 // Ignore bogus elements\r
4650                                                 if (node.getAttribute('data-mce-bogus'))\r
4651                                                         continue;\r
4652 \r
4653                                                 // Keep empty elements like <img />\r
4654                                                 name = node.nodeName.toLowerCase();\r
4655                                                 if (elements && elements[name]) {\r
4656                                                         // Ignore single BR elements in blocks like <p><br /></p>\r
4657                                                         parentNode = node.parentNode;\r
4658                                                         if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {\r
4659                                                                 continue;\r
4660                                                         }\r
4661 \r
4662                                                         return false;\r
4663                                                 }\r
4664 \r
4665                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>\r
4666                                                 attributes = self.getAttribs(node);\r
4667                                                 i = node.attributes.length;\r
4668                                                 while (i--) {\r
4669                                                         name = node.attributes[i].nodeName;\r
4670                                                         if (name === "name" || name === 'data-mce-bookmark')\r
4671                                                                 return false;\r
4672                                                 }\r
4673                                         }\r
4674 \r
4675                                         // Keep comment nodes\r
4676                                         if (type == 8)\r
4677                                                 return false;\r
4678 \r
4679                                         // Keep non whitespace text nodes\r
4680                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))\r
4681                                                 return false;\r
4682                                 } while (node = walker.next());\r
4683                         }\r
4684 \r
4685                         return true;\r
4686                 },\r
4687 \r
4688                 destroy : function(s) {\r
4689                         var t = this;\r
4690 \r
4691                         if (t.events)\r
4692                                 t.events.destroy();\r
4693 \r
4694                         t.win = t.doc = t.root = t.events = null;\r
4695 \r
4696                         // Manual destroy then remove unload handler\r
4697                         if (!s)\r
4698                                 tinymce.removeUnload(t.destroy);\r
4699                 },\r
4700 \r
4701                 createRng : function() {\r
4702                         var d = this.doc;\r
4703 \r
4704                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);\r
4705                 },\r
4706 \r
4707                 nodeIndex : function(node, normalized) {\r
4708                         var idx = 0, lastNodeType, lastNode, nodeType;\r
4709 \r
4710                         if (node) {\r
4711                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {\r
4712                                         nodeType = node.nodeType;\r
4713 \r
4714                                         // Normalize text nodes\r
4715                                         if (normalized && nodeType == 3) {\r
4716                                                 if (nodeType == lastNodeType || !node.nodeValue.length)\r
4717                                                         continue;\r
4718                                         }\r
4719                                         idx++;\r
4720                                         lastNodeType = nodeType;\r
4721                                 }\r
4722                         }\r
4723 \r
4724                         return idx;\r
4725                 },\r
4726 \r
4727                 split : function(pe, e, re) {\r
4728                         var t = this, r = t.createRng(), bef, aft, pa;\r
4729 \r
4730                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense\r
4731                         // but we don't want that in our code since it serves no purpose for the end user\r
4732                         // For example if this is chopped:\r
4733                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>\r
4734                         // would produce:\r
4735                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>\r
4736                         // this function will then trim of empty edges and produce:\r
4737                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>\r
4738                         function trim(node) {\r
4739                                 var i, children = node.childNodes, type = node.nodeType;\r
4740 \r
4741                                 function surroundedBySpans(node) {\r
4742                                         var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';\r
4743                                         var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';\r
4744                                         return previousIsSpan && nextIsSpan;\r
4745                                 }\r
4746 \r
4747                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')\r
4748                                         return;\r
4749 \r
4750                                 for (i = children.length - 1; i >= 0; i--)\r
4751                                         trim(children[i]);\r
4752 \r
4753                                 if (type != 9) {\r
4754                                         // Keep non whitespace text nodes\r
4755                                         if (type == 3 && node.nodeValue.length > 0) {\r
4756                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"\r
4757                                                 // Also keep text nodes with only spaces if surrounded by spans.\r
4758                                                 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b\r
4759                                                 var trimmedLength = tinymce.trim(node.nodeValue).length;\r
4760                                                 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength == 0 && surroundedBySpans(node))\r
4761                                                         return;\r
4762                                         } else if (type == 1) {\r
4763                                                 // If the only child is a bookmark then move it up\r
4764                                                 children = node.childNodes;\r
4765                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')\r
4766                                                         node.parentNode.insertBefore(children[0], node);\r
4767 \r
4768                                                 // Keep non empty elements or img, hr etc\r
4769                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))\r
4770                                                         return;\r
4771                                         }\r
4772 \r
4773                                         t.remove(node);\r
4774                                 }\r
4775 \r
4776                                 return node;\r
4777                         };\r
4778 \r
4779                         if (pe && e) {\r
4780                                 // Get before chunk\r
4781                                 r.setStart(pe.parentNode, t.nodeIndex(pe));\r
4782                                 r.setEnd(e.parentNode, t.nodeIndex(e));\r
4783                                 bef = r.extractContents();\r
4784 \r
4785                                 // Get after chunk\r
4786                                 r = t.createRng();\r
4787                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);\r
4788                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);\r
4789                                 aft = r.extractContents();\r
4790 \r
4791                                 // Insert before chunk\r
4792                                 pa = pe.parentNode;\r
4793                                 pa.insertBefore(trim(bef), pe);\r
4794 \r
4795                                 // Insert middle chunk\r
4796                                 if (re)\r
4797                                 pa.replaceChild(re, e);\r
4798                         else\r
4799                                 pa.insertBefore(e, pe);\r
4800 \r
4801                                 // Insert after chunk\r
4802                                 pa.insertBefore(trim(aft), pe);\r
4803                                 t.remove(pe);\r
4804 \r
4805                                 return re || e;\r
4806                         }\r
4807                 },\r
4808 \r
4809                 bind : function(target, name, func, scope) {\r
4810                         var t = this;\r
4811 \r
4812                         if (!t.events)\r
4813                                 t.events = new tinymce.dom.EventUtils();\r
4814 \r
4815                         return t.events.add(target, name, func, scope || this);\r
4816                 },\r
4817 \r
4818                 unbind : function(target, name, func) {\r
4819                         var t = this;\r
4820 \r
4821                         if (!t.events)\r
4822                                 t.events = new tinymce.dom.EventUtils();\r
4823 \r
4824                         return t.events.remove(target, name, func);\r
4825                 },\r
4826 \r
4827 \r
4828                 _findSib : function(node, selector, name) {\r
4829                         var t = this, f = selector;\r
4830 \r
4831                         if (node) {\r
4832                                 // If expression make a function of it using is\r
4833                                 if (is(f, 'string')) {\r
4834                                         f = function(node) {\r
4835                                                 return t.is(node, selector);\r
4836                                         };\r
4837                                 }\r
4838 \r
4839                                 // Loop all siblings\r
4840                                 for (node = node[name]; node; node = node[name]) {\r
4841                                         if (f(node))\r
4842                                                 return node;\r
4843                                 }\r
4844                         }\r
4845 \r
4846                         return null;\r
4847                 },\r
4848 \r
4849                 _isRes : function(c) {\r
4850                         // Is live resizble element\r
4851                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);\r
4852                 }\r
4853 \r
4854                 /*\r
4855                 walk : function(n, f, s) {\r
4856                         var d = this.doc, w;\r
4857 \r
4858                         if (d.createTreeWalker) {\r
4859                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);\r
4860 \r
4861                                 while ((n = w.nextNode()) != null)\r
4862                                         f.call(s || this, n);\r
4863                         } else\r
4864                                 tinymce.walk(n, f, 'childNodes', s);\r
4865                 }\r
4866                 */\r
4867 \r
4868                 /*\r
4869                 toRGB : function(s) {\r
4870                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);\r
4871 \r
4872                         if (c) {\r
4873                                 // #FFF -> #FFFFFF\r
4874                                 if (!is(c[3]))\r
4875                                         c[3] = c[2] = c[1];\r
4876 \r
4877                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";\r
4878                         }\r
4879 \r
4880                         return s;\r
4881                 }\r
4882                 */\r
4883         });\r
4884 \r
4885         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});\r
4886 })(tinymce);\r
4887 \r
4888 (function(ns) {\r
4889         // Range constructor\r
4890         function Range(dom) {\r
4891                 var t = this,\r
4892                         doc = dom.doc,\r
4893                         EXTRACT = 0,\r
4894                         CLONE = 1,\r
4895                         DELETE = 2,\r
4896                         TRUE = true,\r
4897                         FALSE = false,\r
4898                         START_OFFSET = 'startOffset',\r
4899                         START_CONTAINER = 'startContainer',\r
4900                         END_CONTAINER = 'endContainer',\r
4901                         END_OFFSET = 'endOffset',\r
4902                         extend = tinymce.extend,\r
4903                         nodeIndex = dom.nodeIndex;\r
4904 \r
4905                 extend(t, {\r
4906                         // Inital states\r
4907                         startContainer : doc,\r
4908                         startOffset : 0,\r
4909                         endContainer : doc,\r
4910                         endOffset : 0,\r
4911                         collapsed : TRUE,\r
4912                         commonAncestorContainer : doc,\r
4913 \r
4914                         // Range constants\r
4915                         START_TO_START : 0,\r
4916                         START_TO_END : 1,\r
4917                         END_TO_END : 2,\r
4918                         END_TO_START : 3,\r
4919 \r
4920                         // Public methods\r
4921                         setStart : setStart,\r
4922                         setEnd : setEnd,\r
4923                         setStartBefore : setStartBefore,\r
4924                         setStartAfter : setStartAfter,\r
4925                         setEndBefore : setEndBefore,\r
4926                         setEndAfter : setEndAfter,\r
4927                         collapse : collapse,\r
4928                         selectNode : selectNode,\r
4929                         selectNodeContents : selectNodeContents,\r
4930                         compareBoundaryPoints : compareBoundaryPoints,\r
4931                         deleteContents : deleteContents,\r
4932                         extractContents : extractContents,\r
4933                         cloneContents : cloneContents,\r
4934                         insertNode : insertNode,\r
4935                         surroundContents : surroundContents,\r
4936                         cloneRange : cloneRange\r
4937                 });\r
4938 \r
4939                 function setStart(n, o) {\r
4940                         _setEndPoint(TRUE, n, o);\r
4941                 };\r
4942 \r
4943                 function setEnd(n, o) {\r
4944                         _setEndPoint(FALSE, n, o);\r
4945                 };\r
4946 \r
4947                 function setStartBefore(n) {\r
4948                         setStart(n.parentNode, nodeIndex(n));\r
4949                 };\r
4950 \r
4951                 function setStartAfter(n) {\r
4952                         setStart(n.parentNode, nodeIndex(n) + 1);\r
4953                 };\r
4954 \r
4955                 function setEndBefore(n) {\r
4956                         setEnd(n.parentNode, nodeIndex(n));\r
4957                 };\r
4958 \r
4959                 function setEndAfter(n) {\r
4960                         setEnd(n.parentNode, nodeIndex(n) + 1);\r
4961                 };\r
4962 \r
4963                 function collapse(ts) {\r
4964                         if (ts) {\r
4965                                 t[END_CONTAINER] = t[START_CONTAINER];\r
4966                                 t[END_OFFSET] = t[START_OFFSET];\r
4967                         } else {\r
4968                                 t[START_CONTAINER] = t[END_CONTAINER];\r
4969                                 t[START_OFFSET] = t[END_OFFSET];\r
4970                         }\r
4971 \r
4972                         t.collapsed = TRUE;\r
4973                 };\r
4974 \r
4975                 function selectNode(n) {\r
4976                         setStartBefore(n);\r
4977                         setEndAfter(n);\r
4978                 };\r
4979 \r
4980                 function selectNodeContents(n) {\r
4981                         setStart(n, 0);\r
4982                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);\r
4983                 };\r
4984 \r
4985                 function compareBoundaryPoints(h, r) {\r
4986                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],\r
4987                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;\r
4988 \r
4989                         // Check START_TO_START\r
4990                         if (h === 0)\r
4991                                 return _compareBoundaryPoints(sc, so, rsc, rso);\r
4992         \r
4993                         // Check START_TO_END\r
4994                         if (h === 1)\r
4995                                 return _compareBoundaryPoints(ec, eo, rsc, rso);\r
4996         \r
4997                         // Check END_TO_END\r
4998                         if (h === 2)\r
4999                                 return _compareBoundaryPoints(ec, eo, rec, reo);\r
5000         \r
5001                         // Check END_TO_START\r
5002                         if (h === 3) \r
5003                                 return _compareBoundaryPoints(sc, so, rec, reo);\r
5004                 };\r
5005 \r
5006                 function deleteContents() {\r
5007                         _traverse(DELETE);\r
5008                 };\r
5009 \r
5010                 function extractContents() {\r
5011                         return _traverse(EXTRACT);\r
5012                 };\r
5013 \r
5014                 function cloneContents() {\r
5015                         return _traverse(CLONE);\r
5016                 };\r
5017 \r
5018                 function insertNode(n) {\r
5019                         var startContainer = this[START_CONTAINER],\r
5020                                 startOffset = this[START_OFFSET], nn, o;\r
5021 \r
5022                         // Node is TEXT_NODE or CDATA\r
5023                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {\r
5024                                 if (!startOffset) {\r
5025                                         // At the start of text\r
5026                                         startContainer.parentNode.insertBefore(n, startContainer);\r
5027                                 } else if (startOffset >= startContainer.nodeValue.length) {\r
5028                                         // At the end of text\r
5029                                         dom.insertAfter(n, startContainer);\r
5030                                 } else {\r
5031                                         // Middle, need to split\r
5032                                         nn = startContainer.splitText(startOffset);\r
5033                                         startContainer.parentNode.insertBefore(n, nn);\r
5034                                 }\r
5035                         } else {\r
5036                                 // Insert element node\r
5037                                 if (startContainer.childNodes.length > 0)\r
5038                                         o = startContainer.childNodes[startOffset];\r
5039 \r
5040                                 if (o)\r
5041                                         startContainer.insertBefore(n, o);\r
5042                                 else\r
5043                                         startContainer.appendChild(n);\r
5044                         }\r
5045                 };\r
5046 \r
5047                 function surroundContents(n) {\r
5048                         var f = t.extractContents();\r
5049 \r
5050                         t.insertNode(n);\r
5051                         n.appendChild(f);\r
5052                         t.selectNode(n);\r
5053                 };\r
5054 \r
5055                 function cloneRange() {\r
5056                         return extend(new Range(dom), {\r
5057                                 startContainer : t[START_CONTAINER],\r
5058                                 startOffset : t[START_OFFSET],\r
5059                                 endContainer : t[END_CONTAINER],\r
5060                                 endOffset : t[END_OFFSET],\r
5061                                 collapsed : t.collapsed,\r
5062                                 commonAncestorContainer : t.commonAncestorContainer\r
5063                         });\r
5064                 };\r
5065 \r
5066                 // Private methods\r
5067 \r
5068                 function _getSelectedNode(container, offset) {\r
5069                         var child;\r
5070 \r
5071                         if (container.nodeType == 3 /* TEXT_NODE */)\r
5072                                 return container;\r
5073 \r
5074                         if (offset < 0)\r
5075                                 return container;\r
5076 \r
5077                         child = container.firstChild;\r
5078                         while (child && offset > 0) {\r
5079                                 --offset;\r
5080                                 child = child.nextSibling;\r
5081                         }\r
5082 \r
5083                         if (child)\r
5084                                 return child;\r
5085 \r
5086                         return container;\r
5087                 };\r
5088 \r
5089                 function _isCollapsed() {\r
5090                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);\r
5091                 };\r
5092 \r
5093                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {\r
5094                         var c, offsetC, n, cmnRoot, childA, childB;\r
5095                         \r
5096                         // In the first case the boundary-points have the same container. A is before B\r
5097                         // if its offset is less than the offset of B, A is equal to B if its offset is\r
5098                         // equal to the offset of B, and A is after B if its offset is greater than the\r
5099                         // offset of B.\r
5100                         if (containerA == containerB) {\r
5101                                 if (offsetA == offsetB)\r
5102                                         return 0; // equal\r
5103 \r
5104                                 if (offsetA < offsetB)\r
5105                                         return -1; // before\r
5106 \r
5107                                 return 1; // after\r
5108                         }\r
5109 \r
5110                         // In the second case a child node C of the container of A is an ancestor\r
5111                         // container of B. In this case, A is before B if the offset of A is less than or\r
5112                         // equal to the index of the child node C and A is after B otherwise.\r
5113                         c = containerB;\r
5114                         while (c && c.parentNode != containerA)\r
5115                                 c = c.parentNode;\r
5116 \r
5117                         if (c) {\r
5118                                 offsetC = 0;\r
5119                                 n = containerA.firstChild;\r
5120 \r
5121                                 while (n != c && offsetC < offsetA) {\r
5122                                         offsetC++;\r
5123                                         n = n.nextSibling;\r
5124                                 }\r
5125 \r
5126                                 if (offsetA <= offsetC)\r
5127                                         return -1; // before\r
5128 \r
5129                                 return 1; // after\r
5130                         }\r
5131 \r
5132                         // In the third case a child node C of the container of B is an ancestor container\r
5133                         // of A. In this case, A is before B if the index of the child node C is less than\r
5134                         // the offset of B and A is after B otherwise.\r
5135                         c = containerA;\r
5136                         while (c && c.parentNode != containerB) {\r
5137                                 c = c.parentNode;\r
5138                         }\r
5139 \r
5140                         if (c) {\r
5141                                 offsetC = 0;\r
5142                                 n = containerB.firstChild;\r
5143 \r
5144                                 while (n != c && offsetC < offsetB) {\r
5145                                         offsetC++;\r
5146                                         n = n.nextSibling;\r
5147                                 }\r
5148 \r
5149                                 if (offsetC < offsetB)\r
5150                                         return -1; // before\r
5151 \r
5152                                 return 1; // after\r
5153                         }\r
5154 \r
5155                         // In the fourth case, none of three other cases hold: the containers of A and B\r
5156                         // are siblings or descendants of sibling nodes. In this case, A is before B if\r
5157                         // the container of A is before the container of B in a pre-order traversal of the\r
5158                         // Ranges' context tree and A is after B otherwise.\r
5159                         cmnRoot = dom.findCommonAncestor(containerA, containerB);\r
5160                         childA = containerA;\r
5161 \r
5162                         while (childA && childA.parentNode != cmnRoot)\r
5163                                 childA = childA.parentNode;\r
5164 \r
5165                         if (!childA)\r
5166                                 childA = cmnRoot;\r
5167 \r
5168                         childB = containerB;\r
5169                         while (childB && childB.parentNode != cmnRoot)\r
5170                                 childB = childB.parentNode;\r
5171 \r
5172                         if (!childB)\r
5173                                 childB = cmnRoot;\r
5174 \r
5175                         if (childA == childB)\r
5176                                 return 0; // equal\r
5177 \r
5178                         n = cmnRoot.firstChild;\r
5179                         while (n) {\r
5180                                 if (n == childA)\r
5181                                         return -1; // before\r
5182 \r
5183                                 if (n == childB)\r
5184                                         return 1; // after\r
5185 \r
5186                                 n = n.nextSibling;\r
5187                         }\r
5188                 };\r
5189 \r
5190                 function _setEndPoint(st, n, o) {\r
5191                         var ec, sc;\r
5192 \r
5193                         if (st) {\r
5194                                 t[START_CONTAINER] = n;\r
5195                                 t[START_OFFSET] = o;\r
5196                         } else {\r
5197                                 t[END_CONTAINER] = n;\r
5198                                 t[END_OFFSET] = o;\r
5199                         }\r
5200 \r
5201                         // If one boundary-point of a Range is set to have a root container\r
5202                         // other than the current one for the Range, the Range is collapsed to\r
5203                         // the new position. This enforces the restriction that both boundary-\r
5204                         // points of a Range must have the same root container.\r
5205                         ec = t[END_CONTAINER];\r
5206                         while (ec.parentNode)\r
5207                                 ec = ec.parentNode;\r
5208 \r
5209                         sc = t[START_CONTAINER];\r
5210                         while (sc.parentNode)\r
5211                                 sc = sc.parentNode;\r
5212 \r
5213                         if (sc == ec) {\r
5214                                 // The start position of a Range is guaranteed to never be after the\r
5215                                 // end position. To enforce this restriction, if the start is set to\r
5216                                 // be at a position after the end, the Range is collapsed to that\r
5217                                 // position.\r
5218                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)\r
5219                                         t.collapse(st);\r
5220                         } else\r
5221                                 t.collapse(st);\r
5222 \r
5223                         t.collapsed = _isCollapsed();\r
5224                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);\r
5225                 };\r
5226 \r
5227                 function _traverse(how) {\r
5228                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;\r
5229 \r
5230                         if (t[START_CONTAINER] == t[END_CONTAINER])\r
5231                                 return _traverseSameContainer(how);\r
5232 \r
5233                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
5234                                 if (p == t[START_CONTAINER])\r
5235                                         return _traverseCommonStartContainer(c, how);\r
5236 \r
5237                                 ++endContainerDepth;\r
5238                         }\r
5239 \r
5240                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
5241                                 if (p == t[END_CONTAINER])\r
5242                                         return _traverseCommonEndContainer(c, how);\r
5243 \r
5244                                 ++startContainerDepth;\r
5245                         }\r
5246 \r
5247                         depthDiff = startContainerDepth - endContainerDepth;\r
5248 \r
5249                         startNode = t[START_CONTAINER];\r
5250                         while (depthDiff > 0) {\r
5251                                 startNode = startNode.parentNode;\r
5252                                 depthDiff--;\r
5253                         }\r
5254 \r
5255                         endNode = t[END_CONTAINER];\r
5256                         while (depthDiff < 0) {\r
5257                                 endNode = endNode.parentNode;\r
5258                                 depthDiff++;\r
5259                         }\r
5260 \r
5261                         // ascend the ancestor hierarchy until we have a common parent.\r
5262                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {\r
5263                                 startNode = sp;\r
5264                                 endNode = ep;\r
5265                         }\r
5266 \r
5267                         return _traverseCommonAncestors(startNode, endNode, how);\r
5268                 };\r
5269 \r
5270                  function _traverseSameContainer(how) {\r
5271                         var frag, s, sub, n, cnt, sibling, xferNode;\r
5272 \r
5273                         if (how != DELETE)\r
5274                                 frag = doc.createDocumentFragment();\r
5275 \r
5276                         // If selection is empty, just return the fragment\r
5277                         if (t[START_OFFSET] == t[END_OFFSET])\r
5278                                 return frag;\r
5279 \r
5280                         // Text node needs special case handling\r
5281                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {\r
5282                                 // get the substring\r
5283                                 s = t[START_CONTAINER].nodeValue;\r
5284                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);\r
5285 \r
5286                                 // set the original text node to its new value\r
5287                                 if (how != CLONE) {\r
5288                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);\r
5289 \r
5290                                         // Nothing is partially selected, so collapse to start point\r
5291                                         t.collapse(TRUE);\r
5292                                 }\r
5293 \r
5294                                 if (how == DELETE)\r
5295                                         return;\r
5296 \r
5297                                 frag.appendChild(doc.createTextNode(sub));\r
5298                                 return frag;\r
5299                         }\r
5300 \r
5301                         // Copy nodes between the start/end offsets.\r
5302                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);\r
5303                         cnt = t[END_OFFSET] - t[START_OFFSET];\r
5304 \r
5305                         while (cnt > 0) {\r
5306                                 sibling = n.nextSibling;\r
5307                                 xferNode = _traverseFullySelected(n, how);\r
5308 \r
5309                                 if (frag)\r
5310                                         frag.appendChild( xferNode );\r
5311 \r
5312                                 --cnt;\r
5313                                 n = sibling;\r
5314                         }\r
5315 \r
5316                         // Nothing is partially selected, so collapse to start point\r
5317                         if (how != CLONE)\r
5318                                 t.collapse(TRUE);\r
5319 \r
5320                         return frag;\r
5321                 };\r
5322 \r
5323                 function _traverseCommonStartContainer(endAncestor, how) {\r
5324                         var frag, n, endIdx, cnt, sibling, xferNode;\r
5325 \r
5326                         if (how != DELETE)\r
5327                                 frag = doc.createDocumentFragment();\r
5328 \r
5329                         n = _traverseRightBoundary(endAncestor, how);\r
5330 \r
5331                         if (frag)\r
5332                                 frag.appendChild(n);\r
5333 \r
5334                         endIdx = nodeIndex(endAncestor);\r
5335                         cnt = endIdx - t[START_OFFSET];\r
5336 \r
5337                         if (cnt <= 0) {\r
5338                                 // Collapse to just before the endAncestor, which\r
5339                                 // is partially selected.\r
5340                                 if (how != CLONE) {\r
5341                                         t.setEndBefore(endAncestor);\r
5342                                         t.collapse(FALSE);\r
5343                                 }\r
5344 \r
5345                                 return frag;\r
5346                         }\r
5347 \r
5348                         n = endAncestor.previousSibling;\r
5349                         while (cnt > 0) {\r
5350                                 sibling = n.previousSibling;\r
5351                                 xferNode = _traverseFullySelected(n, how);\r
5352 \r
5353                                 if (frag)\r
5354                                         frag.insertBefore(xferNode, frag.firstChild);\r
5355 \r
5356                                 --cnt;\r
5357                                 n = sibling;\r
5358                         }\r
5359 \r
5360                         // Collapse to just before the endAncestor, which\r
5361                         // is partially selected.\r
5362                         if (how != CLONE) {\r
5363                                 t.setEndBefore(endAncestor);\r
5364                                 t.collapse(FALSE);\r
5365                         }\r
5366 \r
5367                         return frag;\r
5368                 };\r
5369 \r
5370                 function _traverseCommonEndContainer(startAncestor, how) {\r
5371                         var frag, startIdx, n, cnt, sibling, xferNode;\r
5372 \r
5373                         if (how != DELETE)\r
5374                                 frag = doc.createDocumentFragment();\r
5375 \r
5376                         n = _traverseLeftBoundary(startAncestor, how);\r
5377                         if (frag)\r
5378                                 frag.appendChild(n);\r
5379 \r
5380                         startIdx = nodeIndex(startAncestor);\r
5381                         ++startIdx; // Because we already traversed it\r
5382 \r
5383                         cnt = t[END_OFFSET] - startIdx;\r
5384                         n = startAncestor.nextSibling;\r
5385                         while (cnt > 0) {\r
5386                                 sibling = n.nextSibling;\r
5387                                 xferNode = _traverseFullySelected(n, how);\r
5388 \r
5389                                 if (frag)\r
5390                                         frag.appendChild(xferNode);\r
5391 \r
5392                                 --cnt;\r
5393                                 n = sibling;\r
5394                         }\r
5395 \r
5396                         if (how != CLONE) {\r
5397                                 t.setStartAfter(startAncestor);\r
5398                                 t.collapse(TRUE);\r
5399                         }\r
5400 \r
5401                         return frag;\r
5402                 };\r
5403 \r
5404                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {\r
5405                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;\r
5406 \r
5407                         if (how != DELETE)\r
5408                                 frag = doc.createDocumentFragment();\r
5409 \r
5410                         n = _traverseLeftBoundary(startAncestor, how);\r
5411                         if (frag)\r
5412                                 frag.appendChild(n);\r
5413 \r
5414                         commonParent = startAncestor.parentNode;\r
5415                         startOffset = nodeIndex(startAncestor);\r
5416                         endOffset = nodeIndex(endAncestor);\r
5417                         ++startOffset;\r
5418 \r
5419                         cnt = endOffset - startOffset;\r
5420                         sibling = startAncestor.nextSibling;\r
5421 \r
5422                         while (cnt > 0) {\r
5423                                 nextSibling = sibling.nextSibling;\r
5424                                 n = _traverseFullySelected(sibling, how);\r
5425 \r
5426                                 if (frag)\r
5427                                         frag.appendChild(n);\r
5428 \r
5429                                 sibling = nextSibling;\r
5430                                 --cnt;\r
5431                         }\r
5432 \r
5433                         n = _traverseRightBoundary(endAncestor, how);\r
5434 \r
5435                         if (frag)\r
5436                                 frag.appendChild(n);\r
5437 \r
5438                         if (how != CLONE) {\r
5439                                 t.setStartAfter(startAncestor);\r
5440                                 t.collapse(TRUE);\r
5441                         }\r
5442 \r
5443                         return frag;\r
5444                 };\r
5445 \r
5446                 function _traverseRightBoundary(root, how) {\r
5447                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];\r
5448 \r
5449                         if (next == root)\r
5450                                 return _traverseNode(next, isFullySelected, FALSE, how);\r
5451 \r
5452                         parent = next.parentNode;\r
5453                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);\r
5454 \r
5455                         while (parent) {\r
5456                                 while (next) {\r
5457                                         prevSibling = next.previousSibling;\r
5458                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);\r
5459 \r
5460                                         if (how != DELETE)\r
5461                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);\r
5462 \r
5463                                         isFullySelected = TRUE;\r
5464                                         next = prevSibling;\r
5465                                 }\r
5466 \r
5467                                 if (parent == root)\r
5468                                         return clonedParent;\r
5469 \r
5470                                 next = parent.previousSibling;\r
5471                                 parent = parent.parentNode;\r
5472 \r
5473                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);\r
5474 \r
5475                                 if (how != DELETE)\r
5476                                         clonedGrandParent.appendChild(clonedParent);\r
5477 \r
5478                                 clonedParent = clonedGrandParent;\r
5479                         }\r
5480                 };\r
5481 \r
5482                 function _traverseLeftBoundary(root, how) {\r
5483                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;\r
5484 \r
5485                         if (next == root)\r
5486                                 return _traverseNode(next, isFullySelected, TRUE, how);\r
5487 \r
5488                         parent = next.parentNode;\r
5489                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);\r
5490 \r
5491                         while (parent) {\r
5492                                 while (next) {\r
5493                                         nextSibling = next.nextSibling;\r
5494                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);\r
5495 \r
5496                                         if (how != DELETE)\r
5497                                                 clonedParent.appendChild(clonedChild);\r
5498 \r
5499                                         isFullySelected = TRUE;\r
5500                                         next = nextSibling;\r
5501                                 }\r
5502 \r
5503                                 if (parent == root)\r
5504                                         return clonedParent;\r
5505 \r
5506                                 next = parent.nextSibling;\r
5507                                 parent = parent.parentNode;\r
5508 \r
5509                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);\r
5510 \r
5511                                 if (how != DELETE)\r
5512                                         clonedGrandParent.appendChild(clonedParent);\r
5513 \r
5514                                 clonedParent = clonedGrandParent;\r
5515                         }\r
5516                 };\r
5517 \r
5518                 function _traverseNode(n, isFullySelected, isLeft, how) {\r
5519                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;\r
5520 \r
5521                         if (isFullySelected)\r
5522                                 return _traverseFullySelected(n, how);\r
5523 \r
5524                         if (n.nodeType == 3 /* TEXT_NODE */) {\r
5525                                 txtValue = n.nodeValue;\r
5526 \r
5527                                 if (isLeft) {\r
5528                                         offset = t[START_OFFSET];\r
5529                                         newNodeValue = txtValue.substring(offset);\r
5530                                         oldNodeValue = txtValue.substring(0, offset);\r
5531                                 } else {\r
5532                                         offset = t[END_OFFSET];\r
5533                                         newNodeValue = txtValue.substring(0, offset);\r
5534                                         oldNodeValue = txtValue.substring(offset);\r
5535                                 }\r
5536 \r
5537                                 if (how != CLONE)\r
5538                                         n.nodeValue = oldNodeValue;\r
5539 \r
5540                                 if (how == DELETE)\r
5541                                         return;\r
5542 \r
5543                                 newNode = n.cloneNode(FALSE);\r
5544                                 newNode.nodeValue = newNodeValue;\r
5545 \r
5546                                 return newNode;\r
5547                         }\r
5548 \r
5549                         if (how == DELETE)\r
5550                                 return;\r
5551 \r
5552                         return n.cloneNode(FALSE);\r
5553                 };\r
5554 \r
5555                 function _traverseFullySelected(n, how) {\r
5556                         if (how != DELETE)\r
5557                                 return how == CLONE ? n.cloneNode(TRUE) : n;\r
5558 \r
5559                         n.parentNode.removeChild(n);\r
5560                 };\r
5561         };\r
5562 \r
5563         ns.Range = Range;\r
5564 })(tinymce.dom);\r
5565 \r
5566 (function() {\r
5567         function Selection(selection) {\r
5568                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;\r
5569 \r
5570                 function getPosition(rng, start) {\r
5571                         var checkRng, startIndex = 0, endIndex, inside,\r
5572                                 children, child, offset, index, position = -1, parent;\r
5573 \r
5574                         // Setup test range, collapse it and get the parent\r
5575                         checkRng = rng.duplicate();\r
5576                         checkRng.collapse(start);\r
5577                         parent = checkRng.parentElement();\r
5578 \r
5579                         // Check if the selection is within the right document\r
5580                         if (parent.ownerDocument !== selection.dom.doc)\r
5581                                 return;\r
5582 \r
5583                         // IE will report non editable elements as it's parent so look for an editable one\r
5584                         while (parent.contentEditable === "false") {\r
5585                                 parent = parent.parentNode;\r
5586                         }\r
5587 \r
5588                         // If parent doesn't have any children then return that we are inside the element\r
5589                         if (!parent.hasChildNodes()) {\r
5590                                 return {node : parent, inside : 1};\r
5591                         }\r
5592 \r
5593                         // Setup node list and endIndex\r
5594                         children = parent.children;\r
5595                         endIndex = children.length - 1;\r
5596 \r
5597                         // Perform a binary search for the position\r
5598                         while (startIndex <= endIndex) {\r
5599                                 index = Math.floor((startIndex + endIndex) / 2);\r
5600 \r
5601                                 // Move selection to node and compare the ranges\r
5602                                 child = children[index];\r
5603                                 checkRng.moveToElementText(child);\r
5604                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);\r
5605 \r
5606                                 // Before/after or an exact match\r
5607                                 if (position > 0) {\r
5608                                         endIndex = index - 1;\r
5609                                 } else if (position < 0) {\r
5610                                         startIndex = index + 1;\r
5611                                 } else {\r
5612                                         return {node : child};\r
5613                                 }\r
5614                         }\r
5615 \r
5616                         // Check if child position is before or we didn't find a position\r
5617                         if (position < 0) {\r
5618                                 // No element child was found use the parent element and the offset inside that\r
5619                                 if (!child) {\r
5620                                         checkRng.moveToElementText(parent);\r
5621                                         checkRng.collapse(true);\r
5622                                         child = parent;\r
5623                                         inside = true;\r
5624                                 } else\r
5625                                         checkRng.collapse(false);\r
5626 \r
5627                                 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);\r
5628 \r
5629                                 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>\r
5630                                 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {\r
5631                                         checkRng = rng.duplicate();\r
5632                                         checkRng.collapse(start);\r
5633 \r
5634                                         offset = -1;\r
5635                                         while (parent == checkRng.parentElement()) {\r
5636                                                 if (checkRng.move('character', -1) == 0)\r
5637                                                         break;\r
5638 \r
5639                                                 offset++;\r
5640                                         }\r
5641                                 }\r
5642 \r
5643                                 offset = offset || checkRng.text.replace('\r\n', ' ').length;\r
5644                         } else {\r
5645                                 // Child position is after the selection endpoint\r
5646                                 checkRng.collapse(true);\r
5647                                 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);\r
5648 \r
5649                                 // Get the length of the text to find where the endpoint is relative to it's container\r
5650                                 offset = checkRng.text.replace('\r\n', ' ').length;\r
5651                         }\r
5652 \r
5653                         return {node : child, position : position, offset : offset, inside : inside};\r
5654                 };\r
5655 \r
5656                 // Returns a W3C DOM compatible range object by using the IE Range API\r
5657                 function getRange() {\r
5658                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;\r
5659 \r
5660                         // If selection is outside the current document just return an empty range\r
5661                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();\r
5662                         if (element.ownerDocument != dom.doc)\r
5663                                 return domRange;\r
5664 \r
5665                         collapsed = selection.isCollapsed();\r
5666 \r
5667                         // Handle control selection\r
5668                         if (ieRange.item) {\r
5669                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));\r
5670                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);\r
5671 \r
5672                                 return domRange;\r
5673                         }\r
5674 \r
5675                         function findEndPoint(start) {\r
5676                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;\r
5677 \r
5678                                 container = endPoint.node;\r
5679                                 offset = endPoint.offset;\r
5680 \r
5681                                 if (endPoint.inside && !container.hasChildNodes()) {\r
5682                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);\r
5683                                         return;\r
5684                                 }\r
5685 \r
5686                                 if (offset === undef) {\r
5687                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);\r
5688                                         return;\r
5689                                 }\r
5690 \r
5691                                 if (endPoint.position < 0) {\r
5692                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;\r
5693 \r
5694                                         if (!sibling) {\r
5695                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);\r
5696                                                 return;\r
5697                                         }\r
5698 \r
5699                                         if (!offset) {\r
5700                                                 if (sibling.nodeType == 3)\r
5701                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);\r
5702                                                 else\r
5703                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);\r
5704 \r
5705                                                 return;\r
5706                                         }\r
5707 \r
5708                                         // Find the text node and offset\r
5709                                         while (sibling) {\r
5710                                                 nodeValue = sibling.nodeValue;\r
5711                                                 textNodeOffset += nodeValue.length;\r
5712 \r
5713                                                 // We are at or passed the position we where looking for\r
5714                                                 if (textNodeOffset >= offset) {\r
5715                                                         container = sibling;\r
5716                                                         textNodeOffset -= offset;\r
5717                                                         textNodeOffset = nodeValue.length - textNodeOffset;\r
5718                                                         break;\r
5719                                                 }\r
5720 \r
5721                                                 sibling = sibling.nextSibling;\r
5722                                         }\r
5723                                 } else {\r
5724                                         // Find the text node and offset\r
5725                                         sibling = container.previousSibling;\r
5726 \r
5727                                         if (!sibling)\r
5728                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);\r
5729 \r
5730                                         // If there isn't any text to loop then use the first position\r
5731                                         if (!offset) {\r
5732                                                 if (container.nodeType == 3)\r
5733                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);\r
5734                                                 else\r
5735                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);\r
5736 \r
5737                                                 return;\r
5738                                         }\r
5739 \r
5740                                         while (sibling) {\r
5741                                                 textNodeOffset += sibling.nodeValue.length;\r
5742 \r
5743                                                 // We are at or passed the position we where looking for\r
5744                                                 if (textNodeOffset >= offset) {\r
5745                                                         container = sibling;\r
5746                                                         textNodeOffset -= offset;\r
5747                                                         break;\r
5748                                                 }\r
5749 \r
5750                                                 sibling = sibling.previousSibling;\r
5751                                         }\r
5752                                 }\r
5753 \r
5754                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);\r
5755                         };\r
5756 \r
5757                         try {\r
5758                                 // Find start point\r
5759                                 findEndPoint(true);\r
5760 \r
5761                                 // Find end point if needed\r
5762                                 if (!collapsed)\r
5763                                         findEndPoint();\r
5764                         } catch (ex) {\r
5765                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you\r
5766                                 // access the nodeValue or other properties of text nodes. This seems to happend when\r
5767                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.\r
5768                                 if (ex.number == -2147024809) {\r
5769                                         // Get the current selection\r
5770                                         bookmark = self.getBookmark(2);\r
5771 \r
5772                                         // Get start element\r
5773                                         tmpRange = ieRange.duplicate();\r
5774                                         tmpRange.collapse(true);\r
5775                                         element = tmpRange.parentElement();\r
5776 \r
5777                                         // Get end element\r
5778                                         if (!collapsed) {\r
5779                                                 tmpRange = ieRange.duplicate();\r
5780                                                 tmpRange.collapse(false);\r
5781                                                 element2 = tmpRange.parentElement();\r
5782                                                 element2.innerHTML = element2.innerHTML;\r
5783                                         }\r
5784 \r
5785                                         // Remove the broken elements\r
5786                                         element.innerHTML = element.innerHTML;\r
5787 \r
5788                                         // Restore the selection\r
5789                                         self.moveToBookmark(bookmark);\r
5790 \r
5791                                         // Since the range has moved we need to re-get it\r
5792                                         ieRange = selection.getRng();\r
5793 \r
5794                                         // Find start point\r
5795                                         findEndPoint(true);\r
5796 \r
5797                                         // Find end point if needed\r
5798                                         if (!collapsed)\r
5799                                                 findEndPoint();\r
5800                                 } else\r
5801                                         throw ex; // Throw other errors\r
5802                         }\r
5803 \r
5804                         return domRange;\r
5805                 };\r
5806 \r
5807                 this.getBookmark = function(type) {\r
5808                         var rng = selection.getRng(), start, end, bookmark = {};\r
5809 \r
5810                         function getIndexes(node) {\r
5811                                 var node, parent, root, children, i, indexes = [];\r
5812 \r
5813                                 parent = node.parentNode;\r
5814                                 root = dom.getRoot().parentNode;\r
5815 \r
5816                                 while (parent != root && parent.nodeType !== 9) {\r
5817                                         children = parent.children;\r
5818 \r
5819                                         i = children.length;\r
5820                                         while (i--) {\r
5821                                                 if (node === children[i]) {\r
5822                                                         indexes.push(i);\r
5823                                                         break;\r
5824                                                 }\r
5825                                         }\r
5826 \r
5827                                         node = parent;\r
5828                                         parent = parent.parentNode;\r
5829                                 }\r
5830 \r
5831                                 return indexes;\r
5832                         };\r
5833 \r
5834                         function getBookmarkEndPoint(start) {\r
5835                                 var position;\r
5836 \r
5837                                 position = getPosition(rng, start);\r
5838                                 if (position) {\r
5839                                         return {\r
5840                                                 position : position.position,\r
5841                                                 offset : position.offset,\r
5842                                                 indexes : getIndexes(position.node),\r
5843                                                 inside : position.inside\r
5844                                         };\r
5845                                 }\r
5846                         };\r
5847 \r
5848                         // Non ubstructive bookmark\r
5849                         if (type === 2) {\r
5850                                 // Handle text selection\r
5851                                 if (!rng.item) {\r
5852                                         bookmark.start = getBookmarkEndPoint(true);\r
5853 \r
5854                                         if (!selection.isCollapsed())\r
5855                                                 bookmark.end = getBookmarkEndPoint();\r
5856                                 } else\r
5857                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};\r
5858                         }\r
5859 \r
5860                         return bookmark;\r
5861                 };\r
5862 \r
5863                 this.moveToBookmark = function(bookmark) {\r
5864                         var rng, body = dom.doc.body;\r
5865 \r
5866                         function resolveIndexes(indexes) {\r
5867                                 var node, i, idx, children;\r
5868 \r
5869                                 node = dom.getRoot();\r
5870                                 for (i = indexes.length - 1; i >= 0; i--) {\r
5871                                         children = node.children;\r
5872                                         idx = indexes[i];\r
5873 \r
5874                                         if (idx <= children.length - 1) {\r
5875                                                 node = children[idx];\r
5876                                         }\r
5877                                 }\r
5878 \r
5879                                 return node;\r
5880                         };\r
5881                         \r
5882                         function setBookmarkEndPoint(start) {\r
5883                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;\r
5884 \r
5885                                 if (endPoint) {\r
5886                                         moveLeft = endPoint.position > 0;\r
5887 \r
5888                                         moveRng = body.createTextRange();\r
5889                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));\r
5890 \r
5891                                         offset = endPoint.offset;\r
5892                                         if (offset !== undef) {\r
5893                                                 moveRng.collapse(endPoint.inside || moveLeft);\r
5894                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);\r
5895                                         } else\r
5896                                                 moveRng.collapse(start);\r
5897 \r
5898                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);\r
5899 \r
5900                                         if (start)\r
5901                                                 rng.collapse(true);\r
5902                                 }\r
5903                         };\r
5904 \r
5905                         if (bookmark.start) {\r
5906                                 if (bookmark.start.ctrl) {\r
5907                                         rng = body.createControlRange();\r
5908                                         rng.addElement(resolveIndexes(bookmark.start.indexes));\r
5909                                         rng.select();\r
5910                                 } else {\r
5911                                         rng = body.createTextRange();\r
5912                                         setBookmarkEndPoint(true);\r
5913                                         setBookmarkEndPoint();\r
5914                                         rng.select();\r
5915                                 }\r
5916                         }\r
5917                 };\r
5918 \r
5919                 this.addRange = function(rng) {\r
5920                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;\r
5921 \r
5922                         function setEndPoint(start) {\r
5923                                 var container, offset, marker, tmpRng, nodes;\r
5924 \r
5925                                 marker = dom.create('a');\r
5926                                 container = start ? startContainer : endContainer;\r
5927                                 offset = start ? startOffset : endOffset;\r
5928                                 tmpRng = ieRng.duplicate();\r
5929 \r
5930                                 if (container == doc || container == doc.documentElement) {\r
5931                                         container = body;\r
5932                                         offset = 0;\r
5933                                 }\r
5934 \r
5935                                 if (container.nodeType == 3) {\r
5936                                         container.parentNode.insertBefore(marker, container);\r
5937                                         tmpRng.moveToElementText(marker);\r
5938                                         tmpRng.moveStart('character', offset);\r
5939                                         dom.remove(marker);\r
5940                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
5941                                 } else {\r
5942                                         nodes = container.childNodes;\r
5943 \r
5944                                         if (nodes.length) {\r
5945                                                 if (offset >= nodes.length) {\r
5946                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);\r
5947                                                 } else {\r
5948                                                         container.insertBefore(marker, nodes[offset]);\r
5949                                                 }\r
5950 \r
5951                                                 tmpRng.moveToElementText(marker);\r
5952                                         } else {\r
5953                                                 // Empty node selection for example <div>|</div>\r
5954                                                 marker = doc.createTextNode('\uFEFF');\r
5955                                                 container.appendChild(marker);\r
5956                                                 tmpRng.moveToElementText(marker.parentNode);\r
5957                                                 tmpRng.collapse(TRUE);\r
5958                                         }\r
5959 \r
5960                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
5961                                         dom.remove(marker);\r
5962                                 }\r
5963                         }\r
5964 \r
5965                         // Setup some shorter versions\r
5966                         startContainer = rng.startContainer;\r
5967                         startOffset = rng.startOffset;\r
5968                         endContainer = rng.endContainer;\r
5969                         endOffset = rng.endOffset;\r
5970                         ieRng = body.createTextRange();\r
5971 \r
5972                         // If single element selection then try making a control selection out of it\r
5973                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {\r
5974                                 if (startOffset == endOffset - 1) {\r
5975                                         try {\r
5976                                                 ctrlRng = body.createControlRange();\r
5977                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);\r
5978                                                 ctrlRng.select();\r
5979                                                 return;\r
5980                                         } catch (ex) {\r
5981                                                 // Ignore\r
5982                                         }\r
5983                                 }\r
5984                         }\r
5985 \r
5986                         // Set start/end point of selection\r
5987                         setEndPoint(true);\r
5988                         setEndPoint();\r
5989 \r
5990                         // Select the new range and scroll it into view\r
5991                         ieRng.select();\r
5992                 };\r
5993 \r
5994                 // Expose range method\r
5995                 this.getRangeAt = getRange;\r
5996         };\r
5997 \r
5998         // Expose the selection object\r
5999         tinymce.dom.TridentSelection = Selection;\r
6000 })();\r
6001 \r
6002 \r
6003 /*\r
6004  * Sizzle CSS Selector Engine - v1.0\r
6005  *  Copyright 2009, The Dojo Foundation\r
6006  *  Released under the MIT, BSD, and GPL Licenses.\r
6007  *  More information: http://sizzlejs.com/\r
6008  */\r
6009 (function(){\r
6010 \r
6011 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,\r
6012         done = 0,\r
6013         toString = Object.prototype.toString,\r
6014         hasDuplicate = false,\r
6015         baseHasDuplicate = true;\r
6016 \r
6017 // Here we check if the JavaScript engine is using some sort of\r
6018 // optimization where it does not always call our comparision\r
6019 // function. If that is the case, discard the hasDuplicate value.\r
6020 //   Thus far that includes Google Chrome.\r
6021 [0, 0].sort(function(){\r
6022         baseHasDuplicate = false;\r
6023         return 0;\r
6024 });\r
6025 \r
6026 var Sizzle = function(selector, context, results, seed) {\r
6027         results = results || [];\r
6028         context = context || document;\r
6029 \r
6030         var origContext = context;\r
6031 \r
6032         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {\r
6033                 return [];\r
6034         }\r
6035         \r
6036         if ( !selector || typeof selector !== "string" ) {\r
6037                 return results;\r
6038         }\r
6039 \r
6040         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),\r
6041                 soFar = selector, ret, cur, pop, i;\r
6042         \r
6043         // Reset the position of the chunker regexp (start from head)\r
6044         do {\r
6045                 chunker.exec("");\r
6046                 m = chunker.exec(soFar);\r
6047 \r
6048                 if ( m ) {\r
6049                         soFar = m[3];\r
6050                 \r
6051                         parts.push( m[1] );\r
6052                 \r
6053                         if ( m[2] ) {\r
6054                                 extra = m[3];\r
6055                                 break;\r
6056                         }\r
6057                 }\r
6058         } while ( m );\r
6059 \r
6060         if ( parts.length > 1 && origPOS.exec( selector ) ) {\r
6061                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {\r
6062                         set = posProcess( parts[0] + parts[1], context );\r
6063                 } else {\r
6064                         set = Expr.relative[ parts[0] ] ?\r
6065                                 [ context ] :\r
6066                                 Sizzle( parts.shift(), context );\r
6067 \r
6068                         while ( parts.length ) {\r
6069                                 selector = parts.shift();\r
6070 \r
6071                                 if ( Expr.relative[ selector ] ) {\r
6072                                         selector += parts.shift();\r
6073                                 }\r
6074                                 \r
6075                                 set = posProcess( selector, set );\r
6076                         }\r
6077                 }\r
6078         } else {\r
6079                 // Take a shortcut and set the context if the root selector is an ID\r
6080                 // (but not if it'll be faster if the inner selector is an ID)\r
6081                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&\r
6082                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {\r
6083                         ret = Sizzle.find( parts.shift(), context, contextXML );\r
6084                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];\r
6085                 }\r
6086 \r
6087                 if ( context ) {\r
6088                         ret = seed ?\r
6089                                 { expr: parts.pop(), set: makeArray(seed) } :\r
6090                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );\r
6091                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;\r
6092 \r
6093                         if ( parts.length > 0 ) {\r
6094                                 checkSet = makeArray(set);\r
6095                         } else {\r
6096                                 prune = false;\r
6097                         }\r
6098 \r
6099                         while ( parts.length ) {\r
6100                                 cur = parts.pop();\r
6101                                 pop = cur;\r
6102 \r
6103                                 if ( !Expr.relative[ cur ] ) {\r
6104                                         cur = "";\r
6105                                 } else {\r
6106                                         pop = parts.pop();\r
6107                                 }\r
6108 \r
6109                                 if ( pop == null ) {\r
6110                                         pop = context;\r
6111                                 }\r
6112 \r
6113                                 Expr.relative[ cur ]( checkSet, pop, contextXML );\r
6114                         }\r
6115                 } else {\r
6116                         checkSet = parts = [];\r
6117                 }\r
6118         }\r
6119 \r
6120         if ( !checkSet ) {\r
6121                 checkSet = set;\r
6122         }\r
6123 \r
6124         if ( !checkSet ) {\r
6125                 Sizzle.error( cur || selector );\r
6126         }\r
6127 \r
6128         if ( toString.call(checkSet) === "[object Array]" ) {\r
6129                 if ( !prune ) {\r
6130                         results.push.apply( results, checkSet );\r
6131                 } else if ( context && context.nodeType === 1 ) {\r
6132                         for ( i = 0; checkSet[i] != null; i++ ) {\r
6133                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {\r
6134                                         results.push( set[i] );\r
6135                                 }\r
6136                         }\r
6137                 } else {\r
6138                         for ( i = 0; checkSet[i] != null; i++ ) {\r
6139                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {\r
6140                                         results.push( set[i] );\r
6141                                 }\r
6142                         }\r
6143                 }\r
6144         } else {\r
6145                 makeArray( checkSet, results );\r
6146         }\r
6147 \r
6148         if ( extra ) {\r
6149                 Sizzle( extra, origContext, results, seed );\r
6150                 Sizzle.uniqueSort( results );\r
6151         }\r
6152 \r
6153         return results;\r
6154 };\r
6155 \r
6156 Sizzle.uniqueSort = function(results){\r
6157         if ( sortOrder ) {\r
6158                 hasDuplicate = baseHasDuplicate;\r
6159                 results.sort(sortOrder);\r
6160 \r
6161                 if ( hasDuplicate ) {\r
6162                         for ( var i = 1; i < results.length; i++ ) {\r
6163                                 if ( results[i] === results[i-1] ) {\r
6164                                         results.splice(i--, 1);\r
6165                                 }\r
6166                         }\r
6167                 }\r
6168         }\r
6169 \r
6170         return results;\r
6171 };\r
6172 \r
6173 Sizzle.matches = function(expr, set){\r
6174         return Sizzle(expr, null, null, set);\r
6175 };\r
6176 \r
6177 Sizzle.find = function(expr, context, isXML){\r
6178         var set;\r
6179 \r
6180         if ( !expr ) {\r
6181                 return [];\r
6182         }\r
6183 \r
6184         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {\r
6185                 var type = Expr.order[i], match;\r
6186                 \r
6187                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {\r
6188                         var left = match[1];\r
6189                         match.splice(1,1);\r
6190 \r
6191                         if ( left.substr( left.length - 1 ) !== "\\" ) {\r
6192                                 match[1] = (match[1] || "").replace(/\\/g, "");\r
6193                                 set = Expr.find[ type ]( match, context, isXML );\r
6194                                 if ( set != null ) {\r
6195                                         expr = expr.replace( Expr.match[ type ], "" );\r
6196                                         break;\r
6197                                 }\r
6198                         }\r
6199                 }\r
6200         }\r
6201 \r
6202         if ( !set ) {\r
6203                 set = context.getElementsByTagName("*");\r
6204         }\r
6205 \r
6206         return {set: set, expr: expr};\r
6207 };\r
6208 \r
6209 Sizzle.filter = function(expr, set, inplace, not){\r
6210         var old = expr, result = [], curLoop = set, match, anyFound,\r
6211                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);\r
6212 \r
6213         while ( expr && set.length ) {\r
6214                 for ( var type in Expr.filter ) {\r
6215                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {\r
6216                                 var filter = Expr.filter[ type ], found, item, left = match[1];\r
6217                                 anyFound = false;\r
6218 \r
6219                                 match.splice(1,1);\r
6220 \r
6221                                 if ( left.substr( left.length - 1 ) === "\\" ) {\r
6222                                         continue;\r
6223                                 }\r
6224 \r
6225                                 if ( curLoop === result ) {\r
6226                                         result = [];\r
6227                                 }\r
6228 \r
6229                                 if ( Expr.preFilter[ type ] ) {\r
6230                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );\r
6231 \r
6232                                         if ( !match ) {\r
6233                                                 anyFound = found = true;\r
6234                                         } else if ( match === true ) {\r
6235                                                 continue;\r
6236                                         }\r
6237                                 }\r
6238 \r
6239                                 if ( match ) {\r
6240                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {\r
6241                                                 if ( item ) {\r
6242                                                         found = filter( item, match, i, curLoop );\r
6243                                                         var pass = not ^ !!found;\r
6244 \r
6245                                                         if ( inplace && found != null ) {\r
6246                                                                 if ( pass ) {\r
6247                                                                         anyFound = true;\r
6248                                                                 } else {\r
6249                                                                         curLoop[i] = false;\r
6250                                                                 }\r
6251                                                         } else if ( pass ) {\r
6252                                                                 result.push( item );\r
6253                                                                 anyFound = true;\r
6254                                                         }\r
6255                                                 }\r
6256                                         }\r
6257                                 }\r
6258 \r
6259                                 if ( found !== undefined ) {\r
6260                                         if ( !inplace ) {\r
6261                                                 curLoop = result;\r
6262                                         }\r
6263 \r
6264                                         expr = expr.replace( Expr.match[ type ], "" );\r
6265 \r
6266                                         if ( !anyFound ) {\r
6267                                                 return [];\r
6268                                         }\r
6269 \r
6270                                         break;\r
6271                                 }\r
6272                         }\r
6273                 }\r
6274 \r
6275                 // Improper expression\r
6276                 if ( expr === old ) {\r
6277                         if ( anyFound == null ) {\r
6278                                 Sizzle.error( expr );\r
6279                         } else {\r
6280                                 break;\r
6281                         }\r
6282                 }\r
6283 \r
6284                 old = expr;\r
6285         }\r
6286 \r
6287         return curLoop;\r
6288 };\r
6289 \r
6290 Sizzle.error = function( msg ) {\r
6291         throw "Syntax error, unrecognized expression: " + msg;\r
6292 };\r
6293 \r
6294 var Expr = Sizzle.selectors = {\r
6295         order: [ "ID", "NAME", "TAG" ],\r
6296         match: {\r
6297                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
6298                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
6299                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,\r
6300                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,\r
6301                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,\r
6302                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,\r
6303                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,\r
6304                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/\r
6305         },\r
6306         leftMatch: {},\r
6307         attrMap: {\r
6308                 "class": "className",\r
6309                 "for": "htmlFor"\r
6310         },\r
6311         attrHandle: {\r
6312                 href: function(elem){\r
6313                         return elem.getAttribute("href");\r
6314                 }\r
6315         },\r
6316         relative: {\r
6317                 "+": function(checkSet, part){\r
6318                         var isPartStr = typeof part === "string",\r
6319                                 isTag = isPartStr && !/\W/.test(part),\r
6320                                 isPartStrNotTag = isPartStr && !isTag;\r
6321 \r
6322                         if ( isTag ) {\r
6323                                 part = part.toLowerCase();\r
6324                         }\r
6325 \r
6326                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {\r
6327                                 if ( (elem = checkSet[i]) ) {\r
6328                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}\r
6329 \r
6330                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?\r
6331                                                 elem || false :\r
6332                                                 elem === part;\r
6333                                 }\r
6334                         }\r
6335 \r
6336                         if ( isPartStrNotTag ) {\r
6337                                 Sizzle.filter( part, checkSet, true );\r
6338                         }\r
6339                 },\r
6340                 ">": function(checkSet, part){\r
6341                         var isPartStr = typeof part === "string",\r
6342                                 elem, i = 0, l = checkSet.length;\r
6343 \r
6344                         if ( isPartStr && !/\W/.test(part) ) {\r
6345                                 part = part.toLowerCase();\r
6346 \r
6347                                 for ( ; i < l; i++ ) {\r
6348                                         elem = checkSet[i];\r
6349                                         if ( elem ) {\r
6350                                                 var parent = elem.parentNode;\r
6351                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;\r
6352                                         }\r
6353                                 }\r
6354                         } else {\r
6355                                 for ( ; i < l; i++ ) {\r
6356                                         elem = checkSet[i];\r
6357                                         if ( elem ) {\r
6358                                                 checkSet[i] = isPartStr ?\r
6359                                                         elem.parentNode :\r
6360                                                         elem.parentNode === part;\r
6361                                         }\r
6362                                 }\r
6363 \r
6364                                 if ( isPartStr ) {\r
6365                                         Sizzle.filter( part, checkSet, true );\r
6366                                 }\r
6367                         }\r
6368                 },\r
6369                 "": function(checkSet, part, isXML){\r
6370                         var doneName = done++, checkFn = dirCheck, nodeCheck;\r
6371 \r
6372                         if ( typeof part === "string" && !/\W/.test(part) ) {\r
6373                                 part = part.toLowerCase();\r
6374                                 nodeCheck = part;\r
6375                                 checkFn = dirNodeCheck;\r
6376                         }\r
6377 \r
6378                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);\r
6379                 },\r
6380                 "~": function(checkSet, part, isXML){\r
6381                         var doneName = done++, checkFn = dirCheck, nodeCheck;\r
6382 \r
6383                         if ( typeof part === "string" && !/\W/.test(part) ) {\r
6384                                 part = part.toLowerCase();\r
6385                                 nodeCheck = part;\r
6386                                 checkFn = dirNodeCheck;\r
6387                         }\r
6388 \r
6389                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);\r
6390                 }\r
6391         },\r
6392         find: {\r
6393                 ID: function(match, context, isXML){\r
6394                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
6395                                 var m = context.getElementById(match[1]);\r
6396                                 return m ? [m] : [];\r
6397                         }\r
6398                 },\r
6399                 NAME: function(match, context){\r
6400                         if ( typeof context.getElementsByName !== "undefined" ) {\r
6401                                 var ret = [], results = context.getElementsByName(match[1]);\r
6402 \r
6403                                 for ( var i = 0, l = results.length; i < l; i++ ) {\r
6404                                         if ( results[i].getAttribute("name") === match[1] ) {\r
6405                                                 ret.push( results[i] );\r
6406                                         }\r
6407                                 }\r
6408 \r
6409                                 return ret.length === 0 ? null : ret;\r
6410                         }\r
6411                 },\r
6412                 TAG: function(match, context){\r
6413                         return context.getElementsByTagName(match[1]);\r
6414                 }\r
6415         },\r
6416         preFilter: {\r
6417                 CLASS: function(match, curLoop, inplace, result, not, isXML){\r
6418                         match = " " + match[1].replace(/\\/g, "") + " ";\r
6419 \r
6420                         if ( isXML ) {\r
6421                                 return match;\r
6422                         }\r
6423 \r
6424                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {\r
6425                                 if ( elem ) {\r
6426                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {\r
6427                                                 if ( !inplace ) {\r
6428                                                         result.push( elem );\r
6429                                                 }\r
6430                                         } else if ( inplace ) {\r
6431                                                 curLoop[i] = false;\r
6432                                         }\r
6433                                 }\r
6434                         }\r
6435 \r
6436                         return false;\r
6437                 },\r
6438                 ID: function(match){\r
6439                         return match[1].replace(/\\/g, "");\r
6440                 },\r
6441                 TAG: function(match, curLoop){\r
6442                         return match[1].toLowerCase();\r
6443                 },\r
6444                 CHILD: function(match){\r
6445                         if ( match[1] === "nth" ) {\r
6446                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'\r
6447                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(\r
6448                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||\r
6449                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);\r
6450 \r
6451                                 // calculate the numbers (first)n+(last) including if they are negative\r
6452                                 match[2] = (test[1] + (test[2] || 1)) - 0;\r
6453                                 match[3] = test[3] - 0;\r
6454                         }\r
6455 \r
6456                         // TODO: Move to normal caching system\r
6457                         match[0] = done++;\r
6458 \r
6459                         return match;\r
6460                 },\r
6461                 ATTR: function(match, curLoop, inplace, result, not, isXML){\r
6462                         var name = match[1].replace(/\\/g, "");\r
6463                         \r
6464                         if ( !isXML && Expr.attrMap[name] ) {\r
6465                                 match[1] = Expr.attrMap[name];\r
6466                         }\r
6467 \r
6468                         if ( match[2] === "~=" ) {\r
6469                                 match[4] = " " + match[4] + " ";\r
6470                         }\r
6471 \r
6472                         return match;\r
6473                 },\r
6474                 PSEUDO: function(match, curLoop, inplace, result, not){\r
6475                         if ( match[1] === "not" ) {\r
6476                                 // If we're dealing with a complex expression, or a simple one\r
6477                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {\r
6478                                         match[3] = Sizzle(match[3], null, null, curLoop);\r
6479                                 } else {\r
6480                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);\r
6481                                         if ( !inplace ) {\r
6482                                                 result.push.apply( result, ret );\r
6483                                         }\r
6484                                         return false;\r
6485                                 }\r
6486                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {\r
6487                                 return true;\r
6488                         }\r
6489                         \r
6490                         return match;\r
6491                 },\r
6492                 POS: function(match){\r
6493                         match.unshift( true );\r
6494                         return match;\r
6495                 }\r
6496         },\r
6497         filters: {\r
6498                 enabled: function(elem){\r
6499                         return elem.disabled === false && elem.type !== "hidden";\r
6500                 },\r
6501                 disabled: function(elem){\r
6502                         return elem.disabled === true;\r
6503                 },\r
6504                 checked: function(elem){\r
6505                         return elem.checked === true;\r
6506                 },\r
6507                 selected: function(elem){\r
6508                         // Accessing this property makes selected-by-default\r
6509                         // options in Safari work properly\r
6510                         elem.parentNode.selectedIndex;\r
6511                         return elem.selected === true;\r
6512                 },\r
6513                 parent: function(elem){\r
6514                         return !!elem.firstChild;\r
6515                 },\r
6516                 empty: function(elem){\r
6517                         return !elem.firstChild;\r
6518                 },\r
6519                 has: function(elem, i, match){\r
6520                         return !!Sizzle( match[3], elem ).length;\r
6521                 },\r
6522                 header: function(elem){\r
6523                         return (/h\d/i).test( elem.nodeName );\r
6524                 },\r
6525                 text: function(elem){\r
6526                         return "text" === elem.type;\r
6527                 },\r
6528                 radio: function(elem){\r
6529                         return "radio" === elem.type;\r
6530                 },\r
6531                 checkbox: function(elem){\r
6532                         return "checkbox" === elem.type;\r
6533                 },\r
6534                 file: function(elem){\r
6535                         return "file" === elem.type;\r
6536                 },\r
6537                 password: function(elem){\r
6538                         return "password" === elem.type;\r
6539                 },\r
6540                 submit: function(elem){\r
6541                         return "submit" === elem.type;\r
6542                 },\r
6543                 image: function(elem){\r
6544                         return "image" === elem.type;\r
6545                 },\r
6546                 reset: function(elem){\r
6547                         return "reset" === elem.type;\r
6548                 },\r
6549                 button: function(elem){\r
6550                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";\r
6551                 },\r
6552                 input: function(elem){\r
6553                         return (/input|select|textarea|button/i).test(elem.nodeName);\r
6554                 }\r
6555         },\r
6556         setFilters: {\r
6557                 first: function(elem, i){\r
6558                         return i === 0;\r
6559                 },\r
6560                 last: function(elem, i, match, array){\r
6561                         return i === array.length - 1;\r
6562                 },\r
6563                 even: function(elem, i){\r
6564                         return i % 2 === 0;\r
6565                 },\r
6566                 odd: function(elem, i){\r
6567                         return i % 2 === 1;\r
6568                 },\r
6569                 lt: function(elem, i, match){\r
6570                         return i < match[3] - 0;\r
6571                 },\r
6572                 gt: function(elem, i, match){\r
6573                         return i > match[3] - 0;\r
6574                 },\r
6575                 nth: function(elem, i, match){\r
6576                         return match[3] - 0 === i;\r
6577                 },\r
6578                 eq: function(elem, i, match){\r
6579                         return match[3] - 0 === i;\r
6580                 }\r
6581         },\r
6582         filter: {\r
6583                 PSEUDO: function(elem, match, i, array){\r
6584                         var name = match[1], filter = Expr.filters[ name ];\r
6585 \r
6586                         if ( filter ) {\r
6587                                 return filter( elem, i, match, array );\r
6588                         } else if ( name === "contains" ) {\r
6589                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;\r
6590                         } else if ( name === "not" ) {\r
6591                                 var not = match[3];\r
6592 \r
6593                                 for ( var j = 0, l = not.length; j < l; j++ ) {\r
6594                                         if ( not[j] === elem ) {\r
6595                                                 return false;\r
6596                                         }\r
6597                                 }\r
6598 \r
6599                                 return true;\r
6600                         } else {\r
6601                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );\r
6602                         }\r
6603                 },\r
6604                 CHILD: function(elem, match){\r
6605                         var type = match[1], node = elem;\r
6606                         switch (type) {\r
6607                                 case 'only':\r
6608                                 case 'first':\r
6609                                         while ( (node = node.previousSibling) )  {\r
6610                                                 if ( node.nodeType === 1 ) { \r
6611                                                         return false; \r
6612                                                 }\r
6613                                         }\r
6614                                         if ( type === "first" ) { \r
6615                                                 return true; \r
6616                                         }\r
6617                                         node = elem;\r
6618                                 case 'last':\r
6619                                         while ( (node = node.nextSibling) )      {\r
6620                                                 if ( node.nodeType === 1 ) { \r
6621                                                         return false; \r
6622                                                 }\r
6623                                         }\r
6624                                         return true;\r
6625                                 case 'nth':\r
6626                                         var first = match[2], last = match[3];\r
6627 \r
6628                                         if ( first === 1 && last === 0 ) {\r
6629                                                 return true;\r
6630                                         }\r
6631                                         \r
6632                                         var doneName = match[0],\r
6633                                                 parent = elem.parentNode;\r
6634         \r
6635                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {\r
6636                                                 var count = 0;\r
6637                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
6638                                                         if ( node.nodeType === 1 ) {\r
6639                                                                 node.nodeIndex = ++count;\r
6640                                                         }\r
6641                                                 } \r
6642                                                 parent.sizcache = doneName;\r
6643                                         }\r
6644                                         \r
6645                                         var diff = elem.nodeIndex - last;\r
6646                                         if ( first === 0 ) {\r
6647                                                 return diff === 0;\r
6648                                         } else {\r
6649                                                 return ( diff % first === 0 && diff / first >= 0 );\r
6650                                         }\r
6651                         }\r
6652                 },\r
6653                 ID: function(elem, match){\r
6654                         return elem.nodeType === 1 && elem.getAttribute("id") === match;\r
6655                 },\r
6656                 TAG: function(elem, match){\r
6657                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;\r
6658                 },\r
6659                 CLASS: function(elem, match){\r
6660                         return (" " + (elem.className || elem.getAttribute("class")) + " ")\r
6661                                 .indexOf( match ) > -1;\r
6662                 },\r
6663                 ATTR: function(elem, match){\r
6664                         var name = match[1],\r
6665                                 result = Expr.attrHandle[ name ] ?\r
6666                                         Expr.attrHandle[ name ]( elem ) :\r
6667                                         elem[ name ] != null ?\r
6668                                                 elem[ name ] :\r
6669                                                 elem.getAttribute( name ),\r
6670                                 value = result + "",\r
6671                                 type = match[2],\r
6672                                 check = match[4];\r
6673 \r
6674                         return result == null ?\r
6675                                 type === "!=" :\r
6676                                 type === "=" ?\r
6677                                 value === check :\r
6678                                 type === "*=" ?\r
6679                                 value.indexOf(check) >= 0 :\r
6680                                 type === "~=" ?\r
6681                                 (" " + value + " ").indexOf(check) >= 0 :\r
6682                                 !check ?\r
6683                                 value && result !== false :\r
6684                                 type === "!=" ?\r
6685                                 value !== check :\r
6686                                 type === "^=" ?\r
6687                                 value.indexOf(check) === 0 :\r
6688                                 type === "$=" ?\r
6689                                 value.substr(value.length - check.length) === check :\r
6690                                 type === "|=" ?\r
6691                                 value === check || value.substr(0, check.length + 1) === check + "-" :\r
6692                                 false;\r
6693                 },\r
6694                 POS: function(elem, match, i, array){\r
6695                         var name = match[2], filter = Expr.setFilters[ name ];\r
6696 \r
6697                         if ( filter ) {\r
6698                                 return filter( elem, i, match, array );\r
6699                         }\r
6700                 }\r
6701         }\r
6702 };\r
6703 \r
6704 var origPOS = Expr.match.POS,\r
6705         fescape = function(all, num){\r
6706                 return "\\" + (num - 0 + 1);\r
6707         };\r
6708 \r
6709 for ( var type in Expr.match ) {\r
6710         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );\r
6711         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );\r
6712 }\r
6713 \r
6714 var makeArray = function(array, results) {\r
6715         array = Array.prototype.slice.call( array, 0 );\r
6716 \r
6717         if ( results ) {\r
6718                 results.push.apply( results, array );\r
6719                 return results;\r
6720         }\r
6721         \r
6722         return array;\r
6723 };\r
6724 \r
6725 // Perform a simple check to determine if the browser is capable of\r
6726 // converting a NodeList to an array using builtin methods.\r
6727 // Also verifies that the returned array holds DOM nodes\r
6728 // (which is not the case in the Blackberry browser)\r
6729 try {\r
6730         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;\r
6731 \r
6732 // Provide a fallback method if it does not work\r
6733 } catch(e){\r
6734         makeArray = function(array, results) {\r
6735                 var ret = results || [], i = 0;\r
6736 \r
6737                 if ( toString.call(array) === "[object Array]" ) {\r
6738                         Array.prototype.push.apply( ret, array );\r
6739                 } else {\r
6740                         if ( typeof array.length === "number" ) {\r
6741                                 for ( var l = array.length; i < l; i++ ) {\r
6742                                         ret.push( array[i] );\r
6743                                 }\r
6744                         } else {\r
6745                                 for ( ; array[i]; i++ ) {\r
6746                                         ret.push( array[i] );\r
6747                                 }\r
6748                         }\r
6749                 }\r
6750 \r
6751                 return ret;\r
6752         };\r
6753 }\r
6754 \r
6755 var sortOrder;\r
6756 \r
6757 if ( document.documentElement.compareDocumentPosition ) {\r
6758         sortOrder = function( a, b ) {\r
6759                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {\r
6760                         if ( a == b ) {\r
6761                                 hasDuplicate = true;\r
6762                         }\r
6763                         return a.compareDocumentPosition ? -1 : 1;\r
6764                 }\r
6765 \r
6766                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;\r
6767                 if ( ret === 0 ) {\r
6768                         hasDuplicate = true;\r
6769                 }\r
6770                 return ret;\r
6771         };\r
6772 } else if ( "sourceIndex" in document.documentElement ) {\r
6773         sortOrder = function( a, b ) {\r
6774                 if ( !a.sourceIndex || !b.sourceIndex ) {\r
6775                         if ( a == b ) {\r
6776                                 hasDuplicate = true;\r
6777                         }\r
6778                         return a.sourceIndex ? -1 : 1;\r
6779                 }\r
6780 \r
6781                 var ret = a.sourceIndex - b.sourceIndex;\r
6782                 if ( ret === 0 ) {\r
6783                         hasDuplicate = true;\r
6784                 }\r
6785                 return ret;\r
6786         };\r
6787 } else if ( document.createRange ) {\r
6788         sortOrder = function( a, b ) {\r
6789                 if ( !a.ownerDocument || !b.ownerDocument ) {\r
6790                         if ( a == b ) {\r
6791                                 hasDuplicate = true;\r
6792                         }\r
6793                         return a.ownerDocument ? -1 : 1;\r
6794                 }\r
6795 \r
6796                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();\r
6797                 aRange.setStart(a, 0);\r
6798                 aRange.setEnd(a, 0);\r
6799                 bRange.setStart(b, 0);\r
6800                 bRange.setEnd(b, 0);\r
6801                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);\r
6802                 if ( ret === 0 ) {\r
6803                         hasDuplicate = true;\r
6804                 }\r
6805                 return ret;\r
6806         };\r
6807 }\r
6808 \r
6809 // Utility function for retreiving the text value of an array of DOM nodes\r
6810 Sizzle.getText = function( elems ) {\r
6811         var ret = "", elem;\r
6812 \r
6813         for ( var i = 0; elems[i]; i++ ) {\r
6814                 elem = elems[i];\r
6815 \r
6816                 // Get the text from text nodes and CDATA nodes\r
6817                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {\r
6818                         ret += elem.nodeValue;\r
6819 \r
6820                 // Traverse everything else, except comment nodes\r
6821                 } else if ( elem.nodeType !== 8 ) {\r
6822                         ret += Sizzle.getText( elem.childNodes );\r
6823                 }\r
6824         }\r
6825 \r
6826         return ret;\r
6827 };\r
6828 \r
6829 // Check to see if the browser returns elements by name when\r
6830 // querying by getElementById (and provide a workaround)\r
6831 (function(){\r
6832         // We're going to inject a fake input element with a specified name\r
6833         var form = document.createElement("div"),\r
6834                 id = "script" + (new Date()).getTime();\r
6835         form.innerHTML = "<a name='" + id + "'/>";\r
6836 \r
6837         // Inject it into the root element, check its status, and remove it quickly\r
6838         var root = document.documentElement;\r
6839         root.insertBefore( form, root.firstChild );\r
6840 \r
6841         // The workaround has to do additional checks after a getElementById\r
6842         // Which slows things down for other browsers (hence the branching)\r
6843         if ( document.getElementById( id ) ) {\r
6844                 Expr.find.ID = function(match, context, isXML){\r
6845                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
6846                                 var m = context.getElementById(match[1]);\r
6847                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];\r
6848                         }\r
6849                 };\r
6850 \r
6851                 Expr.filter.ID = function(elem, match){\r
6852                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");\r
6853                         return elem.nodeType === 1 && node && node.nodeValue === match;\r
6854                 };\r
6855         }\r
6856 \r
6857         root.removeChild( form );\r
6858         root = form = null; // release memory in IE\r
6859 })();\r
6860 \r
6861 (function(){\r
6862         // Check to see if the browser returns only elements\r
6863         // when doing getElementsByTagName("*")\r
6864 \r
6865         // Create a fake element\r
6866         var div = document.createElement("div");\r
6867         div.appendChild( document.createComment("") );\r
6868 \r
6869         // Make sure no comments are found\r
6870         if ( div.getElementsByTagName("*").length > 0 ) {\r
6871                 Expr.find.TAG = function(match, context){\r
6872                         var results = context.getElementsByTagName(match[1]);\r
6873 \r
6874                         // Filter out possible comments\r
6875                         if ( match[1] === "*" ) {\r
6876                                 var tmp = [];\r
6877 \r
6878                                 for ( var i = 0; results[i]; i++ ) {\r
6879                                         if ( results[i].nodeType === 1 ) {\r
6880                                                 tmp.push( results[i] );\r
6881                                         }\r
6882                                 }\r
6883 \r
6884                                 results = tmp;\r
6885                         }\r
6886 \r
6887                         return results;\r
6888                 };\r
6889         }\r
6890 \r
6891         // Check to see if an attribute returns normalized href attributes\r
6892         div.innerHTML = "<a href='#'></a>";\r
6893         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&\r
6894                         div.firstChild.getAttribute("href") !== "#" ) {\r
6895                 Expr.attrHandle.href = function(elem){\r
6896                         return elem.getAttribute("href", 2);\r
6897                 };\r
6898         }\r
6899 \r
6900         div = null; // release memory in IE\r
6901 })();\r
6902 \r
6903 if ( document.querySelectorAll ) {\r
6904         (function(){\r
6905                 var oldSizzle = Sizzle, div = document.createElement("div");\r
6906                 div.innerHTML = "<p class='TEST'></p>";\r
6907 \r
6908                 // Safari can't handle uppercase or unicode characters when\r
6909                 // in quirks mode.\r
6910                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {\r
6911                         return;\r
6912                 }\r
6913         \r
6914                 Sizzle = function(query, context, extra, seed){\r
6915                         context = context || document;\r
6916 \r
6917                         // Only use querySelectorAll on non-XML documents\r
6918                         // (ID selectors don't work in non-HTML documents)\r
6919                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {\r
6920                                 try {\r
6921                                         return makeArray( context.querySelectorAll(query), extra );\r
6922                                 } catch(e){}\r
6923                         }\r
6924                 \r
6925                         return oldSizzle(query, context, extra, seed);\r
6926                 };\r
6927 \r
6928                 for ( var prop in oldSizzle ) {\r
6929                         Sizzle[ prop ] = oldSizzle[ prop ];\r
6930                 }\r
6931 \r
6932                 div = null; // release memory in IE\r
6933         })();\r
6934 }\r
6935 \r
6936 (function(){\r
6937         var div = document.createElement("div");\r
6938 \r
6939         div.innerHTML = "<div class='test e'></div><div class='test'></div>";\r
6940 \r
6941         // Opera can't find a second classname (in 9.6)\r
6942         // Also, make sure that getElementsByClassName actually exists\r
6943         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {\r
6944                 return;\r
6945         }\r
6946 \r
6947         // Safari caches class attributes, doesn't catch changes (in 3.2)\r
6948         div.lastChild.className = "e";\r
6949 \r
6950         if ( div.getElementsByClassName("e").length === 1 ) {\r
6951                 return;\r
6952         }\r
6953         \r
6954         Expr.order.splice(1, 0, "CLASS");\r
6955         Expr.find.CLASS = function(match, context, isXML) {\r
6956                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {\r
6957                         return context.getElementsByClassName(match[1]);\r
6958                 }\r
6959         };\r
6960 \r
6961         div = null; // release memory in IE\r
6962 })();\r
6963 \r
6964 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
6965         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
6966                 var elem = checkSet[i];\r
6967                 if ( elem ) {\r
6968                         elem = elem[dir];\r
6969                         var match = false;\r
6970 \r
6971                         while ( elem ) {\r
6972                                 if ( elem.sizcache === doneName ) {\r
6973                                         match = checkSet[elem.sizset];\r
6974                                         break;\r
6975                                 }\r
6976 \r
6977                                 if ( elem.nodeType === 1 && !isXML ){\r
6978                                         elem.sizcache = doneName;\r
6979                                         elem.sizset = i;\r
6980                                 }\r
6981 \r
6982                                 if ( elem.nodeName.toLowerCase() === cur ) {\r
6983                                         match = elem;\r
6984                                         break;\r
6985                                 }\r
6986 \r
6987                                 elem = elem[dir];\r
6988                         }\r
6989 \r
6990                         checkSet[i] = match;\r
6991                 }\r
6992         }\r
6993 }\r
6994 \r
6995 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
6996         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
6997                 var elem = checkSet[i];\r
6998                 if ( elem ) {\r
6999                         elem = elem[dir];\r
7000                         var match = false;\r
7001 \r
7002                         while ( elem ) {\r
7003                                 if ( elem.sizcache === doneName ) {\r
7004                                         match = checkSet[elem.sizset];\r
7005                                         break;\r
7006                                 }\r
7007 \r
7008                                 if ( elem.nodeType === 1 ) {\r
7009                                         if ( !isXML ) {\r
7010                                                 elem.sizcache = doneName;\r
7011                                                 elem.sizset = i;\r
7012                                         }\r
7013                                         if ( typeof cur !== "string" ) {\r
7014                                                 if ( elem === cur ) {\r
7015                                                         match = true;\r
7016                                                         break;\r
7017                                                 }\r
7018 \r
7019                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {\r
7020                                                 match = elem;\r
7021                                                 break;\r
7022                                         }\r
7023                                 }\r
7024 \r
7025                                 elem = elem[dir];\r
7026                         }\r
7027 \r
7028                         checkSet[i] = match;\r
7029                 }\r
7030         }\r
7031 }\r
7032 \r
7033 Sizzle.contains = document.compareDocumentPosition ? function(a, b){\r
7034         return !!(a.compareDocumentPosition(b) & 16);\r
7035 } : function(a, b){\r
7036         return a !== b && (a.contains ? a.contains(b) : true);\r
7037 };\r
7038 \r
7039 Sizzle.isXML = function(elem){\r
7040         // documentElement is verified for cases where it doesn't yet exist\r
7041         // (such as loading iframes in IE - #4833) \r
7042         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;\r
7043         return documentElement ? documentElement.nodeName !== "HTML" : false;\r
7044 };\r
7045 \r
7046 var posProcess = function(selector, context){\r
7047         var tmpSet = [], later = "", match,\r
7048                 root = context.nodeType ? [context] : context;\r
7049 \r
7050         // Position selectors must be done after the filter\r
7051         // And so must :not(positional) so we move all PSEUDOs to the end\r
7052         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {\r
7053                 later += match[0];\r
7054                 selector = selector.replace( Expr.match.PSEUDO, "" );\r
7055         }\r
7056 \r
7057         selector = Expr.relative[selector] ? selector + "*" : selector;\r
7058 \r
7059         for ( var i = 0, l = root.length; i < l; i++ ) {\r
7060                 Sizzle( selector, root[i], tmpSet );\r
7061         }\r
7062 \r
7063         return Sizzle.filter( later, tmpSet );\r
7064 };\r
7065 \r
7066 // EXPOSE\r
7067 \r
7068 window.tinymce.dom.Sizzle = Sizzle;\r
7069 \r
7070 })();\r
7071 \r
7072 \r
7073 (function(tinymce) {\r
7074         // Shorten names\r
7075         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;\r
7076 \r
7077         tinymce.create('tinymce.dom.EventUtils', {\r
7078                 EventUtils : function() {\r
7079                         this.inits = [];\r
7080                         this.events = [];\r
7081                 },\r
7082 \r
7083                 add : function(o, n, f, s) {\r
7084                         var cb, t = this, el = t.events, r;\r
7085 \r
7086                         if (n instanceof Array) {\r
7087                                 r = [];\r
7088 \r
7089                                 each(n, function(n) {\r
7090                                         r.push(t.add(o, n, f, s));\r
7091                                 });\r
7092 \r
7093                                 return r;\r
7094                         }\r
7095 \r
7096                         // Handle array\r
7097                         if (o && o.hasOwnProperty && o instanceof Array) {\r
7098                                 r = [];\r
7099 \r
7100                                 each(o, function(o) {\r
7101                                         o = DOM.get(o);\r
7102                                         r.push(t.add(o, n, f, s));\r
7103                                 });\r
7104 \r
7105                                 return r;\r
7106                         }\r
7107 \r
7108                         o = DOM.get(o);\r
7109 \r
7110                         if (!o)\r
7111                                 return;\r
7112 \r
7113                         // Setup event callback\r
7114                         cb = function(e) {\r
7115                                 // Is all events disabled\r
7116                                 if (t.disabled)\r
7117                                         return;\r
7118 \r
7119                                 e = e || window.event;\r
7120 \r
7121                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid\r
7122                                 if (e && isIE) {\r
7123                                         if (!e.target)\r
7124                                                 e.target = e.srcElement;\r
7125 \r
7126                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility\r
7127                                         tinymce.extend(e, t._stoppers);\r
7128                                 }\r
7129 \r
7130                                 if (!s)\r
7131                                         return f(e);\r
7132 \r
7133                                 return f.call(s, e);\r
7134                         };\r
7135 \r
7136                         if (n == 'unload') {\r
7137                                 tinymce.unloads.unshift({func : cb});\r
7138                                 return cb;\r
7139                         }\r
7140 \r
7141                         if (n == 'init') {\r
7142                                 if (t.domLoaded)\r
7143                                         cb();\r
7144                                 else\r
7145                                         t.inits.push(cb);\r
7146 \r
7147                                 return cb;\r
7148                         }\r
7149 \r
7150                         // Store away listener reference\r
7151                         el.push({\r
7152                                 obj : o,\r
7153                                 name : n,\r
7154                                 func : f,\r
7155                                 cfunc : cb,\r
7156                                 scope : s\r
7157                         });\r
7158 \r
7159                         t._add(o, n, cb);\r
7160 \r
7161                         return f;\r
7162                 },\r
7163 \r
7164                 remove : function(o, n, f) {\r
7165                         var t = this, a = t.events, s = false, r;\r
7166 \r
7167                         // Handle array\r
7168                         if (o && o.hasOwnProperty && o instanceof Array) {\r
7169                                 r = [];\r
7170 \r
7171                                 each(o, function(o) {\r
7172                                         o = DOM.get(o);\r
7173                                         r.push(t.remove(o, n, f));\r
7174                                 });\r
7175 \r
7176                                 return r;\r
7177                         }\r
7178 \r
7179                         o = DOM.get(o);\r
7180 \r
7181                         each(a, function(e, i) {\r
7182                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {\r
7183                                         a.splice(i, 1);\r
7184                                         t._remove(o, n, e.cfunc);\r
7185                                         s = true;\r
7186                                         return false;\r
7187                                 }\r
7188                         });\r
7189 \r
7190                         return s;\r
7191                 },\r
7192 \r
7193                 clear : function(o) {\r
7194                         var t = this, a = t.events, i, e;\r
7195 \r
7196                         if (o) {\r
7197                                 o = DOM.get(o);\r
7198 \r
7199                                 for (i = a.length - 1; i >= 0; i--) {\r
7200                                         e = a[i];\r
7201 \r
7202                                         if (e.obj === o) {\r
7203                                                 t._remove(e.obj, e.name, e.cfunc);\r
7204                                                 e.obj = e.cfunc = null;\r
7205                                                 a.splice(i, 1);\r
7206                                         }\r
7207                                 }\r
7208                         }\r
7209                 },\r
7210 \r
7211                 cancel : function(e) {\r
7212                         if (!e)\r
7213                                 return false;\r
7214 \r
7215                         this.stop(e);\r
7216 \r
7217                         return this.prevent(e);\r
7218                 },\r
7219 \r
7220                 stop : function(e) {\r
7221                         if (e.stopPropagation)\r
7222                                 e.stopPropagation();\r
7223                         else\r
7224                                 e.cancelBubble = true;\r
7225 \r
7226                         return false;\r
7227                 },\r
7228 \r
7229                 prevent : function(e) {\r
7230                         if (e.preventDefault)\r
7231                                 e.preventDefault();\r
7232                         else\r
7233                                 e.returnValue = false;\r
7234 \r
7235                         return false;\r
7236                 },\r
7237 \r
7238                 destroy : function() {\r
7239                         var t = this;\r
7240 \r
7241                         each(t.events, function(e, i) {\r
7242                                 t._remove(e.obj, e.name, e.cfunc);\r
7243                                 e.obj = e.cfunc = null;\r
7244                         });\r
7245 \r
7246                         t.events = [];\r
7247                         t = null;\r
7248                 },\r
7249 \r
7250                 _add : function(o, n, f) {\r
7251                         if (o.attachEvent)\r
7252                                 o.attachEvent('on' + n, f);\r
7253                         else if (o.addEventListener)\r
7254                                 o.addEventListener(n, f, false);\r
7255                         else\r
7256                                 o['on' + n] = f;\r
7257                 },\r
7258 \r
7259                 _remove : function(o, n, f) {\r
7260                         if (o) {\r
7261                                 try {\r
7262                                         if (o.detachEvent)\r
7263                                                 o.detachEvent('on' + n, f);\r
7264                                         else if (o.removeEventListener)\r
7265                                                 o.removeEventListener(n, f, false);\r
7266                                         else\r
7267                                                 o['on' + n] = null;\r
7268                                 } catch (ex) {\r
7269                                         // Might fail with permission denined on IE so we just ignore that\r
7270                                 }\r
7271                         }\r
7272                 },\r
7273 \r
7274                 _pageInit : function(win) {\r
7275                         var t = this;\r
7276 \r
7277                         // Keep it from running more than once\r
7278                         if (t.domLoaded)\r
7279                                 return;\r
7280 \r
7281                         t.domLoaded = true;\r
7282 \r
7283                         each(t.inits, function(c) {\r
7284                                 c();\r
7285                         });\r
7286 \r
7287                         t.inits = [];\r
7288                 },\r
7289 \r
7290                 _wait : function(win) {\r
7291                         var t = this, doc = win.document;\r
7292 \r
7293                         // No need since the document is already loaded\r
7294                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {\r
7295                                 t.domLoaded = 1;\r
7296                                 return;\r
7297                         }\r
7298 \r
7299                         // When loaded asynchronously, the DOM Content may already be loaded\r
7300                         if (doc.readyState === 'complete') {\r
7301                                 t._pageInit(win);\r
7302                                 return;\r
7303                         }\r
7304 \r
7305                         // Use IE method\r
7306                         if (doc.attachEvent) {\r
7307                                 doc.attachEvent("onreadystatechange", function() {\r
7308                                         if (doc.readyState === "complete") {\r
7309                                                 doc.detachEvent("onreadystatechange", arguments.callee);\r
7310                                                 t._pageInit(win);\r
7311                                         }\r
7312                                 });\r
7313 \r
7314                                 if (doc.documentElement.doScroll && win == win.top) {\r
7315                                         (function() {\r
7316                                                 if (t.domLoaded)\r
7317                                                         return;\r
7318 \r
7319                                                 try {\r
7320                                                         // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.\r
7321                                                         // http://javascript.nwbox.com/IEContentLoaded/\r
7322                                                         doc.documentElement.doScroll("left");\r
7323                                                 } catch (ex) {\r
7324                                                         setTimeout(arguments.callee, 0);\r
7325                                                         return;\r
7326                                                 }\r
7327 \r
7328                                                 t._pageInit(win);\r
7329                                         })();\r
7330                                 }\r
7331                         } else if (doc.addEventListener) {\r
7332                                 t._add(win, 'DOMContentLoaded', function() {\r
7333                                         t._pageInit(win);\r
7334                                 });\r
7335                         }\r
7336 \r
7337                         t._add(win, 'load', function() {\r
7338                                 t._pageInit(win);\r
7339                         });\r
7340                 },\r
7341 \r
7342                 _stoppers : {\r
7343                         preventDefault : function() {\r
7344                                 this.returnValue = false;\r
7345                         },\r
7346 \r
7347                         stopPropagation : function() {\r
7348                                 this.cancelBubble = true;\r
7349                         }\r
7350                 }\r
7351         });\r
7352 \r
7353         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();\r
7354 \r
7355         // Dispatch DOM content loaded event for IE and Safari\r
7356         Event._wait(window);\r
7357 \r
7358         tinymce.addUnload(function() {\r
7359                 Event.destroy();\r
7360         });\r
7361 })(tinymce);\r
7362 \r
7363 (function(tinymce) {\r
7364         tinymce.dom.Element = function(id, settings) {\r
7365                 var t = this, dom, el;\r
7366 \r
7367                 t.settings = settings = settings || {};\r
7368                 t.id = id;\r
7369                 t.dom = dom = settings.dom || tinymce.DOM;\r
7370 \r
7371                 // Only IE leaks DOM references, this is a lot faster\r
7372                 if (!tinymce.isIE)\r
7373                         el = dom.get(t.id);\r
7374 \r
7375                 tinymce.each(\r
7376                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + \r
7377                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + \r
7378                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + \r
7379                                 'isHidden,setHTML,get').split(/,/)\r
7380                         , function(k) {\r
7381                                 t[k] = function() {\r
7382                                         var a = [id], i;\r
7383 \r
7384                                         for (i = 0; i < arguments.length; i++)\r
7385                                                 a.push(arguments[i]);\r
7386 \r
7387                                         a = dom[k].apply(dom, a);\r
7388                                         t.update(k);\r
7389 \r
7390                                         return a;\r
7391                                 };\r
7392                 });\r
7393 \r
7394                 tinymce.extend(t, {\r
7395                         on : function(n, f, s) {\r
7396                                 return tinymce.dom.Event.add(t.id, n, f, s);\r
7397                         },\r
7398 \r
7399                         getXY : function() {\r
7400                                 return {\r
7401                                         x : parseInt(t.getStyle('left')),\r
7402                                         y : parseInt(t.getStyle('top'))\r
7403                                 };\r
7404                         },\r
7405 \r
7406                         getSize : function() {\r
7407                                 var n = dom.get(t.id);\r
7408 \r
7409                                 return {\r
7410                                         w : parseInt(t.getStyle('width') || n.clientWidth),\r
7411                                         h : parseInt(t.getStyle('height') || n.clientHeight)\r
7412                                 };\r
7413                         },\r
7414 \r
7415                         moveTo : function(x, y) {\r
7416                                 t.setStyles({left : x, top : y});\r
7417                         },\r
7418 \r
7419                         moveBy : function(x, y) {\r
7420                                 var p = t.getXY();\r
7421 \r
7422                                 t.moveTo(p.x + x, p.y + y);\r
7423                         },\r
7424 \r
7425                         resizeTo : function(w, h) {\r
7426                                 t.setStyles({width : w, height : h});\r
7427                         },\r
7428 \r
7429                         resizeBy : function(w, h) {\r
7430                                 var s = t.getSize();\r
7431 \r
7432                                 t.resizeTo(s.w + w, s.h + h);\r
7433                         },\r
7434 \r
7435                         update : function(k) {\r
7436                                 var b;\r
7437 \r
7438                                 if (tinymce.isIE6 && settings.blocker) {\r
7439                                         k = k || '';\r
7440 \r
7441                                         // Ignore getters\r
7442                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)\r
7443                                                 return;\r
7444 \r
7445                                         // Remove blocker on remove\r
7446                                         if (k == 'remove') {\r
7447                                                 dom.remove(t.blocker);\r
7448                                                 return;\r
7449                                         }\r
7450 \r
7451                                         if (!t.blocker) {\r
7452                                                 t.blocker = dom.uniqueId();\r
7453                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});\r
7454                                                 dom.setStyle(b, 'opacity', 0);\r
7455                                         } else\r
7456                                                 b = dom.get(t.blocker);\r
7457 \r
7458                                         dom.setStyles(b, {\r
7459                                                 left : t.getStyle('left', 1),\r
7460                                                 top : t.getStyle('top', 1),\r
7461                                                 width : t.getStyle('width', 1),\r
7462                                                 height : t.getStyle('height', 1),\r
7463                                                 display : t.getStyle('display', 1),\r
7464                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1\r
7465                                         });\r
7466                                 }\r
7467                         }\r
7468                 });\r
7469         };\r
7470 })(tinymce);\r
7471 \r
7472 (function(tinymce) {\r
7473         function trimNl(s) {\r
7474                 return s.replace(/[\n\r]+/g, '');\r
7475         };\r
7476 \r
7477         // Shorten names\r
7478         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;\r
7479 \r
7480         tinymce.create('tinymce.dom.Selection', {\r
7481                 Selection : function(dom, win, serializer) {\r
7482                         var t = this;\r
7483 \r
7484                         t.dom = dom;\r
7485                         t.win = win;\r
7486                         t.serializer = serializer;\r
7487 \r
7488                         // Add events\r
7489                         each([\r
7490                                 'onBeforeSetContent',\r
7491 \r
7492                                 'onBeforeGetContent',\r
7493 \r
7494                                 'onSetContent',\r
7495 \r
7496                                 'onGetContent'\r
7497                         ], function(e) {\r
7498                                 t[e] = new tinymce.util.Dispatcher(t);\r
7499                         });\r
7500 \r
7501                         // No W3C Range support\r
7502                         if (!t.win.getSelection)\r
7503                                 t.tridentSel = new tinymce.dom.TridentSelection(t);\r
7504 \r
7505                         if (tinymce.isIE && dom.boxModel)\r
7506                                 this._fixIESelection();\r
7507 \r
7508                         // Prevent leaks\r
7509                         tinymce.addUnload(t.destroy, t);\r
7510                 },\r
7511 \r
7512                 setCursorLocation: function(node, offset) {\r
7513                         var t = this; var r = t.dom.createRng();\r
7514                         r.setStart(node, offset);\r
7515                         r.setEnd(node, offset);\r
7516                         t.setRng(r);\r
7517                         t.collapse(false);\r
7518                 },\r
7519                 getContent : function(s) {\r
7520                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;\r
7521 \r
7522                         s = s || {};\r
7523                         wb = wa = '';\r
7524                         s.get = true;\r
7525                         s.format = s.format || 'html';\r
7526                         s.forced_root_block = '';\r
7527                         t.onBeforeGetContent.dispatch(t, s);\r
7528 \r
7529                         if (s.format == 'text')\r
7530                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));\r
7531 \r
7532                         if (r.cloneContents) {\r
7533                                 n = r.cloneContents();\r
7534 \r
7535                                 if (n)\r
7536                                         e.appendChild(n);\r
7537                         } else if (is(r.item) || is(r.htmlText)) {\r
7538                                 // IE will produce invalid markup if elements are present that\r
7539                                 // it doesn't understand like custom elements or HTML5 elements.\r
7540                                 // Adding a BR in front of the contents and then remoiving it seems to fix it though.\r
7541                                 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);\r
7542                                 e.removeChild(e.firstChild);\r
7543                         } else\r
7544                                 e.innerHTML = r.toString();\r
7545 \r
7546                         // Keep whitespace before and after\r
7547                         if (/^\s/.test(e.innerHTML))\r
7548                                 wb = ' ';\r
7549 \r
7550                         if (/\s+$/.test(e.innerHTML))\r
7551                                 wa = ' ';\r
7552 \r
7553                         s.getInner = true;\r
7554 \r
7555                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;\r
7556                         t.onGetContent.dispatch(t, s);\r
7557 \r
7558                         return s.content;\r
7559                 },\r
7560 \r
7561                 setContent : function(content, args) {\r
7562                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;\r
7563 \r
7564                         args = args || {format : 'html'};\r
7565                         args.set = true;\r
7566                         content = args.content = content;\r
7567 \r
7568                         // Dispatch before set content event\r
7569                         if (!args.no_events)\r
7570                                 self.onBeforeSetContent.dispatch(self, args);\r
7571 \r
7572                         content = args.content;\r
7573 \r
7574                         if (rng.insertNode) {\r
7575                                 // Make caret marker since insertNode places the caret in the beginning of text after insert\r
7576                                 content += '<span id="__caret">_</span>';\r
7577 \r
7578                                 // Delete and insert new node\r
7579                                 if (rng.startContainer == doc && rng.endContainer == doc) {\r
7580                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents\r
7581                                         doc.body.innerHTML = content;\r
7582                                 } else {\r
7583                                         rng.deleteContents();\r
7584 \r
7585                                         if (doc.body.childNodes.length == 0) {\r
7586                                                 doc.body.innerHTML = content;\r
7587                                         } else {\r
7588                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges\r
7589                                                 if (rng.createContextualFragment) {\r
7590                                                         rng.insertNode(rng.createContextualFragment(content));\r
7591                                                 } else {\r
7592                                                         // Fake createContextualFragment call in IE 9\r
7593                                                         frag = doc.createDocumentFragment();\r
7594                                                         temp = doc.createElement('div');\r
7595 \r
7596                                                         frag.appendChild(temp);\r
7597                                                         temp.outerHTML = content;\r
7598 \r
7599                                                         rng.insertNode(frag);\r
7600                                                 }\r
7601                                         }\r
7602                                 }\r
7603 \r
7604                                 // Move to caret marker\r
7605                                 caretNode = self.dom.get('__caret');\r
7606 \r
7607                                 // Make sure we wrap it compleatly, Opera fails with a simple select call\r
7608                                 rng = doc.createRange();\r
7609                                 rng.setStartBefore(caretNode);\r
7610                                 rng.setEndBefore(caretNode);\r
7611                                 self.setRng(rng);\r
7612 \r
7613                                 // Remove the caret position\r
7614                                 self.dom.remove('__caret');\r
7615 \r
7616                                 try {\r
7617                                         self.setRng(rng);\r
7618                                 } catch (ex) {\r
7619                                         // Might fail on Opera for some odd reason\r
7620                                 }\r
7621                         } else {\r
7622                                 if (rng.item) {\r
7623                                         // Delete content and get caret text selection\r
7624                                         doc.execCommand('Delete', false, null);\r
7625                                         rng = self.getRng();\r
7626                                 }\r
7627 \r
7628                                 // Explorer removes spaces from the beginning of pasted contents\r
7629                                 if (/^\s+/.test(content)) {\r
7630                                         rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);\r
7631                                         self.dom.remove('__mce_tmp');\r
7632                                 } else\r
7633                                         rng.pasteHTML(content);\r
7634                         }\r
7635 \r
7636                         // Dispatch set content event\r
7637                         if (!args.no_events)\r
7638                                 self.onSetContent.dispatch(self, args);\r
7639                 },\r
7640 \r
7641                 getStart : function() {\r
7642                         var rng = this.getRng(), startElement, parentElement, checkRng, node;\r
7643 \r
7644                         if (rng.duplicate || rng.item) {\r
7645                                 // Control selection, return first item\r
7646                                 if (rng.item)\r
7647                                         return rng.item(0);\r
7648 \r
7649                                 // Get start element\r
7650                                 checkRng = rng.duplicate();\r
7651                                 checkRng.collapse(1);\r
7652                                 startElement = checkRng.parentElement();\r
7653 \r
7654                                 // Check if range parent is inside the start element, then return the inner parent element\r
7655                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element\r
7656                                 parentElement = node = rng.parentElement();\r
7657                                 while (node = node.parentNode) {\r
7658                                         if (node == startElement) {\r
7659                                                 startElement = parentElement;\r
7660                                                 break;\r
7661                                         }\r
7662                                 }\r
7663 \r
7664                                 return startElement;\r
7665                         } else {\r
7666                                 startElement = rng.startContainer;\r
7667 \r
7668                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())\r
7669                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];\r
7670 \r
7671                                 if (startElement && startElement.nodeType == 3)\r
7672                                         return startElement.parentNode;\r
7673 \r
7674                                 return startElement;\r
7675                         }\r
7676                 },\r
7677 \r
7678                 getEnd : function() {\r
7679                         var t = this, r = t.getRng(), e, eo;\r
7680 \r
7681                         if (r.duplicate || r.item) {\r
7682                                 if (r.item)\r
7683                                         return r.item(0);\r
7684 \r
7685                                 r = r.duplicate();\r
7686                                 r.collapse(0);\r
7687                                 e = r.parentElement();\r
7688 \r
7689                                 if (e && e.nodeName == 'BODY')\r
7690                                         return e.lastChild || e;\r
7691 \r
7692                                 return e;\r
7693                         } else {\r
7694                                 e = r.endContainer;\r
7695                                 eo = r.endOffset;\r
7696 \r
7697                                 if (e.nodeType == 1 && e.hasChildNodes())\r
7698                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];\r
7699 \r
7700                                 if (e && e.nodeType == 3)\r
7701                                         return e.parentNode;\r
7702 \r
7703                                 return e;\r
7704                         }\r
7705                 },\r
7706 \r
7707                 getBookmark : function(type, normalized) {\r
7708                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;\r
7709 \r
7710                         function findIndex(name, element) {\r
7711                                 var index = 0;\r
7712 \r
7713                                 each(dom.select(name), function(node, i) {\r
7714                                         if (node == element)\r
7715                                                 index = i;\r
7716                                 });\r
7717 \r
7718                                 return index;\r
7719                         };\r
7720 \r
7721                         if (type == 2) {\r
7722                                 function getLocation() {\r
7723                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};\r
7724 \r
7725                                         function getPoint(rng, start) {\r
7726                                                 var container = rng[start ? 'startContainer' : 'endContainer'],\r
7727                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;\r
7728 \r
7729                                                 if (container.nodeType == 3) {\r
7730                                                         if (normalized) {\r
7731                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)\r
7732                                                                         offset += node.nodeValue.length;\r
7733                                                         }\r
7734 \r
7735                                                         point.push(offset);\r
7736                                                 } else {\r
7737                                                         childNodes = container.childNodes;\r
7738 \r
7739                                                         if (offset >= childNodes.length && childNodes.length) {\r
7740                                                                 after = 1;\r
7741                                                                 offset = Math.max(0, childNodes.length - 1);\r
7742                                                         }\r
7743 \r
7744                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);\r
7745                                                 }\r
7746 \r
7747                                                 for (; container && container != root; container = container.parentNode)\r
7748                                                         point.push(t.dom.nodeIndex(container, normalized));\r
7749 \r
7750                                                 return point;\r
7751                                         };\r
7752 \r
7753                                         bookmark.start = getPoint(rng, true);\r
7754 \r
7755                                         if (!t.isCollapsed())\r
7756                                                 bookmark.end = getPoint(rng);\r
7757 \r
7758                                         return bookmark;\r
7759                                 };\r
7760 \r
7761                                 if (t.tridentSel)\r
7762                                         return t.tridentSel.getBookmark(type);\r
7763 \r
7764                                 return getLocation();\r
7765                         }\r
7766 \r
7767                         // Handle simple range\r
7768                         if (type)\r
7769                                 return {rng : t.getRng()};\r
7770 \r
7771                         rng = t.getRng();\r
7772                         id = dom.uniqueId();\r
7773                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();\r
7774                         styles = 'overflow:hidden;line-height:0px';\r
7775 \r
7776                         // Explorer method\r
7777                         if (rng.duplicate || rng.item) {\r
7778                                 // Text selection\r
7779                                 if (!rng.item) {\r
7780                                         rng2 = rng.duplicate();\r
7781 \r
7782                                         try {\r
7783                                                 // Insert start marker\r
7784                                                 rng.collapse();\r
7785                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');\r
7786 \r
7787                                                 // Insert end marker\r
7788                                                 if (!collapsed) {\r
7789                                                         rng2.collapse(false);\r
7790 \r
7791                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>\r
7792                                                         rng.moveToElementText(rng2.parentElement());\r
7793                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)\r
7794                                                                 rng2.move('character', -1);\r
7795 \r
7796                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');\r
7797                                                 }\r
7798                                         } catch (ex) {\r
7799                                                 // IE might throw unspecified error so lets ignore it\r
7800                                                 return null;\r
7801                                         }\r
7802                                 } else {\r
7803                                         // Control selection\r
7804                                         element = rng.item(0);\r
7805                                         name = element.nodeName;\r
7806 \r
7807                                         return {name : name, index : findIndex(name, element)};\r
7808                                 }\r
7809                         } else {\r
7810                                 element = t.getNode();\r
7811                                 name = element.nodeName;\r
7812                                 if (name == 'IMG')\r
7813                                         return {name : name, index : findIndex(name, element)};\r
7814 \r
7815                                 // W3C method\r
7816                                 rng2 = rng.cloneRange();\r
7817 \r
7818                                 // Insert end marker\r
7819                                 if (!collapsed) {\r
7820                                         rng2.collapse(false);\r
7821                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));\r
7822                                 }\r
7823 \r
7824                                 rng.collapse(true);\r
7825                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));\r
7826                         }\r
7827 \r
7828                         t.moveToBookmark({id : id, keep : 1});\r
7829 \r
7830                         return {id : id};\r
7831                 },\r
7832 \r
7833                 moveToBookmark : function(bookmark) {\r
7834                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;\r
7835 \r
7836                         if (bookmark) {\r
7837                                 if (bookmark.start) {\r
7838                                         rng = dom.createRng();\r
7839                                         root = dom.getRoot();\r
7840 \r
7841                                         function setEndPoint(start) {\r
7842                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;\r
7843 \r
7844                                                 if (point) {\r
7845                                                         offset = point[0];\r
7846 \r
7847                                                         // Find container node\r
7848                                                         for (node = root, i = point.length - 1; i >= 1; i--) {\r
7849                                                                 children = node.childNodes;\r
7850 \r
7851                                                                 if (point[i] > children.length - 1)\r
7852                                                                         return;\r
7853 \r
7854                                                                 node = children[point[i]];\r
7855                                                         }\r
7856 \r
7857                                                         // Move text offset to best suitable location\r
7858                                                         if (node.nodeType === 3)\r
7859                                                                 offset = Math.min(point[0], node.nodeValue.length);\r
7860 \r
7861                                                         // Move element offset to best suitable location\r
7862                                                         if (node.nodeType === 1)\r
7863                                                                 offset = Math.min(point[0], node.childNodes.length);\r
7864 \r
7865                                                         // Set offset within container node\r
7866                                                         if (start)\r
7867                                                                 rng.setStart(node, offset);\r
7868                                                         else\r
7869                                                                 rng.setEnd(node, offset);\r
7870                                                 }\r
7871 \r
7872                                                 return true;\r
7873                                         };\r
7874 \r
7875                                         if (t.tridentSel)\r
7876                                                 return t.tridentSel.moveToBookmark(bookmark);\r
7877 \r
7878                                         if (setEndPoint(true) && setEndPoint()) {\r
7879                                                 t.setRng(rng);\r
7880                                         }\r
7881                                 } else if (bookmark.id) {\r
7882                                         function restoreEndPoint(suffix) {\r
7883                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;\r
7884 \r
7885                                                 if (marker) {\r
7886                                                         node = marker.parentNode;\r
7887 \r
7888                                                         if (suffix == 'start') {\r
7889                                                                 if (!keep) {\r
7890                                                                         idx = dom.nodeIndex(marker);\r
7891                                                                 } else {\r
7892                                                                         node = marker.firstChild;\r
7893                                                                         idx = 1;\r
7894                                                                 }\r
7895 \r
7896                                                                 startContainer = endContainer = node;\r
7897                                                                 startOffset = endOffset = idx;\r
7898                                                         } else {\r
7899                                                                 if (!keep) {\r
7900                                                                         idx = dom.nodeIndex(marker);\r
7901                                                                 } else {\r
7902                                                                         node = marker.firstChild;\r
7903                                                                         idx = 1;\r
7904                                                                 }\r
7905 \r
7906                                                                 endContainer = node;\r
7907                                                                 endOffset = idx;\r
7908                                                         }\r
7909 \r
7910                                                         if (!keep) {\r
7911                                                                 prev = marker.previousSibling;\r
7912                                                                 next = marker.nextSibling;\r
7913 \r
7914                                                                 // Remove all marker text nodes\r
7915                                                                 each(tinymce.grep(marker.childNodes), function(node) {\r
7916                                                                         if (node.nodeType == 3)\r
7917                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');\r
7918                                                                 });\r
7919 \r
7920                                                                 // Remove marker but keep children if for example contents where inserted into the marker\r
7921                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature\r
7922                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))\r
7923                                                                         dom.remove(marker, 1);\r
7924 \r
7925                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node\r
7926                                                                 // 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\r
7927                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {\r
7928                                                                         idx = prev.nodeValue.length;\r
7929                                                                         prev.appendData(next.nodeValue);\r
7930                                                                         dom.remove(next);\r
7931 \r
7932                                                                         if (suffix == 'start') {\r
7933                                                                                 startContainer = endContainer = prev;\r
7934                                                                                 startOffset = endOffset = idx;\r
7935                                                                         } else {\r
7936                                                                                 endContainer = prev;\r
7937                                                                                 endOffset = idx;\r
7938                                                                         }\r
7939                                                                 }\r
7940                                                         }\r
7941                                                 }\r
7942                                         };\r
7943 \r
7944                                         function addBogus(node) {\r
7945                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly\r
7946                                                 if (dom.isBlock(node) && !node.innerHTML)\r
7947                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';\r
7948 \r
7949                                                 return node;\r
7950                                         };\r
7951 \r
7952                                         // Restore start/end points\r
7953                                         restoreEndPoint('start');\r
7954                                         restoreEndPoint('end');\r
7955 \r
7956                                         if (startContainer) {\r
7957                                                 rng = dom.createRng();\r
7958                                                 rng.setStart(addBogus(startContainer), startOffset);\r
7959                                                 rng.setEnd(addBogus(endContainer), endOffset);\r
7960                                                 t.setRng(rng);\r
7961                                         }\r
7962                                 } else if (bookmark.name) {\r
7963                                         t.select(dom.select(bookmark.name)[bookmark.index]);\r
7964                                 } else if (bookmark.rng)\r
7965                                         t.setRng(bookmark.rng);\r
7966                         }\r
7967                 },\r
7968 \r
7969                 select : function(node, content) {\r
7970                         var t = this, dom = t.dom, rng = dom.createRng(), idx;\r
7971 \r
7972                         if (node) {\r
7973                                 idx = dom.nodeIndex(node);\r
7974                                 rng.setStart(node.parentNode, idx);\r
7975                                 rng.setEnd(node.parentNode, idx + 1);\r
7976 \r
7977                                 // Find first/last text node or BR element\r
7978                                 if (content) {\r
7979                                         function setPoint(node, start) {\r
7980                                                 var walker = new tinymce.dom.TreeWalker(node, node);\r
7981 \r
7982                                                 do {\r
7983                                                         // Text node\r
7984                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {\r
7985                                                                 if (start)\r
7986                                                                         rng.setStart(node, 0);\r
7987                                                                 else\r
7988                                                                         rng.setEnd(node, node.nodeValue.length);\r
7989 \r
7990                                                                 return;\r
7991                                                         }\r
7992 \r
7993                                                         // BR element\r
7994                                                         if (node.nodeName == 'BR') {\r
7995                                                                 if (start)\r
7996                                                                         rng.setStartBefore(node);\r
7997                                                                 else\r
7998                                                                         rng.setEndBefore(node);\r
7999 \r
8000                                                                 return;\r
8001                                                         }\r
8002                                                 } while (node = (start ? walker.next() : walker.prev()));\r
8003                                         };\r
8004 \r
8005                                         setPoint(node, 1);\r
8006                                         setPoint(node);\r
8007                                 }\r
8008 \r
8009                                 t.setRng(rng);\r
8010                         }\r
8011 \r
8012                         return node;\r
8013                 },\r
8014 \r
8015                 isCollapsed : function() {\r
8016                         var t = this, r = t.getRng(), s = t.getSel();\r
8017 \r
8018                         if (!r || r.item)\r
8019                                 return false;\r
8020 \r
8021                         if (r.compareEndPoints)\r
8022                                 return r.compareEndPoints('StartToEnd', r) === 0;\r
8023 \r
8024                         return !s || r.collapsed;\r
8025                 },\r
8026 \r
8027                 collapse : function(to_start) {\r
8028                         var self = this, rng = self.getRng(), node;\r
8029 \r
8030                         // Control range on IE\r
8031                         if (rng.item) {\r
8032                                 node = rng.item(0);\r
8033                                 rng = self.win.document.body.createTextRange();\r
8034                                 rng.moveToElementText(node);\r
8035                         }\r
8036 \r
8037                         rng.collapse(!!to_start);\r
8038                         self.setRng(rng);\r
8039                 },\r
8040 \r
8041                 getSel : function() {\r
8042                         var t = this, w = this.win;\r
8043 \r
8044                         return w.getSelection ? w.getSelection() : w.document.selection;\r
8045                 },\r
8046 \r
8047                 getRng : function(w3c) {\r
8048                         var t = this, s, r, elm, doc = t.win.document;\r
8049 \r
8050                         // Found tridentSel object then we need to use that one\r
8051                         if (w3c && t.tridentSel)\r
8052                                 return t.tridentSel.getRangeAt(0);\r
8053 \r
8054                         try {\r
8055                                 if (s = t.getSel())\r
8056                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());\r
8057                         } catch (ex) {\r
8058                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe\r
8059                         }\r
8060 \r
8061                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet\r
8062                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {\r
8063                                 elm = doc.selection.createRange().item(0);\r
8064                                 r = doc.createRange();\r
8065                                 r.setStartBefore(elm);\r
8066                                 r.setEndAfter(elm);\r
8067                         }\r
8068 \r
8069                         // No range found then create an empty one\r
8070                         // This can occur when the editor is placed in a hidden container element on Gecko\r
8071                         // Or on IE when there was an exception\r
8072                         if (!r)\r
8073                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();\r
8074 \r
8075                         if (t.selectedRange && t.explicitRange) {\r
8076                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {\r
8077                                         // Safari, Opera and Chrome only ever select text which causes the range to change.\r
8078                                         // This lets us use the originally set range if the selection hasn't been changed by the user.\r
8079                                         r = t.explicitRange;\r
8080                                 } else {\r
8081                                         t.selectedRange = null;\r
8082                                         t.explicitRange = null;\r
8083                                 }\r
8084                         }\r
8085 \r
8086                         return r;\r
8087                 },\r
8088 \r
8089                 setRng : function(r) {\r
8090                         var s, t = this;\r
8091                         \r
8092                         if (!t.tridentSel) {\r
8093                                 s = t.getSel();\r
8094 \r
8095                                 if (s) {\r
8096                                         t.explicitRange = r;\r
8097 \r
8098                                         try {\r
8099                                                 s.removeAllRanges();\r
8100                                         } catch (ex) {\r
8101                                                 // IE9 might throw errors here don't know why\r
8102                                         }\r
8103 \r
8104                                         s.addRange(r);\r
8105                                         // adding range isn't always successful so we need to check range count otherwise an exception can occur\r
8106                                         t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;\r
8107                                 }\r
8108                         } else {\r
8109                                 // Is W3C Range\r
8110                                 if (r.cloneRange) {\r
8111                                         t.tridentSel.addRange(r);\r
8112                                         return;\r
8113                                 }\r
8114 \r
8115                                 // Is IE specific range\r
8116                                 try {\r
8117                                         r.select();\r
8118                                 } catch (ex) {\r
8119                                         // Needed for some odd IE bug #1843306\r
8120                                 }\r
8121                         }\r
8122                 },\r
8123 \r
8124                 setNode : function(n) {\r
8125                         var t = this;\r
8126 \r
8127                         t.setContent(t.dom.getOuterHTML(n));\r
8128 \r
8129                         return n;\r
8130                 },\r
8131 \r
8132                 getNode : function() {\r
8133                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;\r
8134 \r
8135                         // Range maybe lost after the editor is made visible again\r
8136                         if (!rng)\r
8137                                 return t.dom.getRoot();\r
8138 \r
8139                         if (rng.setStart) {\r
8140                                 elm = rng.commonAncestorContainer;\r
8141 \r
8142                                 // Handle selection a image or other control like element such as anchors\r
8143                                 if (!rng.collapsed) {\r
8144                                         if (rng.startContainer == rng.endContainer) {\r
8145                                                 if (rng.endOffset - rng.startOffset < 2) {\r
8146                                                         if (rng.startContainer.hasChildNodes())\r
8147                                                                 elm = rng.startContainer.childNodes[rng.startOffset];\r
8148                                                 }\r
8149                                         }\r
8150 \r
8151                                         // If the anchor node is a element instead of a text node then return this element\r
8152                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) \r
8153                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];\r
8154 \r
8155                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.\r
8156                                         // This happens when you double click an underlined word in FireFox.\r
8157                                         if (start.nodeType === 3 && end.nodeType === 3) {\r
8158                                                 function skipEmptyTextNodes(n, forwards) {\r
8159                                                         var orig = n;\r
8160                                                         while (n && n.nodeType === 3 && n.length === 0) {\r
8161                                                                 n = forwards ? n.nextSibling : n.previousSibling;\r
8162                                                         }\r
8163                                                         return n || orig;\r
8164                                                 }\r
8165                                                 if (start.length === rng.startOffset) {\r
8166                                                         start = skipEmptyTextNodes(start.nextSibling, true);\r
8167                                                 } else {\r
8168                                                         start = start.parentNode;\r
8169                                                 }\r
8170                                                 if (rng.endOffset === 0) {\r
8171                                                         end = skipEmptyTextNodes(end.previousSibling, false);\r
8172                                                 } else {\r
8173                                                         end = end.parentNode;\r
8174                                                 }\r
8175 \r
8176                                                 if (start && start === end)\r
8177                                                         return start;\r
8178                                         }\r
8179                                 }\r
8180 \r
8181                                 if (elm && elm.nodeType == 3)\r
8182                                         return elm.parentNode;\r
8183 \r
8184                                 return elm;\r
8185                         }\r
8186 \r
8187                         return rng.item ? rng.item(0) : rng.parentElement();\r
8188                 },\r
8189 \r
8190                 getSelectedBlocks : function(st, en) {\r
8191                         var t = this, dom = t.dom, sb, eb, n, bl = [];\r
8192 \r
8193                         sb = dom.getParent(st || t.getStart(), dom.isBlock);\r
8194                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);\r
8195 \r
8196                         if (sb)\r
8197                                 bl.push(sb);\r
8198 \r
8199                         if (sb && eb && sb != eb) {\r
8200                                 n = sb;\r
8201 \r
8202                                 var walker = new tinymce.dom.TreeWalker(sb, dom.getRoot());\r
8203                                 while ((n = walker.next()) && n != eb) {\r
8204                                         if (dom.isBlock(n))\r
8205                                                 bl.push(n);\r
8206                                 }\r
8207                         }\r
8208 \r
8209                         if (eb && sb != eb)\r
8210                                 bl.push(eb);\r
8211 \r
8212                         return bl;\r
8213                 },\r
8214 \r
8215                 normalize : function() {\r
8216                         var self = this, rng, normalized;\r
8217 \r
8218                         // TODO:\r
8219                         // Retain selection direction.\r
8220                         // Lean left/right on Gecko for inline elements.\r
8221                         // Run this on mouse up/key up when the user manually moves the selection\r
8222                         \r
8223                         // Normalize only on non IE browsers for now\r
8224                         if (tinymce.isIE)\r
8225                                 return;\r
8226 \r
8227                         function normalizeEndPoint(start) {\r
8228                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;\r
8229 \r
8230                                 container = rng[(start ? 'start' : 'end') + 'Container'];\r
8231                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];\r
8232 \r
8233                                 // If the container is a document move it to the body element\r
8234                                 if (container.nodeType === 9) {\r
8235                                         container = container.body;\r
8236                                         offset = 0;\r
8237                                 }\r
8238 \r
8239                                 // If the container is body try move it into the closest text node or position\r
8240                                 // TODO: Add more logic here to handle element selection cases\r
8241                                 if (container === body) {\r
8242                                         // Resolve the index\r
8243                                         if (container.hasChildNodes()) {\r
8244                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];\r
8245                                                 offset = 0;\r
8246 \r
8247                                                 // Don't walk into elements that doesn't have any child nodes like a IMG\r
8248                                                 if (container.hasChildNodes()) {\r
8249                                                         // Walk the DOM to find a text node to place the caret at or a BR\r
8250                                                         node = container;\r
8251                                                         walker = new tinymce.dom.TreeWalker(container, body);\r
8252                                                         do {\r
8253                                                                 // Found a text node use that position\r
8254                                                                 if (node.nodeType === 3) {\r
8255                                                                         offset = start ? 0 : node.nodeValue.length - 1;\r
8256                                                                         container = node;\r
8257                                                                         normalized = true;\r
8258                                                                         break;\r
8259                                                                 }\r
8260 \r
8261                                                                 // Found a BR/IMG element that we can place the caret before\r
8262                                                                 if (/^(BR|IMG)$/.test(node.nodeName)) {\r
8263                                                                         offset = dom.nodeIndex(node);\r
8264                                                                         container = node.parentNode;\r
8265 \r
8266                                                                         // Put caret after image when moving the end point\r
8267                                                                         if (node.nodeName ==  "IMG" && !start) {\r
8268                                                                                 offset++;\r
8269                                                                         }\r
8270 \r
8271                                                                         normalized = true;\r
8272                                                                         break;\r
8273                                                                 }\r
8274                                                         } while (node = (start ? walker.next() : walker.prev()));\r
8275                                                 }\r
8276                                         }\r
8277                                 }\r
8278 \r
8279                                 // Set endpoint if it was normalized\r
8280                                 if (normalized)\r
8281                                         rng['set' + (start ? 'Start' : 'End')](container, offset);\r
8282                         };\r
8283 \r
8284                         rng = self.getRng();\r
8285 \r
8286                         // Normalize the end points\r
8287                         normalizeEndPoint(true);\r
8288                         \r
8289                         if (!rng.collapsed)\r
8290                                 normalizeEndPoint();\r
8291 \r
8292                         // Set the selection if it was normalized\r
8293                         if (normalized) {\r
8294                                 //console.log(self.dom.dumpRng(rng));\r
8295                                 self.setRng(rng);\r
8296                         }\r
8297                 },\r
8298 \r
8299                 destroy : function(s) {\r
8300                         var t = this;\r
8301 \r
8302                         t.win = null;\r
8303 \r
8304                         // Manual destroy then remove unload handler\r
8305                         if (!s)\r
8306                                 tinymce.removeUnload(t.destroy);\r
8307                 },\r
8308 \r
8309                 // IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode\r
8310                 _fixIESelection : function() {\r
8311                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;\r
8312 \r
8313                         // Make HTML element unselectable since we are going to handle selection by hand\r
8314                         doc.documentElement.unselectable = true;\r
8315 \r
8316                         // Return range from point or null if it failed\r
8317                         function rngFromPoint(x, y) {\r
8318                                 var rng = body.createTextRange();\r
8319 \r
8320                                 try {\r
8321                                         rng.moveToPoint(x, y);\r
8322                                 } catch (ex) {\r
8323                                         // IE sometimes throws and exception, so lets just ignore it\r
8324                                         rng = null;\r
8325                                 }\r
8326 \r
8327                                 return rng;\r
8328                         };\r
8329 \r
8330                         // Fires while the selection is changing\r
8331                         function selectionChange(e) {\r
8332                                 var pointRng;\r
8333 \r
8334                                 // Check if the button is down or not\r
8335                                 if (e.button) {\r
8336                                         // Create range from mouse position\r
8337                                         pointRng = rngFromPoint(e.x, e.y);\r
8338 \r
8339                                         if (pointRng) {\r
8340                                                 // Check if pointRange is before/after selection then change the endPoint\r
8341                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)\r
8342                                                         pointRng.setEndPoint('StartToStart', startRng);\r
8343                                                 else\r
8344                                                         pointRng.setEndPoint('EndToEnd', startRng);\r
8345 \r
8346                                                 pointRng.select();\r
8347                                         }\r
8348                                 } else\r
8349                                         endSelection();\r
8350                         }\r
8351 \r
8352                         // Removes listeners\r
8353                         function endSelection() {\r
8354                                 var rng = doc.selection.createRange();\r
8355 \r
8356                                 // If the range is collapsed then use the last start range\r
8357                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)\r
8358                                         startRng.select();\r
8359 \r
8360                                 dom.unbind(doc, 'mouseup', endSelection);\r
8361                                 dom.unbind(doc, 'mousemove', selectionChange);\r
8362                                 startRng = started = 0;\r
8363                         };\r
8364 \r
8365                         // Detect when user selects outside BODY\r
8366                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {\r
8367                                 if (e.target.nodeName === 'HTML') {\r
8368                                         if (started)\r
8369                                                 endSelection();\r
8370 \r
8371                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML\r
8372                                         htmlElm = doc.documentElement;\r
8373                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)\r
8374                                                 return;\r
8375 \r
8376                                         started = 1;\r
8377                                         // Setup start position\r
8378                                         startRng = rngFromPoint(e.x, e.y);\r
8379                                         if (startRng) {\r
8380                                                 // Listen for selection change events\r
8381                                                 dom.bind(doc, 'mouseup', endSelection);\r
8382                                                 dom.bind(doc, 'mousemove', selectionChange);\r
8383 \r
8384                                                 dom.win.focus();\r
8385                                                 startRng.select();\r
8386                                         }\r
8387                                 }\r
8388                         });\r
8389                 }\r
8390         });\r
8391 })(tinymce);\r
8392 \r
8393 (function(tinymce) {\r
8394         tinymce.dom.Serializer = function(settings, dom, schema) {\r
8395                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;\r
8396 \r
8397                 // Support the old apply_source_formatting option\r
8398                 if (!settings.apply_source_formatting)\r
8399                         settings.indent = false;\r
8400 \r
8401                 // Default DOM and Schema if they are undefined\r
8402                 dom = dom || tinymce.DOM;\r
8403                 schema = schema || new tinymce.html.Schema(settings);\r
8404                 settings.entity_encoding = settings.entity_encoding || 'named';\r
8405                 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;\r
8406 \r
8407                 onPreProcess = new tinymce.util.Dispatcher(self);\r
8408 \r
8409                 onPostProcess = new tinymce.util.Dispatcher(self);\r
8410 \r
8411                 htmlParser = new tinymce.html.DomParser(settings, schema);\r
8412 \r
8413                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed\r
8414                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {\r
8415                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;\r
8416 \r
8417                         while (i--) {\r
8418                                 node = nodes[i];\r
8419 \r
8420                                 value = node.attributes.map[internalName];\r
8421                                 if (value !== undef) {\r
8422                                         // Set external name to internal value and remove internal\r
8423                                         node.attr(name, value.length > 0 ? value : null);\r
8424                                         node.attr(internalName, null);\r
8425                                 } else {\r
8426                                         // No internal attribute found then convert the value we have in the DOM\r
8427                                         value = node.attributes.map[name];\r
8428 \r
8429                                         if (name === "style")\r
8430                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);\r
8431                                         else if (urlConverter)\r
8432                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);\r
8433 \r
8434                                         node.attr(name, value.length > 0 ? value : null);\r
8435                                 }\r
8436                         }\r
8437                 });\r
8438 \r
8439                 // Remove internal classes mceItem<..>\r
8440                 htmlParser.addAttributeFilter('class', function(nodes, name) {\r
8441                         var i = nodes.length, node, value;\r
8442 \r
8443                         while (i--) {\r
8444                                 node = nodes[i];\r
8445                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');\r
8446                                 node.attr('class', value.length > 0 ? value : null);\r
8447                         }\r
8448                 });\r
8449 \r
8450                 // Remove bookmark elements\r
8451                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {\r
8452                         var i = nodes.length, node;\r
8453 \r
8454                         while (i--) {\r
8455                                 node = nodes[i];\r
8456 \r
8457                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)\r
8458                                         node.remove();\r
8459                         }\r
8460                 });\r
8461 \r
8462                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles\r
8463                 htmlParser.addNodeFilter('script,style', function(nodes, name) {\r
8464                         var i = nodes.length, node, value;\r
8465 \r
8466                         function trim(value) {\r
8467                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')\r
8468                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')\r
8469                                                 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')\r
8470                                                 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');\r
8471                         };\r
8472 \r
8473                         while (i--) {\r
8474                                 node = nodes[i];\r
8475                                 value = node.firstChild ? node.firstChild.value : '';\r
8476 \r
8477                                 if (name === "script") {\r
8478                                         // Remove mce- prefix from script elements\r
8479                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));\r
8480 \r
8481                                         if (value.length > 0)\r
8482                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';\r
8483                                 } else {\r
8484                                         if (value.length > 0)\r
8485                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';\r
8486                                 }\r
8487                         }\r
8488                 });\r
8489 \r
8490                 // Convert comments to cdata and handle protected comments\r
8491                 htmlParser.addNodeFilter('#comment', function(nodes, name) {\r
8492                         var i = nodes.length, node;\r
8493 \r
8494                         while (i--) {\r
8495                                 node = nodes[i];\r
8496 \r
8497                                 if (node.value.indexOf('[CDATA[') === 0) {\r
8498                                         node.name = '#cdata';\r
8499                                         node.type = 4;\r
8500                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');\r
8501                                 } else if (node.value.indexOf('mce:protected ') === 0) {\r
8502                                         node.name = "#text";\r
8503                                         node.type = 3;\r
8504                                         node.raw = true;\r
8505                                         node.value = unescape(node.value).substr(14);\r
8506                                 }\r
8507                         }\r
8508                 });\r
8509 \r
8510                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {\r
8511                         var i = nodes.length, node;\r
8512 \r
8513                         while (i--) {\r
8514                                 node = nodes[i];\r
8515                                 if (node.type === 7)\r
8516                                         node.remove();\r
8517                                 else if (node.type === 1) {\r
8518                                         if (name === "input" && !("type" in node.attributes.map))\r
8519                                                 node.attr('type', 'text');\r
8520                                 }\r
8521                         }\r
8522                 });\r
8523 \r
8524                 // Fix list elements, TODO: Replace this later\r
8525                 if (settings.fix_list_elements) {\r
8526                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {\r
8527                                 var i = nodes.length, node, parentNode;\r
8528 \r
8529                                 while (i--) {\r
8530                                         node = nodes[i];\r
8531                                         parentNode = node.parent;\r
8532 \r
8533                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {\r
8534                                                 if (node.prev && node.prev.name === 'li') {\r
8535                                                         node.prev.append(node);\r
8536                                                 }\r
8537                                         }\r
8538                                 }\r
8539                         });\r
8540                 }\r
8541 \r
8542                 // Remove internal data attributes\r
8543                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {\r
8544                         var i = nodes.length;\r
8545 \r
8546                         while (i--) {\r
8547                                 nodes[i].attr(name, null);\r
8548                         }\r
8549                 });\r
8550 \r
8551                 // Return public methods\r
8552                 return {\r
8553                         schema : schema,\r
8554 \r
8555                         addNodeFilter : htmlParser.addNodeFilter,\r
8556 \r
8557                         addAttributeFilter : htmlParser.addAttributeFilter,\r
8558 \r
8559                         onPreProcess : onPreProcess,\r
8560 \r
8561                         onPostProcess : onPostProcess,\r
8562 \r
8563                         serialize : function(node, args) {\r
8564                                 var impl, doc, oldDoc, htmlSerializer, content;\r
8565 \r
8566                                 // Explorer won't clone contents of script and style and the\r
8567                                 // selected index of select elements are cleared on a clone operation.\r
8568                                 if (isIE && dom.select('script,style,select,map').length > 0) {\r
8569                                         content = node.innerHTML;\r
8570                                         node = node.cloneNode(false);\r
8571                                         dom.setHTML(node, content);\r
8572                                 } else\r
8573                                         node = node.cloneNode(true);\r
8574 \r
8575                                 // Nodes needs to be attached to something in WebKit/Opera\r
8576                                 // Older builds of Opera crashes if you attach the node to an document created dynamically\r
8577                                 // and since we can't feature detect a crash we need to sniff the acutal build number\r
8578                                 // This fix will make DOM ranges and make Sizzle happy!\r
8579                                 impl = node.ownerDocument.implementation;\r
8580                                 if (impl.createHTMLDocument) {\r
8581                                         // Create an empty HTML document\r
8582                                         doc = impl.createHTMLDocument("");\r
8583 \r
8584                                         // Add the element or it's children if it's a body element to the new document\r
8585                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {\r
8586                                                 doc.body.appendChild(doc.importNode(node, true));\r
8587                                         });\r
8588 \r
8589                                         // Grab first child or body element for serialization\r
8590                                         if (node.nodeName != 'BODY')\r
8591                                                 node = doc.body.firstChild;\r
8592                                         else\r
8593                                                 node = doc.body;\r
8594 \r
8595                                         // set the new document in DOMUtils so createElement etc works\r
8596                                         oldDoc = dom.doc;\r
8597                                         dom.doc = doc;\r
8598                                 }\r
8599 \r
8600                                 args = args || {};\r
8601                                 args.format = args.format || 'html';\r
8602 \r
8603                                 // Pre process\r
8604                                 if (!args.no_events) {\r
8605                                         args.node = node;\r
8606                                         onPreProcess.dispatch(self, args);\r
8607                                 }\r
8608 \r
8609                                 // Setup serializer\r
8610                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);\r
8611 \r
8612                                 // Parse and serialize HTML\r
8613                                 args.content = htmlSerializer.serialize(\r
8614                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)\r
8615                                 );\r
8616 \r
8617                                 // Replace all BOM characters for now until we can find a better solution\r
8618                                 if (!args.cleanup)\r
8619                                         args.content = args.content.replace(/\uFEFF|\u200B/g, '');\r
8620 \r
8621                                 // Post process\r
8622                                 if (!args.no_events)\r
8623                                         onPostProcess.dispatch(self, args);\r
8624 \r
8625                                 // Restore the old document if it was changed\r
8626                                 if (oldDoc)\r
8627                                         dom.doc = oldDoc;\r
8628 \r
8629                                 args.node = null;\r
8630 \r
8631                                 return args.content;\r
8632                         },\r
8633 \r
8634                         addRules : function(rules) {\r
8635                                 schema.addValidElements(rules);\r
8636                         },\r
8637 \r
8638                         setRules : function(rules) {\r
8639                                 schema.setValidElements(rules);\r
8640                         }\r
8641                 };\r
8642         };\r
8643 })(tinymce);\r
8644 (function(tinymce) {\r
8645         tinymce.dom.ScriptLoader = function(settings) {\r
8646                 var QUEUED = 0,\r
8647                         LOADING = 1,\r
8648                         LOADED = 2,\r
8649                         states = {},\r
8650                         queue = [],\r
8651                         scriptLoadedCallbacks = {},\r
8652                         queueLoadedCallbacks = [],\r
8653                         loading = 0,\r
8654                         undefined;\r
8655 \r
8656                 function loadScript(url, callback) {\r
8657                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;\r
8658 \r
8659                         // Execute callback when script is loaded\r
8660                         function done() {\r
8661                                 dom.remove(id);\r
8662 \r
8663                                 if (elm)\r
8664                                         elm.onreadystatechange = elm.onload = elm = null;\r
8665 \r
8666                                 callback();\r
8667                         };\r
8668                         \r
8669                         function error() {\r
8670                                 // Report the error so it's easier for people to spot loading errors\r
8671                                 if (typeof(console) !== "undefined" && console.log)\r
8672                                         console.log("Failed to load: " + url);\r
8673 \r
8674                                 // We can't mark it as done if there is a load error since\r
8675                                 // A) We don't want to produce 404 errors on the server and\r
8676                                 // B) the onerror event won't fire on all browsers.\r
8677                                 // done();\r
8678                         };\r
8679 \r
8680                         id = dom.uniqueId();\r
8681 \r
8682                         if (tinymce.isIE6) {\r
8683                                 uri = new tinymce.util.URI(url);\r
8684                                 loc = location;\r
8685 \r
8686                                 // If script is from same domain and we\r
8687                                 // use IE 6 then use XHR since it's more reliable\r
8688                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {\r
8689                                         tinymce.util.XHR.send({\r
8690                                                 url : tinymce._addVer(uri.getURI()),\r
8691                                                 success : function(content) {\r
8692                                                         // Create new temp script element\r
8693                                                         var script = dom.create('script', {\r
8694                                                                 type : 'text/javascript'\r
8695                                                         });\r
8696 \r
8697                                                         // Evaluate script in global scope\r
8698                                                         script.text = content;\r
8699                                                         document.getElementsByTagName('head')[0].appendChild(script);\r
8700                                                         dom.remove(script);\r
8701 \r
8702                                                         done();\r
8703                                                 },\r
8704                                                 \r
8705                                                 error : error\r
8706                                         });\r
8707 \r
8708                                         return;\r
8709                                 }\r
8710                         }\r
8711 \r
8712                         // Create new script element\r
8713                         elm = dom.create('script', {\r
8714                                 id : id,\r
8715                                 type : 'text/javascript',\r
8716                                 src : tinymce._addVer(url)\r
8717                         });\r
8718 \r
8719                         // Add onload listener for non IE browsers since IE9\r
8720                         // fires onload event before the script is parsed and executed\r
8721                         if (!tinymce.isIE)\r
8722                                 elm.onload = done;\r
8723 \r
8724                         // Add onerror event will get fired on some browsers but not all of them\r
8725                         elm.onerror = error;\r
8726 \r
8727                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly\r
8728                         if (!tinymce.isOpera) {\r
8729                                 elm.onreadystatechange = function() {\r
8730                                         var state = elm.readyState;\r
8731 \r
8732                                         // Loaded state is passed on IE 6 however there\r
8733                                         // are known issues with this method but we can't use\r
8734                                         // XHR in a cross domain loading\r
8735                                         if (state == 'complete' || state == 'loaded')\r
8736                                                 done();\r
8737                                 };\r
8738                         }\r
8739 \r
8740                         // Most browsers support this feature so we report errors\r
8741                         // for those at least to help users track their missing plugins etc\r
8742                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option\r
8743                         /*elm.onerror = function() {\r
8744                                 alert('Failed to load: ' + url);\r
8745                         };*/\r
8746 \r
8747                         // Add script to document\r
8748                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);\r
8749                 };\r
8750 \r
8751                 this.isDone = function(url) {\r
8752                         return states[url] == LOADED;\r
8753                 };\r
8754 \r
8755                 this.markDone = function(url) {\r
8756                         states[url] = LOADED;\r
8757                 };\r
8758 \r
8759                 this.add = this.load = function(url, callback, scope) {\r
8760                         var item, state = states[url];\r
8761 \r
8762                         // Add url to load queue\r
8763                         if (state == undefined) {\r
8764                                 queue.push(url);\r
8765                                 states[url] = QUEUED;\r
8766                         }\r
8767 \r
8768                         if (callback) {\r
8769                                 // Store away callback for later execution\r
8770                                 if (!scriptLoadedCallbacks[url])\r
8771                                         scriptLoadedCallbacks[url] = [];\r
8772 \r
8773                                 scriptLoadedCallbacks[url].push({\r
8774                                         func : callback,\r
8775                                         scope : scope || this\r
8776                                 });\r
8777                         }\r
8778                 };\r
8779 \r
8780                 this.loadQueue = function(callback, scope) {\r
8781                         this.loadScripts(queue, callback, scope);\r
8782                 };\r
8783 \r
8784                 this.loadScripts = function(scripts, callback, scope) {\r
8785                         var loadScripts;\r
8786 \r
8787                         function execScriptLoadedCallbacks(url) {\r
8788                                 // Execute URL callback functions\r
8789                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {\r
8790                                         callback.func.call(callback.scope);\r
8791                                 });\r
8792 \r
8793                                 scriptLoadedCallbacks[url] = undefined;\r
8794                         };\r
8795 \r
8796                         queueLoadedCallbacks.push({\r
8797                                 func : callback,\r
8798                                 scope : scope || this\r
8799                         });\r
8800 \r
8801                         loadScripts = function() {\r
8802                                 var loadingScripts = tinymce.grep(scripts);\r
8803 \r
8804                                 // Current scripts has been handled\r
8805                                 scripts.length = 0;\r
8806 \r
8807                                 // Load scripts that needs to be loaded\r
8808                                 tinymce.each(loadingScripts, function(url) {\r
8809                                         // Script is already loaded then execute script callbacks directly\r
8810                                         if (states[url] == LOADED) {\r
8811                                                 execScriptLoadedCallbacks(url);\r
8812                                                 return;\r
8813                                         }\r
8814 \r
8815                                         // Is script not loading then start loading it\r
8816                                         if (states[url] != LOADING) {\r
8817                                                 states[url] = LOADING;\r
8818                                                 loading++;\r
8819 \r
8820                                                 loadScript(url, function() {\r
8821                                                         states[url] = LOADED;\r
8822                                                         loading--;\r
8823 \r
8824                                                         execScriptLoadedCallbacks(url);\r
8825 \r
8826                                                         // Load more scripts if they where added by the recently loaded script\r
8827                                                         loadScripts();\r
8828                                                 });\r
8829                                         }\r
8830                                 });\r
8831 \r
8832                                 // No scripts are currently loading then execute all pending queue loaded callbacks\r
8833                                 if (!loading) {\r
8834                                         tinymce.each(queueLoadedCallbacks, function(callback) {\r
8835                                                 callback.func.call(callback.scope);\r
8836                                         });\r
8837 \r
8838                                         queueLoadedCallbacks.length = 0;\r
8839                                 }\r
8840                         };\r
8841 \r
8842                         loadScripts();\r
8843                 };\r
8844         };\r
8845 \r
8846         // Global script loader\r
8847         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();\r
8848 })(tinymce);\r
8849 \r
8850 tinymce.dom.TreeWalker = function(start_node, root_node) {\r
8851         var node = start_node;\r
8852 \r
8853         function findSibling(node, start_name, sibling_name, shallow) {\r
8854                 var sibling, parent;\r
8855 \r
8856                 if (node) {\r
8857                         // Walk into nodes if it has a start\r
8858                         if (!shallow && node[start_name])\r
8859                                 return node[start_name];\r
8860 \r
8861                         // Return the sibling if it has one\r
8862                         if (node != root_node) {\r
8863                                 sibling = node[sibling_name];\r
8864                                 if (sibling)\r
8865                                         return sibling;\r
8866 \r
8867                                 // Walk up the parents to look for siblings\r
8868                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {\r
8869                                         sibling = parent[sibling_name];\r
8870                                         if (sibling)\r
8871                                                 return sibling;\r
8872                                 }\r
8873                         }\r
8874                 }\r
8875         };\r
8876 \r
8877         this.current = function() {\r
8878                 return node;\r
8879         };\r
8880 \r
8881         this.next = function(shallow) {\r
8882                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));\r
8883         };\r
8884 \r
8885         this.prev = function(shallow) {\r
8886                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));\r
8887         };\r
8888 };\r
8889 \r
8890 (function(tinymce) {\r
8891         tinymce.dom.RangeUtils = function(dom) {\r
8892                 var INVISIBLE_CHAR = '\uFEFF';\r
8893 \r
8894                 this.walk = function(rng, callback) {\r
8895                         var startContainer = rng.startContainer,\r
8896                                 startOffset = rng.startOffset,\r
8897                                 endContainer = rng.endContainer,\r
8898                                 endOffset = rng.endOffset,\r
8899                                 ancestor, startPoint,\r
8900                                 endPoint, node, parent, siblings, nodes;\r
8901 \r
8902                         // Handle table cell selection the table plugin enables\r
8903                         // you to fake select table cells and perform formatting actions on them\r
8904                         nodes = dom.select('td.mceSelected,th.mceSelected');\r
8905                         if (nodes.length > 0) {\r
8906                                 tinymce.each(nodes, function(node) {\r
8907                                         callback([node]);\r
8908                                 });\r
8909 \r
8910                                 return;\r
8911                         }\r
8912 \r
8913                         function exclude(nodes) {\r
8914                                 var node;\r
8915 \r
8916                                 // First node is excluded\r
8917                                 node = nodes[0];\r
8918                                 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {\r
8919                                         nodes.splice(0, 1);\r
8920                                 }\r
8921 \r
8922                                 // Last node is excluded\r
8923                                 node = nodes[nodes.length - 1];\r
8924                                 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {\r
8925                                         nodes.splice(nodes.length - 1, 1);\r
8926                                 }\r
8927 \r
8928                                 return nodes;\r
8929                         };\r
8930 \r
8931                         function collectSiblings(node, name, end_node) {\r
8932                                 var siblings = [];\r
8933 \r
8934                                 for (; node && node != end_node; node = node[name])\r
8935                                         siblings.push(node);\r
8936 \r
8937                                 return siblings;\r
8938                         };\r
8939 \r
8940                         function findEndPoint(node, root) {\r
8941                                 do {\r
8942                                         if (node.parentNode == root)\r
8943                                                 return node;\r
8944 \r
8945                                         node = node.parentNode;\r
8946                                 } while(node);\r
8947                         };\r
8948 \r
8949                         function walkBoundary(start_node, end_node, next) {\r
8950                                 var siblingName = next ? 'nextSibling' : 'previousSibling';\r
8951 \r
8952                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {\r
8953                                         parent = node.parentNode;\r
8954                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);\r
8955 \r
8956                                         if (siblings.length) {\r
8957                                                 if (!next)\r
8958                                                         siblings.reverse();\r
8959 \r
8960                                                 callback(exclude(siblings));\r
8961                                         }\r
8962                                 }\r
8963                         };\r
8964 \r
8965                         // If index based start position then resolve it\r
8966                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())\r
8967                                 startContainer = startContainer.childNodes[startOffset];\r
8968 \r
8969                         // If index based end position then resolve it\r
8970                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())\r
8971                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];\r
8972 \r
8973                         // Same container\r
8974                         if (startContainer == endContainer)\r
8975                                 return callback(exclude([startContainer]));\r
8976 \r
8977                         // Find common ancestor and end points\r
8978                         ancestor = dom.findCommonAncestor(startContainer, endContainer);\r
8979                                 \r
8980                         // Process left side\r
8981                         for (node = startContainer; node; node = node.parentNode) {\r
8982                                 if (node === endContainer)\r
8983                                         return walkBoundary(startContainer, ancestor, true);\r
8984 \r
8985                                 if (node === ancestor)\r
8986                                         break;\r
8987                         }\r
8988 \r
8989                         // Process right side\r
8990                         for (node = endContainer; node; node = node.parentNode) {\r
8991                                 if (node === startContainer)\r
8992                                         return walkBoundary(endContainer, ancestor);\r
8993 \r
8994                                 if (node === ancestor)\r
8995                                         break;\r
8996                         }\r
8997 \r
8998                         // Find start/end point\r
8999                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;\r
9000                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;\r
9001 \r
9002                         // Walk left leaf\r
9003                         walkBoundary(startContainer, startPoint, true);\r
9004 \r
9005                         // Walk the middle from start to end point\r
9006                         siblings = collectSiblings(\r
9007                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,\r
9008                                 'nextSibling',\r
9009                                 endPoint == endContainer ? endPoint.nextSibling : endPoint\r
9010                         );\r
9011 \r
9012                         if (siblings.length)\r
9013                                 callback(exclude(siblings));\r
9014 \r
9015                         // Walk right leaf\r
9016                         walkBoundary(endContainer, endPoint);\r
9017                 };\r
9018 \r
9019                 this.split = function(rng) {\r
9020                         var startContainer = rng.startContainer,\r
9021                                 startOffset = rng.startOffset,\r
9022                                 endContainer = rng.endContainer,\r
9023                                 endOffset = rng.endOffset;\r
9024 \r
9025                         function splitText(node, offset) {\r
9026                                 return node.splitText(offset);\r
9027                         };\r
9028 \r
9029                         // Handle single text node\r
9030                         if (startContainer == endContainer && startContainer.nodeType == 3) {\r
9031                                 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
9032                                         endContainer = splitText(startContainer, startOffset);\r
9033                                         startContainer = endContainer.previousSibling;\r
9034 \r
9035                                         if (endOffset > startOffset) {\r
9036                                                 endOffset = endOffset - startOffset;\r
9037                                                 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;\r
9038                                                 endOffset = endContainer.nodeValue.length;\r
9039                                                 startOffset = 0;\r
9040                                         } else {\r
9041                                                 endOffset = 0;\r
9042                                         }\r
9043                                 }\r
9044                         } else {\r
9045                                 // Split startContainer text node if needed\r
9046                                 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
9047                                         startContainer = splitText(startContainer, startOffset);\r
9048                                         startOffset = 0;\r
9049                                 }\r
9050 \r
9051                                 // Split endContainer text node if needed\r
9052                                 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {\r
9053                                         endContainer = splitText(endContainer, endOffset).previousSibling;\r
9054                                         endOffset = endContainer.nodeValue.length;\r
9055                                 }\r
9056                         }\r
9057 \r
9058                         return {\r
9059                                 startContainer : startContainer,\r
9060                                 startOffset : startOffset,\r
9061                                 endContainer : endContainer,\r
9062                                 endOffset : endOffset\r
9063                         };\r
9064                 };\r
9065 \r
9066         };\r
9067 \r
9068         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {\r
9069                 if (rng1 && rng2) {\r
9070                         // Compare native IE ranges\r
9071                         if (rng1.item || rng1.duplicate) {\r
9072                                 // Both are control ranges and the selected element matches\r
9073                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))\r
9074                                         return true;\r
9075 \r
9076                                 // Both are text ranges and the range matches\r
9077                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))\r
9078                                         return true;\r
9079                         } else {\r
9080                                 // Compare w3c ranges\r
9081                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;\r
9082                         }\r
9083                 }\r
9084 \r
9085                 return false;\r
9086         };\r
9087 })(tinymce);\r
9088 \r
9089 (function(tinymce) {\r
9090         var Event = tinymce.dom.Event, each = tinymce.each;\r
9091 \r
9092         tinymce.create('tinymce.ui.KeyboardNavigation', {\r
9093                 KeyboardNavigation: function(settings, dom) {\r
9094                         var t = this, root = settings.root, items = settings.items,\r
9095                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,\r
9096                                         excludeFromTabOrder = settings.excludeFromTabOrder,\r
9097                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;\r
9098 \r
9099                         dom = dom || tinymce.DOM;\r
9100 \r
9101                         itemFocussed = function(evt) {\r
9102                                 focussedId = evt.target.id;\r
9103                         };\r
9104                         \r
9105                         itemBlurred = function(evt) {\r
9106                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');\r
9107                         };\r
9108                         \r
9109                         rootFocussed = function(evt) {\r
9110                                 var item = dom.get(focussedId);\r
9111                                 dom.setAttrib(item, 'tabindex', '0');\r
9112                                 item.focus();\r
9113                         };\r
9114                         \r
9115                         t.focus = function() {\r
9116                                 dom.get(focussedId).focus();\r
9117                         };\r
9118 \r
9119                         t.destroy = function() {\r
9120                                 each(items, function(item) {\r
9121                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);\r
9122                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);\r
9123                                 });\r
9124 \r
9125                                 dom.unbind(dom.get(root), 'focus', rootFocussed);\r
9126                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);\r
9127 \r
9128                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;\r
9129                                 t.destroy = function() {};\r
9130                         };\r
9131                         \r
9132                         t.moveFocus = function(dir, evt) {\r
9133                                 var idx = -1, controls = t.controls, newFocus;\r
9134 \r
9135                                 if (!focussedId)\r
9136                                         return;\r
9137 \r
9138                                 each(items, function(item, index) {\r
9139                                         if (item.id === focussedId) {\r
9140                                                 idx = index;\r
9141                                                 return false;\r
9142                                         }\r
9143                                 });\r
9144 \r
9145                                 idx += dir;\r
9146                                 if (idx < 0) {\r
9147                                         idx = items.length - 1;\r
9148                                 } else if (idx >= items.length) {\r
9149                                         idx = 0;\r
9150                                 }\r
9151                                 \r
9152                                 newFocus = items[idx];\r
9153                                 dom.setAttrib(focussedId, 'tabindex', '-1');\r
9154                                 dom.setAttrib(newFocus.id, 'tabindex', '0');\r
9155                                 dom.get(newFocus.id).focus();\r
9156 \r
9157                                 if (settings.actOnFocus) {\r
9158                                         settings.onAction(newFocus.id);\r
9159                                 }\r
9160 \r
9161                                 if (evt)\r
9162                                         Event.cancel(evt);\r
9163                         };\r
9164                         \r
9165                         rootKeydown = function(evt) {\r
9166                                 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;\r
9167                                 \r
9168                                 switch (evt.keyCode) {\r
9169                                         case DOM_VK_LEFT:\r
9170                                                 if (enableLeftRight) t.moveFocus(-1);\r
9171                                                 break;\r
9172         \r
9173                                         case DOM_VK_RIGHT:\r
9174                                                 if (enableLeftRight) t.moveFocus(1);\r
9175                                                 break;\r
9176         \r
9177                                         case DOM_VK_UP:\r
9178                                                 if (enableUpDown) t.moveFocus(-1);\r
9179                                                 break;\r
9180 \r
9181                                         case DOM_VK_DOWN:\r
9182                                                 if (enableUpDown) t.moveFocus(1);\r
9183                                                 break;\r
9184 \r
9185                                         case DOM_VK_ESCAPE:\r
9186                                                 if (settings.onCancel) {\r
9187                                                         settings.onCancel();\r
9188                                                         Event.cancel(evt);\r
9189                                                 }\r
9190                                                 break;\r
9191 \r
9192                                         case DOM_VK_ENTER:\r
9193                                         case DOM_VK_RETURN:\r
9194                                         case DOM_VK_SPACE:\r
9195                                                 if (settings.onAction) {\r
9196                                                         settings.onAction(focussedId);\r
9197                                                         Event.cancel(evt);\r
9198                                                 }\r
9199                                                 break;\r
9200                                 }\r
9201                         };\r
9202 \r
9203                         // Set up state and listeners for each item.\r
9204                         each(items, function(item, idx) {\r
9205                                 var tabindex;\r
9206 \r
9207                                 if (!item.id) {\r
9208                                         item.id = dom.uniqueId('_mce_item_');\r
9209                                 }\r
9210 \r
9211                                 if (excludeFromTabOrder) {\r
9212                                         dom.bind(item.id, 'blur', itemBlurred);\r
9213                                         tabindex = '-1';\r
9214                                 } else {\r
9215                                         tabindex = (idx === 0 ? '0' : '-1');\r
9216                                 }\r
9217 \r
9218                                 dom.setAttrib(item.id, 'tabindex', tabindex);\r
9219                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);\r
9220                         });\r
9221                         \r
9222                         // Setup initial state for root element.\r
9223                         if (items[0]){\r
9224                                 focussedId = items[0].id;\r
9225                         }\r
9226 \r
9227                         dom.setAttrib(root, 'tabindex', '-1');\r
9228                         \r
9229                         // Setup listeners for root element.\r
9230                         dom.bind(dom.get(root), 'focus', rootFocussed);\r
9231                         dom.bind(dom.get(root), 'keydown', rootKeydown);\r
9232                 }\r
9233         });\r
9234 })(tinymce);\r
9235 \r
9236 (function(tinymce) {\r
9237         // Shorten class names\r
9238         var DOM = tinymce.DOM, is = tinymce.is;\r
9239 \r
9240         tinymce.create('tinymce.ui.Control', {\r
9241                 Control : function(id, s, editor) {\r
9242                         this.id = id;\r
9243                         this.settings = s = s || {};\r
9244                         this.rendered = false;\r
9245                         this.onRender = new tinymce.util.Dispatcher(this);\r
9246                         this.classPrefix = '';\r
9247                         this.scope = s.scope || this;\r
9248                         this.disabled = 0;\r
9249                         this.active = 0;\r
9250                         this.editor = editor;\r
9251                 },\r
9252                 \r
9253                 setAriaProperty : function(property, value) {\r
9254                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);\r
9255                         if (element) {\r
9256                                 DOM.setAttrib(element, 'aria-' + property, !!value);\r
9257                         }\r
9258                 },\r
9259                 \r
9260                 focus : function() {\r
9261                         DOM.get(this.id).focus();\r
9262                 },\r
9263 \r
9264                 setDisabled : function(s) {\r
9265                         if (s != this.disabled) {\r
9266                                 this.setAriaProperty('disabled', s);\r
9267 \r
9268                                 this.setState('Disabled', s);\r
9269                                 this.setState('Enabled', !s);\r
9270                                 this.disabled = s;\r
9271                         }\r
9272                 },\r
9273 \r
9274                 isDisabled : function() {\r
9275                         return this.disabled;\r
9276                 },\r
9277 \r
9278                 setActive : function(s) {\r
9279                         if (s != this.active) {\r
9280                                 this.setState('Active', s);\r
9281                                 this.active = s;\r
9282                                 this.setAriaProperty('pressed', s);\r
9283                         }\r
9284                 },\r
9285 \r
9286                 isActive : function() {\r
9287                         return this.active;\r
9288                 },\r
9289 \r
9290                 setState : function(c, s) {\r
9291                         var n = DOM.get(this.id);\r
9292 \r
9293                         c = this.classPrefix + c;\r
9294 \r
9295                         if (s)\r
9296                                 DOM.addClass(n, c);\r
9297                         else\r
9298                                 DOM.removeClass(n, c);\r
9299                 },\r
9300 \r
9301                 isRendered : function() {\r
9302                         return this.rendered;\r
9303                 },\r
9304 \r
9305                 renderHTML : function() {\r
9306                 },\r
9307 \r
9308                 renderTo : function(n) {\r
9309                         DOM.setHTML(n, this.renderHTML());\r
9310                 },\r
9311 \r
9312                 postRender : function() {\r
9313                         var t = this, b;\r
9314 \r
9315                         // Set pending states\r
9316                         if (is(t.disabled)) {\r
9317                                 b = t.disabled;\r
9318                                 t.disabled = -1;\r
9319                                 t.setDisabled(b);\r
9320                         }\r
9321 \r
9322                         if (is(t.active)) {\r
9323                                 b = t.active;\r
9324                                 t.active = -1;\r
9325                                 t.setActive(b);\r
9326                         }\r
9327                 },\r
9328 \r
9329                 remove : function() {\r
9330                         DOM.remove(this.id);\r
9331                         this.destroy();\r
9332                 },\r
9333 \r
9334                 destroy : function() {\r
9335                         tinymce.dom.Event.clear(this.id);\r
9336                 }\r
9337         });\r
9338 })(tinymce);\r
9339 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {\r
9340         Container : function(id, s, editor) {\r
9341                 this.parent(id, s, editor);\r
9342 \r
9343                 this.controls = [];\r
9344 \r
9345                 this.lookup = {};\r
9346         },\r
9347 \r
9348         add : function(c) {\r
9349                 this.lookup[c.id] = c;\r
9350                 this.controls.push(c);\r
9351 \r
9352                 return c;\r
9353         },\r
9354 \r
9355         get : function(n) {\r
9356                 return this.lookup[n];\r
9357         }\r
9358 });\r
9359 \r
9360 \r
9361 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {\r
9362         Separator : function(id, s) {\r
9363                 this.parent(id, s);\r
9364                 this.classPrefix = 'mceSeparator';\r
9365                 this.setDisabled(true);\r
9366         },\r
9367 \r
9368         renderHTML : function() {\r
9369                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});\r
9370         }\r
9371 });\r
9372 \r
9373 (function(tinymce) {\r
9374         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
9375 \r
9376         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {\r
9377                 MenuItem : function(id, s) {\r
9378                         this.parent(id, s);\r
9379                         this.classPrefix = 'mceMenuItem';\r
9380                 },\r
9381 \r
9382                 setSelected : function(s) {\r
9383                         this.setState('Selected', s);\r
9384                         this.setAriaProperty('checked', !!s);\r
9385                         this.selected = s;\r
9386                 },\r
9387 \r
9388                 isSelected : function() {\r
9389                         return this.selected;\r
9390                 },\r
9391 \r
9392                 postRender : function() {\r
9393                         var t = this;\r
9394                         \r
9395                         t.parent();\r
9396 \r
9397                         // Set pending state\r
9398                         if (is(t.selected))\r
9399                                 t.setSelected(t.selected);\r
9400                 }\r
9401         });\r
9402 })(tinymce);\r
9403 \r
9404 (function(tinymce) {\r
9405         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
9406 \r
9407         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {\r
9408                 Menu : function(id, s) {\r
9409                         var t = this;\r
9410 \r
9411                         t.parent(id, s);\r
9412                         t.items = {};\r
9413                         t.collapsed = false;\r
9414                         t.menuCount = 0;\r
9415                         t.onAddItem = new tinymce.util.Dispatcher(this);\r
9416                 },\r
9417 \r
9418                 expand : function(d) {\r
9419                         var t = this;\r
9420 \r
9421                         if (d) {\r
9422                                 walk(t, function(o) {\r
9423                                         if (o.expand)\r
9424                                                 o.expand();\r
9425                                 }, 'items', t);\r
9426                         }\r
9427 \r
9428                         t.collapsed = false;\r
9429                 },\r
9430 \r
9431                 collapse : function(d) {\r
9432                         var t = this;\r
9433 \r
9434                         if (d) {\r
9435                                 walk(t, function(o) {\r
9436                                         if (o.collapse)\r
9437                                                 o.collapse();\r
9438                                 }, 'items', t);\r
9439                         }\r
9440 \r
9441                         t.collapsed = true;\r
9442                 },\r
9443 \r
9444                 isCollapsed : function() {\r
9445                         return this.collapsed;\r
9446                 },\r
9447 \r
9448                 add : function(o) {\r
9449                         if (!o.settings)\r
9450                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);\r
9451 \r
9452                         this.onAddItem.dispatch(this, o);\r
9453 \r
9454                         return this.items[o.id] = o;\r
9455                 },\r
9456 \r
9457                 addSeparator : function() {\r
9458                         return this.add({separator : true});\r
9459                 },\r
9460 \r
9461                 addMenu : function(o) {\r
9462                         if (!o.collapse)\r
9463                                 o = this.createMenu(o);\r
9464 \r
9465                         this.menuCount++;\r
9466 \r
9467                         return this.add(o);\r
9468                 },\r
9469 \r
9470                 hasMenus : function() {\r
9471                         return this.menuCount !== 0;\r
9472                 },\r
9473 \r
9474                 remove : function(o) {\r
9475                         delete this.items[o.id];\r
9476                 },\r
9477 \r
9478                 removeAll : function() {\r
9479                         var t = this;\r
9480 \r
9481                         walk(t, function(o) {\r
9482                                 if (o.removeAll)\r
9483                                         o.removeAll();\r
9484                                 else\r
9485                                         o.remove();\r
9486 \r
9487                                 o.destroy();\r
9488                         }, 'items', t);\r
9489 \r
9490                         t.items = {};\r
9491                 },\r
9492 \r
9493                 createMenu : function(o) {\r
9494                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);\r
9495 \r
9496                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);\r
9497 \r
9498                         return m;\r
9499                 }\r
9500         });\r
9501 })(tinymce);\r
9502 (function(tinymce) {\r
9503         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;\r
9504 \r
9505         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {\r
9506                 DropMenu : function(id, s) {\r
9507                         s = s || {};\r
9508                         s.container = s.container || DOM.doc.body;\r
9509                         s.offset_x = s.offset_x || 0;\r
9510                         s.offset_y = s.offset_y || 0;\r
9511                         s.vp_offset_x = s.vp_offset_x || 0;\r
9512                         s.vp_offset_y = s.vp_offset_y || 0;\r
9513 \r
9514                         if (is(s.icons) && !s.icons)\r
9515                                 s['class'] += ' mceNoIcons';\r
9516 \r
9517                         this.parent(id, s);\r
9518                         this.onShowMenu = new tinymce.util.Dispatcher(this);\r
9519                         this.onHideMenu = new tinymce.util.Dispatcher(this);\r
9520                         this.classPrefix = 'mceMenu';\r
9521                 },\r
9522 \r
9523                 createMenu : function(s) {\r
9524                         var t = this, cs = t.settings, m;\r
9525 \r
9526                         s.container = s.container || cs.container;\r
9527                         s.parent = t;\r
9528                         s.constrain = s.constrain || cs.constrain;\r
9529                         s['class'] = s['class'] || cs['class'];\r
9530                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;\r
9531                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;\r
9532                         s.keyboard_focus = cs.keyboard_focus;\r
9533                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);\r
9534 \r
9535                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);\r
9536 \r
9537                         return m;\r
9538                 },\r
9539                 \r
9540                 focus : function() {\r
9541                         var t = this;\r
9542                         if (t.keyboardNav) {\r
9543                                 t.keyboardNav.focus();\r
9544                         }\r
9545                 },\r
9546 \r
9547                 update : function() {\r
9548                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;\r
9549 \r
9550                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;\r
9551                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;\r
9552 \r
9553                         if (!DOM.boxModel)\r
9554                                 t.element.setStyles({width : tw + 2, height : th + 2});\r
9555                         else\r
9556                                 t.element.setStyles({width : tw, height : th});\r
9557 \r
9558                         if (s.max_width)\r
9559                                 DOM.setStyle(co, 'width', tw);\r
9560 \r
9561                         if (s.max_height) {\r
9562                                 DOM.setStyle(co, 'height', th);\r
9563 \r
9564                                 if (tb.clientHeight < s.max_height)\r
9565                                         DOM.setStyle(co, 'overflow', 'hidden');\r
9566                         }\r
9567                 },\r
9568 \r
9569                 showMenu : function(x, y, px) {\r
9570                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;\r
9571 \r
9572                         t.collapse(1);\r
9573 \r
9574                         if (t.isMenuVisible)\r
9575                                 return;\r
9576 \r
9577                         if (!t.rendered) {\r
9578                                 co = DOM.add(t.settings.container, t.renderNode());\r
9579 \r
9580                                 each(t.items, function(o) {\r
9581                                         o.postRender();\r
9582                                 });\r
9583 \r
9584                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
9585                         } else\r
9586                                 co = DOM.get('menu_' + t.id);\r
9587 \r
9588                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug\r
9589                         if (!tinymce.isOpera)\r
9590                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});\r
9591 \r
9592                         DOM.show(co);\r
9593                         t.update();\r
9594 \r
9595                         x += s.offset_x || 0;\r
9596                         y += s.offset_y || 0;\r
9597                         vp.w -= 4;\r
9598                         vp.h -= 4;\r
9599 \r
9600                         // Move inside viewport if not submenu\r
9601                         if (s.constrain) {\r
9602                                 w = co.clientWidth - ot;\r
9603                                 h = co.clientHeight - ot;\r
9604                                 mx = vp.x + vp.w;\r
9605                                 my = vp.y + vp.h;\r
9606 \r
9607                                 if ((x + s.vp_offset_x + w) > mx)\r
9608                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);\r
9609 \r
9610                                 if ((y + s.vp_offset_y + h) > my)\r
9611                                         y = Math.max(0, (my - s.vp_offset_y) - h);\r
9612                         }\r
9613 \r
9614                         DOM.setStyles(co, {left : x , top : y});\r
9615                         t.element.update();\r
9616 \r
9617                         t.isMenuVisible = 1;\r
9618                         t.mouseClickFunc = Event.add(co, 'click', function(e) {\r
9619                                 var m;\r
9620 \r
9621                                 e = e.target;\r
9622 \r
9623                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {\r
9624                                         m = t.items[e.id];\r
9625 \r
9626                                         if (m.isDisabled())\r
9627                                                 return;\r
9628 \r
9629                                         dm = t;\r
9630 \r
9631                                         while (dm) {\r
9632                                                 if (dm.hideMenu)\r
9633                                                         dm.hideMenu();\r
9634 \r
9635                                                 dm = dm.settings.parent;\r
9636                                         }\r
9637 \r
9638                                         if (m.settings.onclick)\r
9639                                                 m.settings.onclick(e);\r
9640 \r
9641                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
9642                                 }\r
9643                         });\r
9644 \r
9645                         if (t.hasMenus()) {\r
9646                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {\r
9647                                         var m, r, mi;\r
9648 \r
9649                                         e = e.target;\r
9650                                         if (e && (e = DOM.getParent(e, 'tr'))) {\r
9651                                                 m = t.items[e.id];\r
9652 \r
9653                                                 if (t.lastMenu)\r
9654                                                         t.lastMenu.collapse(1);\r
9655 \r
9656                                                 if (m.isDisabled())\r
9657                                                         return;\r
9658 \r
9659                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {\r
9660                                                         //p = DOM.getPos(s.container);\r
9661                                                         r = DOM.getRect(e);\r
9662                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);\r
9663                                                         t.lastMenu = m;\r
9664                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');\r
9665                                                 }\r
9666                                         }\r
9667                                 });\r
9668                         }\r
9669                         \r
9670                         Event.add(co, 'keydown', t._keyHandler, t);\r
9671 \r
9672                         t.onShowMenu.dispatch(t);\r
9673 \r
9674                         if (s.keyboard_focus) { \r
9675                                 t._setupKeyboardNav(); \r
9676                         }\r
9677                 },\r
9678 \r
9679                 hideMenu : function(c) {\r
9680                         var t = this, co = DOM.get('menu_' + t.id), e;\r
9681 \r
9682                         if (!t.isMenuVisible)\r
9683                                 return;\r
9684 \r
9685                         if (t.keyboardNav) t.keyboardNav.destroy();\r
9686                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
9687                         Event.remove(co, 'click', t.mouseClickFunc);\r
9688                         Event.remove(co, 'keydown', t._keyHandler);\r
9689                         DOM.hide(co);\r
9690                         t.isMenuVisible = 0;\r
9691 \r
9692                         if (!c)\r
9693                                 t.collapse(1);\r
9694 \r
9695                         if (t.element)\r
9696                                 t.element.hide();\r
9697 \r
9698                         if (e = DOM.get(t.id))\r
9699                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');\r
9700 \r
9701                         t.onHideMenu.dispatch(t);\r
9702                 },\r
9703 \r
9704                 add : function(o) {\r
9705                         var t = this, co;\r
9706 \r
9707                         o = t.parent(o);\r
9708 \r
9709                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))\r
9710                                 t._add(DOM.select('tbody', co)[0], o);\r
9711 \r
9712                         return o;\r
9713                 },\r
9714 \r
9715                 collapse : function(d) {\r
9716                         this.parent(d);\r
9717                         this.hideMenu(1);\r
9718                 },\r
9719 \r
9720                 remove : function(o) {\r
9721                         DOM.remove(o.id);\r
9722                         this.destroy();\r
9723 \r
9724                         return this.parent(o);\r
9725                 },\r
9726 \r
9727                 destroy : function() {\r
9728                         var t = this, co = DOM.get('menu_' + t.id);\r
9729 \r
9730                         if (t.keyboardNav) t.keyboardNav.destroy();\r
9731                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
9732                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);\r
9733                         Event.remove(co, 'click', t.mouseClickFunc);\r
9734                         Event.remove(co, 'keydown', t._keyHandler);\r
9735 \r
9736                         if (t.element)\r
9737                                 t.element.remove();\r
9738 \r
9739                         DOM.remove(co);\r
9740                 },\r
9741 \r
9742                 renderNode : function() {\r
9743                         var t = this, s = t.settings, n, tb, co, w;\r
9744 \r
9745                         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'});\r
9746                         if (t.settings.parent) {\r
9747                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);\r
9748                         }\r
9749                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});\r
9750                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
9751 \r
9752                         if (s.menu_line)\r
9753                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});\r
9754 \r
9755 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});\r
9756                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});\r
9757                         tb = DOM.add(n, 'tbody');\r
9758 \r
9759                         each(t.items, function(o) {\r
9760                                 t._add(tb, o);\r
9761                         });\r
9762 \r
9763                         t.rendered = true;\r
9764 \r
9765                         return w;\r
9766                 },\r
9767 \r
9768                 // Internal functions\r
9769                 _setupKeyboardNav : function(){\r
9770                         var contextMenu, menuItems, t=this; \r
9771                         contextMenu = DOM.get('menu_' + t.id);\r
9772                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);\r
9773                         menuItems.splice(0,0,contextMenu);\r
9774                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({\r
9775                                 root: 'menu_' + t.id,\r
9776                                 items: menuItems,\r
9777                                 onCancel: function() {\r
9778                                         t.hideMenu();\r
9779                                 },\r
9780                                 enableUpDown: true\r
9781                         });\r
9782                         contextMenu.focus();\r
9783                 },\r
9784 \r
9785                 _keyHandler : function(evt) {\r
9786                         var t = this, e;\r
9787                         switch (evt.keyCode) {\r
9788                                 case 37: // Left\r
9789                                         if (t.settings.parent) {\r
9790                                                 t.hideMenu();\r
9791                                                 t.settings.parent.focus();\r
9792                                                 Event.cancel(evt);\r
9793                                         }\r
9794                                         break;\r
9795                                 case 39: // Right\r
9796                                         if (t.mouseOverFunc)\r
9797                                                 t.mouseOverFunc(evt);\r
9798                                         break;\r
9799                         }\r
9800                 },\r
9801 \r
9802                 _add : function(tb, o) {\r
9803                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;\r
9804 \r
9805                         if (s.separator) {\r
9806                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});\r
9807                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});\r
9808 \r
9809                                 if (n = ro.previousSibling)\r
9810                                         DOM.addClass(n, 'mceLast');\r
9811 \r
9812                                 return;\r
9813                         }\r
9814 \r
9815                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});\r
9816                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');\r
9817                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});\r
9818 \r
9819                         if (s.parent) {\r
9820                                 DOM.setAttrib(a, 'aria-haspopup', 'true');\r
9821                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);\r
9822                         }\r
9823 \r
9824                         DOM.addClass(it, s['class']);\r
9825 //                      n = DOM.add(n, 'span', {'class' : 'item'});\r
9826 \r
9827                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});\r
9828 \r
9829                         if (s.icon_src)\r
9830                                 DOM.add(ic, 'img', {src : s.icon_src});\r
9831 \r
9832                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);\r
9833 \r
9834                         if (o.settings.style)\r
9835                                 DOM.setAttrib(n, 'style', o.settings.style);\r
9836 \r
9837                         if (tb.childNodes.length == 1)\r
9838                                 DOM.addClass(ro, 'mceFirst');\r
9839 \r
9840                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))\r
9841                                 DOM.addClass(ro, 'mceFirst');\r
9842 \r
9843                         if (o.collapse)\r
9844                                 DOM.addClass(ro, cp + 'ItemSub');\r
9845 \r
9846                         if (n = ro.previousSibling)\r
9847                                 DOM.removeClass(n, 'mceLast');\r
9848 \r
9849                         DOM.addClass(ro, 'mceLast');\r
9850                 }\r
9851         });\r
9852 })(tinymce);\r
9853 (function(tinymce) {\r
9854         var DOM = tinymce.DOM;\r
9855 \r
9856         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {\r
9857                 Button : function(id, s, ed) {\r
9858                         this.parent(id, s, ed);\r
9859                         this.classPrefix = 'mceButton';\r
9860                 },\r
9861 \r
9862                 renderHTML : function() {\r
9863                         var cp = this.classPrefix, s = this.settings, h, l;\r
9864 \r
9865                         l = DOM.encode(s.label || '');\r
9866                         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) + '">';\r
9867                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )\r
9868                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;\r
9869                         else\r
9870                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
9871 \r
9872                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; \r
9873                         h += '</a>';\r
9874                         return h;\r
9875                 },\r
9876 \r
9877                 postRender : function() {\r
9878                         var t = this, s = t.settings, imgBookmark;\r
9879 \r
9880                         // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so\r
9881                         // need to keep the selection in case the selection is lost\r
9882                         if (tinymce.isIE && t.editor) {\r
9883                                 tinymce.dom.Event.add(t.id, 'mousedown', function(e) {\r
9884                                         var nodeName = t.editor.selection.getNode().nodeName;\r
9885                                         imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;\r
9886                                 });\r
9887                         }\r
9888                         tinymce.dom.Event.add(t.id, 'click', function(e) {\r
9889                                 if (!t.isDisabled()) {\r
9890                                         // restore the selection in case the selection is lost in IE\r
9891                                         if (tinymce.isIE && t.editor && imgBookmark !== null) {\r
9892                                                 t.editor.selection.moveToBookmark(imgBookmark);\r
9893                                         }\r
9894                                         return s.onclick.call(s.scope, e);\r
9895                                 }\r
9896                         });\r
9897                         tinymce.dom.Event.add(t.id, 'keyup', function(e) {\r
9898                                 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)\r
9899                                         return s.onclick.call(s.scope, e);\r
9900                         });\r
9901                 }\r
9902         });\r
9903 })(tinymce);\r
9904 \r
9905 (function(tinymce) {\r
9906         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;\r
9907 \r
9908         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {\r
9909                 ListBox : function(id, s, ed) {\r
9910                         var t = this;\r
9911 \r
9912                         t.parent(id, s, ed);\r
9913 \r
9914                         t.items = [];\r
9915 \r
9916                         t.onChange = new Dispatcher(t);\r
9917 \r
9918                         t.onPostRender = new Dispatcher(t);\r
9919 \r
9920                         t.onAdd = new Dispatcher(t);\r
9921 \r
9922                         t.onRenderMenu = new tinymce.util.Dispatcher(this);\r
9923 \r
9924                         t.classPrefix = 'mceListBox';\r
9925                 },\r
9926 \r
9927                 select : function(va) {\r
9928                         var t = this, fv, f;\r
9929 \r
9930                         if (va == undefined)\r
9931                                 return t.selectByIndex(-1);\r
9932 \r
9933                         // Is string or number make function selector\r
9934                         if (va && typeof(va)=="function")\r
9935                                 f = va;\r
9936                         else {\r
9937                                 f = function(v) {\r
9938                                         return v == va;\r
9939                                 };\r
9940                         }\r
9941 \r
9942                         // Do we need to do something?\r
9943                         if (va != t.selectedValue) {\r
9944                                 // Find item\r
9945                                 each(t.items, function(o, i) {\r
9946                                         if (f(o.value)) {\r
9947                                                 fv = 1;\r
9948                                                 t.selectByIndex(i);\r
9949                                                 return false;\r
9950                                         }\r
9951                                 });\r
9952 \r
9953                                 if (!fv)\r
9954                                         t.selectByIndex(-1);\r
9955                         }\r
9956                 },\r
9957 \r
9958                 selectByIndex : function(idx) {\r
9959                         var t = this, e, o, label;\r
9960 \r
9961                         if (idx != t.selectedIndex) {\r
9962                                 e = DOM.get(t.id + '_text');\r
9963                                 label = DOM.get(t.id + '_voiceDesc');\r
9964                                 o = t.items[idx];\r
9965 \r
9966                                 if (o) {\r
9967                                         t.selectedValue = o.value;\r
9968                                         t.selectedIndex = idx;\r
9969                                         DOM.setHTML(e, DOM.encode(o.title));\r
9970                                         DOM.setHTML(label, t.settings.title + " - " + o.title);\r
9971                                         DOM.removeClass(e, 'mceTitle');\r
9972                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);\r
9973                                 } else {\r
9974                                         DOM.setHTML(e, DOM.encode(t.settings.title));\r
9975                                         DOM.setHTML(label, DOM.encode(t.settings.title));\r
9976                                         DOM.addClass(e, 'mceTitle');\r
9977                                         t.selectedValue = t.selectedIndex = null;\r
9978                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);\r
9979                                 }\r
9980                                 e = 0;\r
9981                         }\r
9982                 },\r
9983 \r
9984                 add : function(n, v, o) {\r
9985                         var t = this;\r
9986 \r
9987                         o = o || {};\r
9988                         o = tinymce.extend(o, {\r
9989                                 title : n,\r
9990                                 value : v\r
9991                         });\r
9992 \r
9993                         t.items.push(o);\r
9994                         t.onAdd.dispatch(t, o);\r
9995                 },\r
9996 \r
9997                 getLength : function() {\r
9998                         return this.items.length;\r
9999                 },\r
10000 \r
10001                 renderHTML : function() {\r
10002                         var h = '', t = this, s = t.settings, cp = t.classPrefix;\r
10003 \r
10004                         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>';\r
10005                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); \r
10006                         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>';\r
10007                         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>';\r
10008                         h += '</tr></tbody></table></span>';\r
10009 \r
10010                         return h;\r
10011                 },\r
10012 \r
10013                 showMenu : function() {\r
10014                         var t = this, p2, e = DOM.get(this.id), m;\r
10015 \r
10016                         if (t.isDisabled() || t.items.length == 0)\r
10017                                 return;\r
10018 \r
10019                         if (t.menu && t.menu.isMenuVisible)\r
10020                                 return t.hideMenu();\r
10021 \r
10022                         if (!t.isMenuRendered) {\r
10023                                 t.renderMenu();\r
10024                                 t.isMenuRendered = true;\r
10025                         }\r
10026 \r
10027                         p2 = DOM.getPos(e);\r
10028 \r
10029                         m = t.menu;\r
10030                         m.settings.offset_x = p2.x;\r
10031                         m.settings.offset_y = p2.y;\r
10032                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus\r
10033 \r
10034                         // Select in menu\r
10035                         if (t.oldID)\r
10036                                 m.items[t.oldID].setSelected(0);\r
10037 \r
10038                         each(t.items, function(o) {\r
10039                                 if (o.value === t.selectedValue) {\r
10040                                         m.items[o.id].setSelected(1);\r
10041                                         t.oldID = o.id;\r
10042                                 }\r
10043                         });\r
10044 \r
10045                         m.showMenu(0, e.clientHeight);\r
10046 \r
10047                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
10048                         DOM.addClass(t.id, t.classPrefix + 'Selected');\r
10049 \r
10050                         //DOM.get(t.id + '_text').focus();\r
10051                 },\r
10052 \r
10053                 hideMenu : function(e) {\r
10054                         var t = this;\r
10055 \r
10056                         if (t.menu && t.menu.isMenuVisible) {\r
10057                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
10058 \r
10059                                 // Prevent double toogles by canceling the mouse click event to the button\r
10060                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))\r
10061                                         return;\r
10062 \r
10063                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
10064                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
10065                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
10066                                         t.menu.hideMenu();\r
10067                                 }\r
10068                         }\r
10069                 },\r
10070 \r
10071                 renderMenu : function() {\r
10072                         var t = this, m;\r
10073 \r
10074                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
10075                                 menu_line : 1,\r
10076                                 'class' : t.classPrefix + 'Menu mceNoIcons',\r
10077                                 max_width : 150,\r
10078                                 max_height : 150\r
10079                         });\r
10080 \r
10081                         m.onHideMenu.add(function() {\r
10082                                 t.hideMenu();\r
10083                                 t.focus();\r
10084                         });\r
10085 \r
10086                         m.add({\r
10087                                 title : t.settings.title,\r
10088                                 'class' : 'mceMenuItemTitle',\r
10089                                 onclick : function() {\r
10090                                         if (t.settings.onselect('') !== false)\r
10091                                                 t.select(''); // Must be runned after\r
10092                                 }\r
10093                         });\r
10094 \r
10095                         each(t.items, function(o) {\r
10096                                 // No value then treat it as a title\r
10097                                 if (o.value === undefined) {\r
10098                                         m.add({\r
10099                                                 title : o.title,\r
10100                                                 role : "option",\r
10101                                                 'class' : 'mceMenuItemTitle',\r
10102                                                 onclick : function() {\r
10103                                                         if (t.settings.onselect('') !== false)\r
10104                                                                 t.select(''); // Must be runned after\r
10105                                                 }\r
10106                                         });\r
10107                                 } else {\r
10108                                         o.id = DOM.uniqueId();\r
10109                                         o.role= "option";\r
10110                                         o.onclick = function() {\r
10111                                                 if (t.settings.onselect(o.value) !== false)\r
10112                                                         t.select(o.value); // Must be runned after\r
10113                                         };\r
10114 \r
10115                                         m.add(o);\r
10116                                 }\r
10117                         });\r
10118 \r
10119                         t.onRenderMenu.dispatch(t, m);\r
10120                         t.menu = m;\r
10121                 },\r
10122 \r
10123                 postRender : function() {\r
10124                         var t = this, cp = t.classPrefix;\r
10125 \r
10126                         Event.add(t.id, 'click', t.showMenu, t);\r
10127                         Event.add(t.id, 'keydown', function(evt) {\r
10128                                 if (evt.keyCode == 32) { // Space\r
10129                                         t.showMenu(evt);\r
10130                                         Event.cancel(evt);\r
10131                                 }\r
10132                         });\r
10133                         Event.add(t.id, 'focus', function() {\r
10134                                 if (!t._focused) {\r
10135                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {\r
10136                                                 if (e.keyCode == 40) {\r
10137                                                         t.showMenu();\r
10138                                                         Event.cancel(e);\r
10139                                                 }\r
10140                                         });\r
10141                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {\r
10142                                                 var v;\r
10143                                                 if (e.keyCode == 13) {\r
10144                                                         // Fake select on enter\r
10145                                                         v = t.selectedValue;\r
10146                                                         t.selectedValue = null; // Needs to be null to fake change\r
10147                                                         Event.cancel(e);\r
10148                                                         t.settings.onselect(v);\r
10149                                                 }\r
10150                                         });\r
10151                                 }\r
10152 \r
10153                                 t._focused = 1;\r
10154                         });\r
10155                         Event.add(t.id, 'blur', function() {\r
10156                                 Event.remove(t.id, 'keydown', t.keyDownHandler);\r
10157                                 Event.remove(t.id, 'keypress', t.keyPressHandler);\r
10158                                 t._focused = 0;\r
10159                         });\r
10160 \r
10161                         // Old IE doesn't have hover on all elements\r
10162                         if (tinymce.isIE6 || !DOM.boxModel) {\r
10163                                 Event.add(t.id, 'mouseover', function() {\r
10164                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
10165                                                 DOM.addClass(t.id, cp + 'Hover');\r
10166                                 });\r
10167 \r
10168                                 Event.add(t.id, 'mouseout', function() {\r
10169                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
10170                                                 DOM.removeClass(t.id, cp + 'Hover');\r
10171                                 });\r
10172                         }\r
10173 \r
10174                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
10175                 },\r
10176 \r
10177                 destroy : function() {\r
10178                         this.parent();\r
10179 \r
10180                         Event.clear(this.id + '_text');\r
10181                         Event.clear(this.id + '_open');\r
10182                 }\r
10183         });\r
10184 })(tinymce);\r
10185 \r
10186 (function(tinymce) {\r
10187         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;\r
10188 \r
10189         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {\r
10190                 NativeListBox : function(id, s) {\r
10191                         this.parent(id, s);\r
10192                         this.classPrefix = 'mceNativeListBox';\r
10193                 },\r
10194 \r
10195                 setDisabled : function(s) {\r
10196                         DOM.get(this.id).disabled = s;\r
10197                         this.setAriaProperty('disabled', s);\r
10198                 },\r
10199 \r
10200                 isDisabled : function() {\r
10201                         return DOM.get(this.id).disabled;\r
10202                 },\r
10203 \r
10204                 select : function(va) {\r
10205                         var t = this, fv, f;\r
10206 \r
10207                         if (va == undefined)\r
10208                                 return t.selectByIndex(-1);\r
10209 \r
10210                         // Is string or number make function selector\r
10211                         if (va && typeof(va)=="function")\r
10212                                 f = va;\r
10213                         else {\r
10214                                 f = function(v) {\r
10215                                         return v == va;\r
10216                                 };\r
10217                         }\r
10218 \r
10219                         // Do we need to do something?\r
10220                         if (va != t.selectedValue) {\r
10221                                 // Find item\r
10222                                 each(t.items, function(o, i) {\r
10223                                         if (f(o.value)) {\r
10224                                                 fv = 1;\r
10225                                                 t.selectByIndex(i);\r
10226                                                 return false;\r
10227                                         }\r
10228                                 });\r
10229 \r
10230                                 if (!fv)\r
10231                                         t.selectByIndex(-1);\r
10232                         }\r
10233                 },\r
10234 \r
10235                 selectByIndex : function(idx) {\r
10236                         DOM.get(this.id).selectedIndex = idx + 1;\r
10237                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;\r
10238                 },\r
10239 \r
10240                 add : function(n, v, a) {\r
10241                         var o, t = this;\r
10242 \r
10243                         a = a || {};\r
10244                         a.value = v;\r
10245 \r
10246                         if (t.isRendered())\r
10247                                 DOM.add(DOM.get(this.id), 'option', a, n);\r
10248 \r
10249                         o = {\r
10250                                 title : n,\r
10251                                 value : v,\r
10252                                 attribs : a\r
10253                         };\r
10254 \r
10255                         t.items.push(o);\r
10256                         t.onAdd.dispatch(t, o);\r
10257                 },\r
10258 \r
10259                 getLength : function() {\r
10260                         return this.items.length;\r
10261                 },\r
10262 \r
10263                 renderHTML : function() {\r
10264                         var h, t = this;\r
10265 \r
10266                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');\r
10267 \r
10268                         each(t.items, function(it) {\r
10269                                 h += DOM.createHTML('option', {value : it.value}, it.title);\r
10270                         });\r
10271 \r
10272                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);\r
10273                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);\r
10274                         return h;\r
10275                 },\r
10276 \r
10277                 postRender : function() {\r
10278                         var t = this, ch, changeListenerAdded = true;\r
10279 \r
10280                         t.rendered = true;\r
10281 \r
10282                         function onChange(e) {\r
10283                                 var v = t.items[e.target.selectedIndex - 1];\r
10284 \r
10285                                 if (v && (v = v.value)) {\r
10286                                         t.onChange.dispatch(t, v);\r
10287 \r
10288                                         if (t.settings.onselect)\r
10289                                                 t.settings.onselect(v);\r
10290                                 }\r
10291                         };\r
10292 \r
10293                         Event.add(t.id, 'change', onChange);\r
10294 \r
10295                         // Accessibility keyhandler\r
10296                         Event.add(t.id, 'keydown', function(e) {\r
10297                                 var bf;\r
10298 \r
10299                                 Event.remove(t.id, 'change', ch);\r
10300                                 changeListenerAdded = false;\r
10301 \r
10302                                 bf = Event.add(t.id, 'blur', function() {\r
10303                                         if (changeListenerAdded) return;\r
10304                                         changeListenerAdded = true;\r
10305                                         Event.add(t.id, 'change', onChange);\r
10306                                         Event.remove(t.id, 'blur', bf);\r
10307                                 });\r
10308 \r
10309                                 //prevent default left and right keys on chrome - so that the keyboard navigation is used.\r
10310                                 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {\r
10311                                         return Event.prevent(e);\r
10312                                 }\r
10313                                 \r
10314                                 if (e.keyCode == 13 || e.keyCode == 32) {\r
10315                                         onChange(e);\r
10316                                         return Event.cancel(e);\r
10317                                 }\r
10318                         });\r
10319 \r
10320                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
10321                 }\r
10322         });\r
10323 })(tinymce);\r
10324 \r
10325 (function(tinymce) {\r
10326         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
10327 \r
10328         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {\r
10329                 MenuButton : function(id, s, ed) {\r
10330                         this.parent(id, s, ed);\r
10331 \r
10332                         this.onRenderMenu = new tinymce.util.Dispatcher(this);\r
10333 \r
10334                         s.menu_container = s.menu_container || DOM.doc.body;\r
10335                 },\r
10336 \r
10337                 showMenu : function() {\r
10338                         var t = this, p1, p2, e = DOM.get(t.id), m;\r
10339 \r
10340                         if (t.isDisabled())\r
10341                                 return;\r
10342 \r
10343                         if (!t.isMenuRendered) {\r
10344                                 t.renderMenu();\r
10345                                 t.isMenuRendered = true;\r
10346                         }\r
10347 \r
10348                         if (t.isMenuVisible)\r
10349                                 return t.hideMenu();\r
10350 \r
10351                         p1 = DOM.getPos(t.settings.menu_container);\r
10352                         p2 = DOM.getPos(e);\r
10353 \r
10354                         m = t.menu;\r
10355                         m.settings.offset_x = p2.x;\r
10356                         m.settings.offset_y = p2.y;\r
10357                         m.settings.vp_offset_x = p2.x;\r
10358                         m.settings.vp_offset_y = p2.y;\r
10359                         m.settings.keyboard_focus = t._focused;\r
10360                         m.showMenu(0, e.clientHeight);\r
10361 \r
10362                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
10363                         t.setState('Selected', 1);\r
10364 \r
10365                         t.isMenuVisible = 1;\r
10366                 },\r
10367 \r
10368                 renderMenu : function() {\r
10369                         var t = this, m;\r
10370 \r
10371                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
10372                                 menu_line : 1,\r
10373                                 'class' : this.classPrefix + 'Menu',\r
10374                                 icons : t.settings.icons\r
10375                         });\r
10376 \r
10377                         m.onHideMenu.add(function() {\r
10378                                 t.hideMenu();\r
10379                                 t.focus();\r
10380                         });\r
10381 \r
10382                         t.onRenderMenu.dispatch(t, m);\r
10383                         t.menu = m;\r
10384                 },\r
10385 \r
10386                 hideMenu : function(e) {\r
10387                         var t = this;\r
10388 \r
10389                         // Prevent double toogles by canceling the mouse click event to the button\r
10390                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))\r
10391                                 return;\r
10392 \r
10393                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
10394                                 t.setState('Selected', 0);\r
10395                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
10396                                 if (t.menu)\r
10397                                         t.menu.hideMenu();\r
10398                         }\r
10399 \r
10400                         t.isMenuVisible = 0;\r
10401                 },\r
10402 \r
10403                 postRender : function() {\r
10404                         var t = this, s = t.settings;\r
10405 \r
10406                         Event.add(t.id, 'click', function() {\r
10407                                 if (!t.isDisabled()) {\r
10408                                         if (s.onclick)\r
10409                                                 s.onclick(t.value);\r
10410 \r
10411                                         t.showMenu();\r
10412                                 }\r
10413                         });\r
10414                 }\r
10415         });\r
10416 })(tinymce);\r
10417 \r
10418 (function(tinymce) {\r
10419         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
10420 \r
10421         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {\r
10422                 SplitButton : function(id, s, ed) {\r
10423                         this.parent(id, s, ed);\r
10424                         this.classPrefix = 'mceSplitButton';\r
10425                 },\r
10426 \r
10427                 renderHTML : function() {\r
10428                         var h, t = this, s = t.settings, h1;\r
10429 \r
10430                         h = '<tbody><tr>';\r
10431 \r
10432                         if (s.image)\r
10433                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});\r
10434                         else\r
10435                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');\r
10436 \r
10437                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);\r
10438                         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>';\r
10439         \r
10440                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');\r
10441                         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>';\r
10442 \r
10443                         h += '</tr></tbody>';\r
10444                         h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);\r
10445                         return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);\r
10446                 },\r
10447 \r
10448                 postRender : function() {\r
10449                         var t = this, s = t.settings, activate;\r
10450 \r
10451                         if (s.onclick) {\r
10452                                 activate = function(evt) {\r
10453                                         if (!t.isDisabled()) {\r
10454                                                 s.onclick(t.value);\r
10455                                                 Event.cancel(evt);\r
10456                                         }\r
10457                                 };\r
10458                                 Event.add(t.id + '_action', 'click', activate);\r
10459                                 Event.add(t.id, ['click', 'keydown'], function(evt) {\r
10460                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;\r
10461                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {\r
10462                                                 activate();\r
10463                                                 Event.cancel(evt);\r
10464                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {\r
10465                                                 t.showMenu();\r
10466                                                 Event.cancel(evt);\r
10467                                         }\r
10468                                 });\r
10469                         }\r
10470 \r
10471                         Event.add(t.id + '_open', 'click', function (evt) {\r
10472                                 t.showMenu();\r
10473                                 Event.cancel(evt);\r
10474                         });\r
10475                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});\r
10476                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});\r
10477 \r
10478                         // Old IE doesn't have hover on all elements\r
10479                         if (tinymce.isIE6 || !DOM.boxModel) {\r
10480                                 Event.add(t.id, 'mouseover', function() {\r
10481                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
10482                                                 DOM.addClass(t.id, 'mceSplitButtonHover');\r
10483                                 });\r
10484 \r
10485                                 Event.add(t.id, 'mouseout', function() {\r
10486                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
10487                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');\r
10488                                 });\r
10489                         }\r
10490                 },\r
10491 \r
10492                 destroy : function() {\r
10493                         this.parent();\r
10494 \r
10495                         Event.clear(this.id + '_action');\r
10496                         Event.clear(this.id + '_open');\r
10497                         Event.clear(this.id);\r
10498                 }\r
10499         });\r
10500 })(tinymce);\r
10501 \r
10502 (function(tinymce) {\r
10503         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;\r
10504 \r
10505         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {\r
10506                 ColorSplitButton : function(id, s, ed) {\r
10507                         var t = this;\r
10508 \r
10509                         t.parent(id, s, ed);\r
10510 \r
10511                         t.settings = s = tinymce.extend({\r
10512                                 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',\r
10513                                 grid_width : 8,\r
10514                                 default_color : '#888888'\r
10515                         }, t.settings);\r
10516 \r
10517                         t.onShowMenu = new tinymce.util.Dispatcher(t);\r
10518 \r
10519                         t.onHideMenu = new tinymce.util.Dispatcher(t);\r
10520 \r
10521                         t.value = s.default_color;\r
10522                 },\r
10523 \r
10524                 showMenu : function() {\r
10525                         var t = this, r, p, e, p2;\r
10526 \r
10527                         if (t.isDisabled())\r
10528                                 return;\r
10529 \r
10530                         if (!t.isMenuRendered) {\r
10531                                 t.renderMenu();\r
10532                                 t.isMenuRendered = true;\r
10533                         }\r
10534 \r
10535                         if (t.isMenuVisible)\r
10536                                 return t.hideMenu();\r
10537 \r
10538                         e = DOM.get(t.id);\r
10539                         DOM.show(t.id + '_menu');\r
10540                         DOM.addClass(e, 'mceSplitButtonSelected');\r
10541                         p2 = DOM.getPos(e);\r
10542                         DOM.setStyles(t.id + '_menu', {\r
10543                                 left : p2.x,\r
10544                                 top : p2.y + e.clientHeight,\r
10545                                 zIndex : 200000\r
10546                         });\r
10547                         e = 0;\r
10548 \r
10549                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
10550                         t.onShowMenu.dispatch(t);\r
10551 \r
10552                         if (t._focused) {\r
10553                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {\r
10554                                         if (e.keyCode == 27)\r
10555                                                 t.hideMenu();\r
10556                                 });\r
10557 \r
10558                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link\r
10559                         }\r
10560 \r
10561                         t.isMenuVisible = 1;\r
10562                 },\r
10563 \r
10564                 hideMenu : function(e) {\r
10565                         var t = this;\r
10566 \r
10567                         if (t.isMenuVisible) {\r
10568                                 // Prevent double toogles by canceling the mouse click event to the button\r
10569                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))\r
10570                                         return;\r
10571 \r
10572                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {\r
10573                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');\r
10574                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
10575                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);\r
10576                                         DOM.hide(t.id + '_menu');\r
10577                                 }\r
10578 \r
10579                                 t.isMenuVisible = 0;\r
10580                                 t.onHideMenu.dispatch();\r
10581                         }\r
10582                 },\r
10583 \r
10584                 renderMenu : function() {\r
10585                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;\r
10586 \r
10587                         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;'});\r
10588                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});\r
10589                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});\r
10590 \r
10591                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});\r
10592                         tb = DOM.add(n, 'tbody');\r
10593 \r
10594                         // Generate color grid\r
10595                         i = 0;\r
10596                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {\r
10597                                 c = c.replace(/^#/, '');\r
10598 \r
10599                                 if (!i--) {\r
10600                                         tr = DOM.add(tb, 'tr');\r
10601                                         i = s.grid_width - 1;\r
10602                                 }\r
10603 \r
10604                                 n = DOM.add(tr, 'td');\r
10605                                 var settings = {\r
10606                                         href : 'javascript:;',\r
10607                                         style : {\r
10608                                                 backgroundColor : '#' + c\r
10609                                         },\r
10610                                         'title': t.editor.getLang('colors.' + c, c),\r
10611                                         'data-mce-color' : '#' + c\r
10612                                 };\r
10613 \r
10614                                 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.\r
10615                                 if (!tinymce.isIE ) {\r
10616                                         settings['role']= 'option';\r
10617                                 }\r
10618 \r
10619                                 n = DOM.add(n, 'a', settings);\r
10620 \r
10621                                 if (t.editor.forcedHighContrastMode) {\r
10622                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });\r
10623                                         if (n.getContext && (context = n.getContext("2d"))) {\r
10624                                                 context.fillStyle = '#' + c;\r
10625                                                 context.fillRect(0, 0, 16, 16);\r
10626                                         } else {\r
10627                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.\r
10628                                                 DOM.remove(n);\r
10629                                         }\r
10630                                 }\r
10631                         });\r
10632 \r
10633                         if (s.more_colors_func) {\r
10634                                 n = DOM.add(tb, 'tr');\r
10635                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});\r
10636                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);\r
10637 \r
10638                                 Event.add(n, 'click', function(e) {\r
10639                                         s.more_colors_func.call(s.more_colors_scope || this);\r
10640                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
10641                                 });\r
10642                         }\r
10643 \r
10644                         DOM.addClass(m, 'mceColorSplitMenu');\r
10645                         \r
10646                         new tinymce.ui.KeyboardNavigation({\r
10647                                 root: t.id + '_menu',\r
10648                                 items: DOM.select('a', t.id + '_menu'),\r
10649                                 onCancel: function() {\r
10650                                         t.hideMenu();\r
10651                                         t.focus();\r
10652                                 }\r
10653                         });\r
10654 \r
10655                         // Prevent IE from scrolling and hindering click to occur #4019\r
10656                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});\r
10657 \r
10658                         Event.add(t.id + '_menu', 'click', function(e) {\r
10659                                 var c;\r
10660 \r
10661                                 e = DOM.getParent(e.target, 'a', tb);\r
10662 \r
10663                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))\r
10664                                         t.setColor(c);\r
10665 \r
10666                                 return Event.cancel(e); // Prevent IE auto save warning\r
10667                         });\r
10668 \r
10669                         return w;\r
10670                 },\r
10671 \r
10672                 setColor : function(c) {\r
10673                         this.displayColor(c);\r
10674                         this.hideMenu();\r
10675                         this.settings.onselect(c);\r
10676                 },\r
10677                 \r
10678                 displayColor : function(c) {\r
10679                         var t = this;\r
10680 \r
10681                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);\r
10682 \r
10683                         t.value = c;\r
10684                 },\r
10685 \r
10686                 postRender : function() {\r
10687                         var t = this, id = t.id;\r
10688 \r
10689                         t.parent();\r
10690                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});\r
10691                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);\r
10692                 },\r
10693 \r
10694                 destroy : function() {\r
10695                         this.parent();\r
10696 \r
10697                         Event.clear(this.id + '_menu');\r
10698                         Event.clear(this.id + '_more');\r
10699                         DOM.remove(this.id + '_menu');\r
10700                 }\r
10701         });\r
10702 })(tinymce);\r
10703 \r
10704 (function(tinymce) {\r
10705 // Shorten class names\r
10706 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;\r
10707 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {\r
10708         renderHTML : function() {\r
10709                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;\r
10710 \r
10711                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');\r
10712                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.\r
10713                 h.push("<span role='application'>");\r
10714                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');\r
10715                 each(controls, function(toolbar) {\r
10716                         h.push(toolbar.renderHTML());\r
10717                 });\r
10718                 h.push("</span>");\r
10719                 h.push('</div>');\r
10720 \r
10721                 return h.join('');\r
10722         },\r
10723         \r
10724         focus : function() {\r
10725                 var t = this;\r
10726                 dom.get(t.id).focus();\r
10727         },\r
10728         \r
10729         postRender : function() {\r
10730                 var t = this, items = [];\r
10731 \r
10732                 each(t.controls, function(toolbar) {\r
10733                         each (toolbar.controls, function(control) {\r
10734                                 if (control.id) {\r
10735                                         items.push(control);\r
10736                                 }\r
10737                         });\r
10738                 });\r
10739 \r
10740                 t.keyNav = new tinymce.ui.KeyboardNavigation({\r
10741                         root: t.id,\r
10742                         items: items,\r
10743                         onCancel: function() {\r
10744                                 //Move focus if webkit so that navigation back will read the item.\r
10745                                 if (tinymce.isWebKit) {\r
10746                                         dom.get(t.editor.id+"_ifr").focus();\r
10747                                 }\r
10748                                 t.editor.focus();\r
10749                         },\r
10750                         excludeFromTabOrder: !t.settings.tab_focus_toolbar\r
10751                 });\r
10752         },\r
10753         \r
10754         destroy : function() {\r
10755                 var self = this;\r
10756 \r
10757                 self.parent();\r
10758                 self.keyNav.destroy();\r
10759                 Event.clear(self.id);\r
10760         }\r
10761 });\r
10762 })(tinymce);\r
10763 \r
10764 (function(tinymce) {\r
10765 // Shorten class names\r
10766 var dom = tinymce.DOM, each = tinymce.each;\r
10767 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {\r
10768         renderHTML : function() {\r
10769                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;\r
10770 \r
10771                 cl = t.controls;\r
10772                 for (i=0; i<cl.length; i++) {\r
10773                         // Get current control, prev control, next control and if the control is a list box or not\r
10774                         co = cl[i];\r
10775                         pr = cl[i - 1];\r
10776                         nx = cl[i + 1];\r
10777 \r
10778                         // Add toolbar start\r
10779                         if (i === 0) {\r
10780                                 c = 'mceToolbarStart';\r
10781 \r
10782                                 if (co.Button)\r
10783                                         c += ' mceToolbarStartButton';\r
10784                                 else if (co.SplitButton)\r
10785                                         c += ' mceToolbarStartSplitButton';\r
10786                                 else if (co.ListBox)\r
10787                                         c += ' mceToolbarStartListBox';\r
10788 \r
10789                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
10790                         }\r
10791 \r
10792                         // Add toolbar end before list box and after the previous button\r
10793                         // This is to fix the o2k7 editor skins\r
10794                         if (pr && co.ListBox) {\r
10795                                 if (pr.Button || pr.SplitButton)\r
10796                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));\r
10797                         }\r
10798 \r
10799                         // Render control HTML\r
10800 \r
10801                         // IE 8 quick fix, needed to propertly generate a hit area for anchors\r
10802                         if (dom.stdMode)\r
10803                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';\r
10804                         else\r
10805                                 h += '<td>' + co.renderHTML() + '</td>';\r
10806 \r
10807                         // Add toolbar start after list box and before the next button\r
10808                         // This is to fix the o2k7 editor skins\r
10809                         if (nx && co.ListBox) {\r
10810                                 if (nx.Button || nx.SplitButton)\r
10811                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));\r
10812                         }\r
10813                 }\r
10814 \r
10815                 c = 'mceToolbarEnd';\r
10816 \r
10817                 if (co.Button)\r
10818                         c += ' mceToolbarEndButton';\r
10819                 else if (co.SplitButton)\r
10820                         c += ' mceToolbarEndSplitButton';\r
10821                 else if (co.ListBox)\r
10822                         c += ' mceToolbarEndListBox';\r
10823 \r
10824                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
10825 \r
10826                 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>');\r
10827         }\r
10828 });\r
10829 })(tinymce);\r
10830 \r
10831 (function(tinymce) {\r
10832         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;\r
10833 \r
10834         tinymce.create('tinymce.AddOnManager', {\r
10835                 AddOnManager : function() {\r
10836                         var self = this;\r
10837 \r
10838                         self.items = [];\r
10839                         self.urls = {};\r
10840                         self.lookup = {};\r
10841                         self.onAdd = new Dispatcher(self);\r
10842                 },\r
10843 \r
10844                 get : function(n) {\r
10845                         if (this.lookup[n]) {\r
10846                                 return this.lookup[n].instance;\r
10847                         } else {\r
10848                                 return undefined;\r
10849                         }\r
10850                 },\r
10851 \r
10852                 dependencies : function(n) {\r
10853                         var result;\r
10854                         if (this.lookup[n]) {\r
10855                                 result = this.lookup[n].dependencies;\r
10856                         }\r
10857                         return result || [];\r
10858                 },\r
10859 \r
10860                 requireLangPack : function(n) {\r
10861                         var s = tinymce.settings;\r
10862 \r
10863                         if (s && s.language && s.language_load !== false)\r
10864                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');\r
10865                 },\r
10866 \r
10867                 add : function(id, o, dependencies) {\r
10868                         this.items.push(o);\r
10869                         this.lookup[id] = {instance:o, dependencies:dependencies};\r
10870                         this.onAdd.dispatch(this, id, o);\r
10871 \r
10872                         return o;\r
10873                 },\r
10874                 createUrl: function(baseUrl, dep) {\r
10875                         if (typeof dep === "object") {\r
10876                                 return dep\r
10877                         } else {\r
10878                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};\r
10879                         }\r
10880                 },\r
10881 \r
10882                 addComponents: function(pluginName, scripts) {\r
10883                         var pluginUrl = this.urls[pluginName];\r
10884                         tinymce.each(scripts, function(script){\r
10885                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); \r
10886                         });\r
10887                 },\r
10888 \r
10889                 load : function(n, u, cb, s) {\r
10890                         var t = this, url = u;\r
10891 \r
10892                         function loadDependencies() {\r
10893                                 var dependencies = t.dependencies(n);\r
10894                                 tinymce.each(dependencies, function(dep) {\r
10895                                         var newUrl = t.createUrl(u, dep);\r
10896                                         t.load(newUrl.resource, newUrl, undefined, undefined);\r
10897                                 });\r
10898                                 if (cb) {\r
10899                                         if (s) {\r
10900                                                 cb.call(s);\r
10901                                         } else {\r
10902                                                 cb.call(tinymce.ScriptLoader);\r
10903                                         }\r
10904                                 }\r
10905                         }\r
10906 \r
10907                         if (t.urls[n])\r
10908                                 return;\r
10909                         if (typeof u === "object")\r
10910                                 url = u.prefix + u.resource + u.suffix;\r
10911 \r
10912                         if (url.indexOf('/') != 0 && url.indexOf('://') == -1)\r
10913                                 url = tinymce.baseURL + '/' + url;\r
10914 \r
10915                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));\r
10916 \r
10917                         if (t.lookup[n]) {\r
10918                                 loadDependencies();\r
10919                         } else {\r
10920                                 tinymce.ScriptLoader.add(url, loadDependencies, s);\r
10921                         }\r
10922                 }\r
10923         });\r
10924 \r
10925         // Create plugin and theme managers\r
10926         tinymce.PluginManager = new tinymce.AddOnManager();\r
10927         tinymce.ThemeManager = new tinymce.AddOnManager();\r
10928 }(tinymce));\r
10929 \r
10930 (function(tinymce) {\r
10931         // Shorten names\r
10932         var each = tinymce.each, extend = tinymce.extend,\r
10933                 DOM = tinymce.DOM, Event = tinymce.dom.Event,\r
10934                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
10935                 explode = tinymce.explode,\r
10936                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;\r
10937 \r
10938         // Setup some URLs where the editor API is located and where the document is\r
10939         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');\r
10940         if (!/[\/\\]$/.test(tinymce.documentBaseURL))\r
10941                 tinymce.documentBaseURL += '/';\r
10942 \r
10943         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);\r
10944 \r
10945         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);\r
10946 \r
10947         // Add before unload listener\r
10948         // This was required since IE was leaking memory if you added and removed beforeunload listeners\r
10949         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event\r
10950         tinymce.onBeforeUnload = new Dispatcher(tinymce);\r
10951 \r
10952         // Must be on window or IE will leak if the editor is placed in frame or iframe\r
10953         Event.add(window, 'beforeunload', function(e) {\r
10954                 tinymce.onBeforeUnload.dispatch(tinymce, e);\r
10955         });\r
10956 \r
10957         tinymce.onAddEditor = new Dispatcher(tinymce);\r
10958 \r
10959         tinymce.onRemoveEditor = new Dispatcher(tinymce);\r
10960 \r
10961         tinymce.EditorManager = extend(tinymce, {\r
10962                 editors : [],\r
10963 \r
10964                 i18n : {},\r
10965 \r
10966                 activeEditor : null,\r
10967 \r
10968                 init : function(s) {\r
10969                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;\r
10970 \r
10971                         function execCallback(se, n, s) {\r
10972                                 var f = se[n];\r
10973 \r
10974                                 if (!f)\r
10975                                         return;\r
10976 \r
10977                                 if (tinymce.is(f, 'string')) {\r
10978                                         s = f.replace(/\.\w+$/, '');\r
10979                                         s = s ? tinymce.resolve(s) : 0;\r
10980                                         f = tinymce.resolve(f);\r
10981                                 }\r
10982 \r
10983                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));\r
10984                         };\r
10985 \r
10986                         s = extend({\r
10987                                 theme : "simple",\r
10988                                 language : "en"\r
10989                         }, s);\r
10990 \r
10991                         t.settings = s;\r
10992 \r
10993                         // Legacy call\r
10994                         Event.add(document, 'init', function() {\r
10995                                 var l, co;\r
10996 \r
10997                                 execCallback(s, 'onpageload');\r
10998 \r
10999                                 switch (s.mode) {\r
11000                                         case "exact":\r
11001                                                 l = s.elements || '';\r
11002 \r
11003                                                 if(l.length > 0) {\r
11004                                                         each(explode(l), function(v) {\r
11005                                                                 if (DOM.get(v)) {\r
11006                                                                         ed = new tinymce.Editor(v, s);\r
11007                                                                         el.push(ed);\r
11008                                                                         ed.render(1);\r
11009                                                                 } else {\r
11010                                                                         each(document.forms, function(f) {\r
11011                                                                                 each(f.elements, function(e) {\r
11012                                                                                         if (e.name === v) {\r
11013                                                                                                 v = 'mce_editor_' + instanceCounter++;\r
11014                                                                                                 DOM.setAttrib(e, 'id', v);\r
11015 \r
11016                                                                                                 ed = new tinymce.Editor(v, s);\r
11017                                                                                                 el.push(ed);\r
11018                                                                                                 ed.render(1);\r
11019                                                                                         }\r
11020                                                                                 });\r
11021                                                                         });\r
11022                                                                 }\r
11023                                                         });\r
11024                                                 }\r
11025                                                 break;\r
11026 \r
11027                                         case "textareas":\r
11028                                         case "specific_textareas":\r
11029                                                 function hasClass(n, c) {\r
11030                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);\r
11031                                                 };\r
11032 \r
11033                                                 each(DOM.select('textarea'), function(v) {\r
11034                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))\r
11035                                                                 return;\r
11036 \r
11037                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {\r
11038                                                                 // Can we use the name\r
11039                                                                 e = DOM.get(v.name);\r
11040                                                                 if (!v.id && !e)\r
11041                                                                         v.id = v.name;\r
11042 \r
11043                                                                 // Generate unique name if missing or already exists\r
11044                                                                 if (!v.id || t.get(v.id))\r
11045                                                                         v.id = DOM.uniqueId();\r
11046 \r
11047                                                                 ed = new tinymce.Editor(v.id, s);\r
11048                                                                 el.push(ed);\r
11049                                                                 ed.render(1);\r
11050                                                         }\r
11051                                                 });\r
11052                                                 break;\r
11053                                 }\r
11054 \r
11055                                 // Call onInit when all editors are initialized\r
11056                                 if (s.oninit) {\r
11057                                         l = co = 0;\r
11058 \r
11059                                         each(el, function(ed) {\r
11060                                                 co++;\r
11061 \r
11062                                                 if (!ed.initialized) {\r
11063                                                         // Wait for it\r
11064                                                         ed.onInit.add(function() {\r
11065                                                                 l++;\r
11066 \r
11067                                                                 // All done\r
11068                                                                 if (l == co)\r
11069                                                                         execCallback(s, 'oninit');\r
11070                                                         });\r
11071                                                 } else\r
11072                                                         l++;\r
11073 \r
11074                                                 // All done\r
11075                                                 if (l == co)\r
11076                                                         execCallback(s, 'oninit');                                      \r
11077                                         });\r
11078                                 }\r
11079                         });\r
11080                 },\r
11081 \r
11082                 get : function(id) {\r
11083                         if (id === undefined)\r
11084                                 return this.editors;\r
11085 \r
11086                         return this.editors[id];\r
11087                 },\r
11088 \r
11089                 getInstanceById : function(id) {\r
11090                         return this.get(id);\r
11091                 },\r
11092 \r
11093                 add : function(editor) {\r
11094                         var self = this, editors = self.editors;\r
11095 \r
11096                         // Add named and index editor instance\r
11097                         editors[editor.id] = editor;\r
11098                         editors.push(editor);\r
11099 \r
11100                         self._setActive(editor);\r
11101                         self.onAddEditor.dispatch(self, editor);\r
11102 \r
11103 \r
11104                         return editor;\r
11105                 },\r
11106 \r
11107                 remove : function(editor) {\r
11108                         var t = this, i, editors = t.editors;\r
11109 \r
11110                         // Not in the collection\r
11111                         if (!editors[editor.id])\r
11112                                 return null;\r
11113 \r
11114                         delete editors[editor.id];\r
11115 \r
11116                         for (i = 0; i < editors.length; i++) {\r
11117                                 if (editors[i] == editor) {\r
11118                                         editors.splice(i, 1);\r
11119                                         break;\r
11120                                 }\r
11121                         }\r
11122 \r
11123                         // Select another editor since the active one was removed\r
11124                         if (t.activeEditor == editor)\r
11125                                 t._setActive(editors[0]);\r
11126 \r
11127                         editor.destroy();\r
11128                         t.onRemoveEditor.dispatch(t, editor);\r
11129 \r
11130                         return editor;\r
11131                 },\r
11132 \r
11133                 execCommand : function(c, u, v) {\r
11134                         var t = this, ed = t.get(v), w;\r
11135 \r
11136                         // Manager commands\r
11137                         switch (c) {\r
11138                                 case "mceFocus":\r
11139                                         ed.focus();\r
11140                                         return true;\r
11141 \r
11142                                 case "mceAddEditor":\r
11143                                 case "mceAddControl":\r
11144                                         if (!t.get(v))\r
11145                                                 new tinymce.Editor(v, t.settings).render();\r
11146 \r
11147                                         return true;\r
11148 \r
11149                                 case "mceAddFrameControl":\r
11150                                         w = v.window;\r
11151 \r
11152                                         // Add tinyMCE global instance and tinymce namespace to specified window\r
11153                                         w.tinyMCE = tinyMCE;\r
11154                                         w.tinymce = tinymce;\r
11155 \r
11156                                         tinymce.DOM.doc = w.document;\r
11157                                         tinymce.DOM.win = w;\r
11158 \r
11159                                         ed = new tinymce.Editor(v.element_id, v);\r
11160                                         ed.render();\r
11161 \r
11162                                         // Fix IE memory leaks\r
11163                                         if (tinymce.isIE) {\r
11164                                                 function clr() {\r
11165                                                         ed.destroy();\r
11166                                                         w.detachEvent('onunload', clr);\r
11167                                                         w = w.tinyMCE = w.tinymce = null; // IE leak\r
11168                                                 };\r
11169 \r
11170                                                 w.attachEvent('onunload', clr);\r
11171                                         }\r
11172 \r
11173                                         v.page_window = null;\r
11174 \r
11175                                         return true;\r
11176 \r
11177                                 case "mceRemoveEditor":\r
11178                                 case "mceRemoveControl":\r
11179                                         if (ed)\r
11180                                                 ed.remove();\r
11181 \r
11182                                         return true;\r
11183 \r
11184                                 case 'mceToggleEditor':\r
11185                                         if (!ed) {\r
11186                                                 t.execCommand('mceAddControl', 0, v);\r
11187                                                 return true;\r
11188                                         }\r
11189 \r
11190                                         if (ed.isHidden())\r
11191                                                 ed.show();\r
11192                                         else\r
11193                                                 ed.hide();\r
11194 \r
11195                                         return true;\r
11196                         }\r
11197 \r
11198                         // Run command on active editor\r
11199                         if (t.activeEditor)\r
11200                                 return t.activeEditor.execCommand(c, u, v);\r
11201 \r
11202                         return false;\r
11203                 },\r
11204 \r
11205                 execInstanceCommand : function(id, c, u, v) {\r
11206                         var ed = this.get(id);\r
11207 \r
11208                         if (ed)\r
11209                                 return ed.execCommand(c, u, v);\r
11210 \r
11211                         return false;\r
11212                 },\r
11213 \r
11214                 triggerSave : function() {\r
11215                         each(this.editors, function(e) {\r
11216                                 e.save();\r
11217                         });\r
11218                 },\r
11219 \r
11220                 addI18n : function(p, o) {\r
11221                         var lo, i18n = this.i18n;\r
11222 \r
11223                         if (!tinymce.is(p, 'string')) {\r
11224                                 each(p, function(o, lc) {\r
11225                                         each(o, function(o, g) {\r
11226                                                 each(o, function(o, k) {\r
11227                                                         if (g === 'common')\r
11228                                                                 i18n[lc + '.' + k] = o;\r
11229                                                         else\r
11230                                                                 i18n[lc + '.' + g + '.' + k] = o;\r
11231                                                 });\r
11232                                         });\r
11233                                 });\r
11234                         } else {\r
11235                                 each(o, function(o, k) {\r
11236                                         i18n[p + '.' + k] = o;\r
11237                                 });\r
11238                         }\r
11239                 },\r
11240 \r
11241                 // Private methods\r
11242 \r
11243                 _setActive : function(editor) {\r
11244                         this.selectedInstance = this.activeEditor = editor;\r
11245                 }\r
11246         });\r
11247 })(tinymce);\r
11248 \r
11249 (function(tinymce) {\r
11250         // Shorten these names\r
11251         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,\r
11252                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,\r
11253                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,\r
11254                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
11255                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode, VK = tinymce.VK;\r
11256 \r
11257         tinymce.create('tinymce.Editor', {\r
11258                 Editor : function(id, s) {\r
11259                         var t = this;\r
11260 \r
11261                         t.id = t.editorId = id;\r
11262 \r
11263                         t.execCommands = {};\r
11264                         t.queryStateCommands = {};\r
11265                         t.queryValueCommands = {};\r
11266 \r
11267                         t.isNotDirty = false;\r
11268 \r
11269                         t.plugins = {};\r
11270 \r
11271                         // Add events to the editor\r
11272                         each([\r
11273                                 'onPreInit',\r
11274 \r
11275                                 'onBeforeRenderUI',\r
11276 \r
11277                                 'onPostRender',\r
11278 \r
11279                                 'onLoad',\r
11280 \r
11281                                 'onInit',\r
11282 \r
11283                                 'onRemove',\r
11284 \r
11285                                 'onActivate',\r
11286 \r
11287                                 'onDeactivate',\r
11288 \r
11289                                 'onClick',\r
11290 \r
11291                                 'onEvent',\r
11292 \r
11293                                 'onMouseUp',\r
11294 \r
11295                                 'onMouseDown',\r
11296 \r
11297                                 'onDblClick',\r
11298 \r
11299                                 'onKeyDown',\r
11300 \r
11301                                 'onKeyUp',\r
11302 \r
11303                                 'onKeyPress',\r
11304 \r
11305                                 'onContextMenu',\r
11306 \r
11307                                 'onSubmit',\r
11308 \r
11309                                 'onReset',\r
11310 \r
11311                                 'onPaste',\r
11312 \r
11313                                 'onPreProcess',\r
11314 \r
11315                                 'onPostProcess',\r
11316 \r
11317                                 'onBeforeSetContent',\r
11318 \r
11319                                 'onBeforeGetContent',\r
11320 \r
11321                                 'onSetContent',\r
11322 \r
11323                                 'onGetContent',\r
11324 \r
11325                                 'onLoadContent',\r
11326 \r
11327                                 'onSaveContent',\r
11328 \r
11329                                 'onNodeChange',\r
11330 \r
11331                                 'onChange',\r
11332 \r
11333                                 'onBeforeExecCommand',\r
11334 \r
11335                                 'onExecCommand',\r
11336 \r
11337                                 'onUndo',\r
11338 \r
11339                                 'onRedo',\r
11340 \r
11341                                 'onVisualAid',\r
11342 \r
11343                                 'onSetProgressState',\r
11344 \r
11345                                 'onSetAttrib'\r
11346                         ], function(e) {\r
11347                                 t[e] = new Dispatcher(t);\r
11348                         });\r
11349 \r
11350                         t.settings = s = extend({\r
11351                                 id : id,\r
11352                                 language : 'en',\r
11353                                 docs_language : 'en',\r
11354                                 theme : 'simple',\r
11355                                 skin : 'default',\r
11356                                 delta_width : 0,\r
11357                                 delta_height : 0,\r
11358                                 popup_css : '',\r
11359                                 plugins : '',\r
11360                                 document_base_url : tinymce.documentBaseURL,\r
11361                                 add_form_submit_trigger : 1,\r
11362                                 submit_patch : 1,\r
11363                                 add_unload_trigger : 1,\r
11364                                 convert_urls : 1,\r
11365                                 relative_urls : 1,\r
11366                                 remove_script_host : 1,\r
11367                                 table_inline_editing : 0,\r
11368                                 object_resizing : 1,\r
11369                                 cleanup : 1,\r
11370                                 accessibility_focus : 1,\r
11371                                 custom_shortcuts : 1,\r
11372                                 custom_undo_redo_keyboard_shortcuts : 1,\r
11373                                 custom_undo_redo_restore_selection : 1,\r
11374                                 custom_undo_redo : 1,\r
11375                                 doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll\r
11376                                 visual_table_class : 'mceItemTable',\r
11377                                 visual : 1,\r
11378                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',\r
11379                                 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\r
11380                                 apply_source_formatting : 1,\r
11381                                 directionality : 'ltr',\r
11382                                 forced_root_block : 'p',\r
11383                                 hidden_input : 1,\r
11384                                 padd_empty_editor : 1,\r
11385                                 render_ui : 1,\r
11386                                 init_theme : 1,\r
11387                                 force_p_newlines : 1,\r
11388                                 indentation : '30px',\r
11389                                 keep_styles : 1,\r
11390                                 fix_table_elements : 1,\r
11391                                 inline_styles : 1,\r
11392                                 convert_fonts_to_spans : true,\r
11393                                 indent : 'simple',\r
11394                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',\r
11395                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',\r
11396                                 validate : true,\r
11397                                 entity_encoding : 'named',\r
11398                                 url_converter : t.convertURL,\r
11399                                 url_converter_scope : t,\r
11400                                 ie7_compat : true\r
11401                         }, s);\r
11402 \r
11403                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {\r
11404                                 base_uri : tinyMCE.baseURI\r
11405                         });\r
11406 \r
11407                         t.baseURI = tinymce.baseURI;\r
11408 \r
11409                         t.contentCSS = [];\r
11410 \r
11411                         // Call setup\r
11412                         t.execCallback('setup', t);\r
11413                 },\r
11414 \r
11415                 render : function(nst) {\r
11416                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;\r
11417 \r
11418                         // Page is not loaded yet, wait for it\r
11419                         if (!Event.domLoaded) {\r
11420                                 Event.add(document, 'init', function() {\r
11421                                         t.render();\r
11422                                 });\r
11423                                 return;\r
11424                         }\r
11425 \r
11426                         tinyMCE.settings = s;\r
11427 \r
11428                         // Element not found, then skip initialization\r
11429                         if (!t.getElement())\r
11430                                 return;\r
11431 \r
11432                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff \r
11433                         // here since the browser says it has contentEditable support but there is no visible\r
11434                         // caret We will remove this check ones Apple implements full contentEditable support\r
11435                         if (tinymce.isIDevice && !tinymce.isIOS5)\r
11436                                 return;\r
11437 \r
11438                         // Add hidden input for non input elements inside form elements\r
11439                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))\r
11440                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);\r
11441 \r
11442                         if (tinymce.WindowManager)\r
11443                                 t.windowManager = new tinymce.WindowManager(t);\r
11444 \r
11445                         if (s.encoding == 'xml') {\r
11446                                 t.onGetContent.add(function(ed, o) {\r
11447                                         if (o.save)\r
11448                                                 o.content = DOM.encode(o.content);\r
11449                                 });\r
11450                         }\r
11451 \r
11452                         if (s.add_form_submit_trigger) {\r
11453                                 t.onSubmit.addToTop(function() {\r
11454                                         if (t.initialized) {\r
11455                                                 t.save();\r
11456                                                 t.isNotDirty = 1;\r
11457                                         }\r
11458                                 });\r
11459                         }\r
11460 \r
11461                         if (s.add_unload_trigger) {\r
11462                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {\r
11463                                         if (t.initialized && !t.destroyed && !t.isHidden())\r
11464                                                 t.save({format : 'raw', no_events : true});\r
11465                                 });\r
11466                         }\r
11467 \r
11468                         tinymce.addUnload(t.destroy, t);\r
11469 \r
11470                         if (s.submit_patch) {\r
11471                                 t.onBeforeRenderUI.add(function() {\r
11472                                         var n = t.getElement().form;\r
11473 \r
11474                                         if (!n)\r
11475                                                 return;\r
11476 \r
11477                                         // Already patched\r
11478                                         if (n._mceOldSubmit)\r
11479                                                 return;\r
11480 \r
11481                                         // Check page uses id="submit" or name="submit" for it's submit button\r
11482                                         if (!n.submit.nodeType && !n.submit.length) {\r
11483                                                 t.formElement = n;\r
11484                                                 n._mceOldSubmit = n.submit;\r
11485                                                 n.submit = function() {\r
11486                                                         // Save all instances\r
11487                                                         tinymce.triggerSave();\r
11488                                                         t.isNotDirty = 1;\r
11489 \r
11490                                                         return t.formElement._mceOldSubmit(t.formElement);\r
11491                                                 };\r
11492                                         }\r
11493 \r
11494                                         n = null;\r
11495                                 });\r
11496                         }\r
11497 \r
11498                         // Load scripts\r
11499                         function loadScripts() {\r
11500                                 if (s.language && s.language_load !== false)\r
11501                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');\r
11502 \r
11503                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])\r
11504                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');\r
11505 \r
11506                                 each(explode(s.plugins), function(p) {\r
11507                                         if (p &&!PluginManager.urls[p]) {\r
11508                                                 if (p.charAt(0) == '-') {\r
11509                                                         p = p.substr(1, p.length);\r
11510                                                         var dependencies = PluginManager.dependencies(p);\r
11511                                                         each(dependencies, function(dep) {\r
11512                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};\r
11513                                                                 var dep = PluginManager.createUrl(defaultSettings, dep);\r
11514                                                                 PluginManager.load(dep.resource, dep);\r
11515                                                                 \r
11516                                                         });\r
11517                                                 } else {\r
11518                                                         // Skip safari plugin, since it is removed as of 3.3b1\r
11519                                                         if (p == 'safari') {\r
11520                                                                 return;\r
11521                                                         }\r
11522                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});\r
11523                                                 }\r
11524                                         }\r
11525                                 });\r
11526 \r
11527                                 // Init when que is loaded\r
11528                                 sl.loadQueue(function() {\r
11529                                         if (!t.removed)\r
11530                                                 t.init();\r
11531                                 });\r
11532                         };\r
11533 \r
11534                         loadScripts();\r
11535                 },\r
11536 \r
11537                 init : function() {\r
11538                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];\r
11539 \r
11540                         tinymce.add(t);\r
11541 \r
11542                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));\r
11543 \r
11544                         if (s.theme) {\r
11545                                 s.theme = s.theme.replace(/-/, '');\r
11546                                 o = ThemeManager.get(s.theme);\r
11547                                 t.theme = new o();\r
11548 \r
11549                                 if (t.theme.init && s.init_theme)\r
11550                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));\r
11551                         }\r
11552                         function initPlugin(p) {\r
11553                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;\r
11554                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {\r
11555                                         each(PluginManager.dependencies(p), function(dep){\r
11556                                                 initPlugin(dep);\r
11557                                         });\r
11558                                         po = new c(t, u);\r
11559 \r
11560                                         t.plugins[p] = po;\r
11561 \r
11562                                         if (po.init) {\r
11563                                                 po.init(t, u);\r
11564                                                 initializedPlugins.push(p);\r
11565                                         }\r
11566                                 }\r
11567                         }\r
11568                         \r
11569                         // Create all plugins\r
11570                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);\r
11571 \r
11572                         // Setup popup CSS path(s)\r
11573                         if (s.popup_css !== false) {\r
11574                                 if (s.popup_css)\r
11575                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);\r
11576                                 else\r
11577                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");\r
11578                         }\r
11579 \r
11580                         if (s.popup_css_add)\r
11581                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);\r
11582 \r
11583                         t.controlManager = new tinymce.ControlManager(t);\r
11584 \r
11585                         if (s.custom_undo_redo) {\r
11586                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {\r
11587                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
11588                                                 t.undoManager.beforeChange();\r
11589                                 });\r
11590 \r
11591                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {\r
11592                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
11593                                                 t.undoManager.add();\r
11594                                 });\r
11595                         }\r
11596 \r
11597                         t.onExecCommand.add(function(ed, c) {\r
11598                                 // Don't refresh the select lists until caret move\r
11599                                 if (!/^(FontName|FontSize)$/.test(c))\r
11600                                         t.nodeChanged();\r
11601                         });\r
11602 \r
11603                         // Remove ghost selections on images and tables in Gecko\r
11604                         if (isGecko) {\r
11605                                 function repaint(a, o) {\r
11606                                         if (!o || !o.initial)\r
11607                                                 t.execCommand('mceRepaint');\r
11608                                 };\r
11609 \r
11610                                 t.onUndo.add(repaint);\r
11611                                 t.onRedo.add(repaint);\r
11612                                 t.onSetContent.add(repaint);\r
11613                         }\r
11614 \r
11615                         // Enables users to override the control factory\r
11616                         t.onBeforeRenderUI.dispatch(t, t.controlManager);\r
11617 \r
11618                         // Measure box\r
11619                         if (s.render_ui) {\r
11620                                 w = s.width || e.style.width || e.offsetWidth;\r
11621                                 h = s.height || e.style.height || e.offsetHeight;\r
11622                                 t.orgDisplay = e.style.display;\r
11623                                 re = /^[0-9\.]+(|px)$/i;\r
11624 \r
11625                                 if (re.test('' + w))\r
11626                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);\r
11627 \r
11628                                 if (re.test('' + h))\r
11629                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);\r
11630 \r
11631                                 // Render UI\r
11632                                 o = t.theme.renderUI({\r
11633                                         targetNode : e,\r
11634                                         width : w,\r
11635                                         height : h,\r
11636                                         deltaWidth : s.delta_width,\r
11637                                         deltaHeight : s.delta_height\r
11638                                 });\r
11639 \r
11640                                 t.editorContainer = o.editorContainer;\r
11641                         }\r
11642 \r
11643 \r
11644                         // User specified a document.domain value\r
11645                         if (document.domain && location.hostname != document.domain)\r
11646                                 tinymce.relaxedDomain = document.domain;\r
11647 \r
11648                         // Resize editor\r
11649                         DOM.setStyles(o.sizeContainer || o.editorContainer, {\r
11650                                 width : w,\r
11651                                 height : h\r
11652                         });\r
11653 \r
11654                         // Load specified content CSS last\r
11655                         if (s.content_css) {\r
11656                                 tinymce.each(explode(s.content_css), function(u) {\r
11657                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));\r
11658                                 });\r
11659                         }\r
11660 \r
11661                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');\r
11662                         if (h < 100)\r
11663                                 h = 100;\r
11664 \r
11665                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';\r
11666 \r
11667                         // We only need to override paths if we have to\r
11668                         // IE has a bug where it remove site absolute urls to relative ones if this is specified\r
11669                         if (s.document_base_url != tinymce.documentBaseURL)\r
11670                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';\r
11671 \r
11672                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.\r
11673                         if (s.ie7_compat)\r
11674                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';\r
11675                         else\r
11676                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';\r
11677 \r
11678                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';\r
11679 \r
11680                         // Load the CSS by injecting them into the HTML this will reduce "flicker"\r
11681                         for (i = 0; i < t.contentCSS.length; i++) {\r
11682                                 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';\r
11683                         }\r
11684 \r
11685                         t.contentCSS = [];\r
11686 \r
11687                         bi = s.body_id || 'tinymce';\r
11688                         if (bi.indexOf('=') != -1) {\r
11689                                 bi = t.getParam('body_id', '', 'hash');\r
11690                                 bi = bi[t.id] || bi;\r
11691                         }\r
11692 \r
11693                         bc = s.body_class || '';\r
11694                         if (bc.indexOf('=') != -1) {\r
11695                                 bc = t.getParam('body_class', '', 'hash');\r
11696                                 bc = bc[t.id] || '';\r
11697                         }\r
11698 \r
11699                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';\r
11700 \r
11701                         // Domain relaxing enabled, then set document domain\r
11702                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {\r
11703                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button\r
11704                                 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';\r
11705                         }\r
11706 \r
11707                         // Create iframe\r
11708                         // TODO: ACC add the appropriate description on this.\r
11709                         n = DOM.add(o.iframeContainer, 'iframe', { \r
11710                                 id : t.id + "_ifr",\r
11711                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7\r
11712                                 frameBorder : '0',\r
11713                                 allowTransparency : "true",\r
11714                                 title : s.aria_label,\r
11715                                 style : {\r
11716                                         width : '100%',\r
11717                                         height : h,\r
11718                                         display : 'block' // Important for Gecko to render the iframe correctly\r
11719                                 }\r
11720                         });\r
11721 \r
11722                         t.contentAreaContainer = o.iframeContainer;\r
11723                         DOM.get(o.editorContainer).style.display = t.orgDisplay;\r
11724                         DOM.get(t.id).style.display = 'none';\r
11725                         DOM.setAttrib(t.id, 'aria-hidden', true);\r
11726 \r
11727                         if (!tinymce.relaxedDomain || !u)\r
11728                                 t.setupIframe();\r
11729 \r
11730                         e = n = o = null; // Cleanup\r
11731                 },\r
11732 \r
11733                 setupIframe : function() {\r
11734                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;\r
11735 \r
11736                         // Setup iframe body\r
11737                         if (!isIE || !tinymce.relaxedDomain) {\r
11738                                 d.open();\r
11739                                 d.write(t.iframeHTML);\r
11740                                 d.close();\r
11741 \r
11742                                 if (tinymce.relaxedDomain)\r
11743                                         d.domain = tinymce.relaxedDomain;\r
11744                         }\r
11745 \r
11746                         // It will not steal focus while setting contentEditable\r
11747                         b = t.getBody();\r
11748                         b.disabled = true;\r
11749 \r
11750                         if (!s.readonly)\r
11751                                 b.contentEditable = true;\r
11752 \r
11753                         b.disabled = false;\r
11754 \r
11755                         t.schema = new tinymce.html.Schema(s);\r
11756 \r
11757                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {\r
11758                                 keep_values : true,\r
11759                                 url_converter : t.convertURL,\r
11760                                 url_converter_scope : t,\r
11761                                 hex_colors : s.force_hex_style_colors,\r
11762                                 class_filter : s.class_filter,\r
11763                                 update_styles : 1,\r
11764                                 fix_ie_paragraphs : 1,\r
11765                                 schema : t.schema\r
11766                         });\r
11767 \r
11768                         t.parser = new tinymce.html.DomParser(s, t.schema);\r
11769 \r
11770                         // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.\r
11771                         if (!t.settings.allow_html_in_named_anchor) {\r
11772                                 t.parser.addAttributeFilter('name', function(nodes, name) {\r
11773                                         var i = nodes.length, sibling, prevSibling, parent, node;\r
11774         \r
11775                                         while (i--) {\r
11776                                                 node = nodes[i];\r
11777                                                 if (node.name === 'a' && node.firstChild) {\r
11778                                                         parent = node.parent;\r
11779         \r
11780                                                         // Move children after current node\r
11781                                                         sibling = node.lastChild;\r
11782                                                         do {\r
11783                                                                 prevSibling = sibling.prev;\r
11784                                                                 parent.insert(sibling, node);\r
11785                                                                 sibling = prevSibling;\r
11786                                                         } while (sibling);\r
11787                                                 }\r
11788                                         }\r
11789                                 });\r
11790                         }\r
11791 \r
11792                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style\r
11793                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {\r
11794                                 var i = nodes.length, node, dom = t.dom, value, internalName;\r
11795 \r
11796                                 while (i--) {\r
11797                                         node = nodes[i];\r
11798                                         value = node.attr(name);\r
11799                                         internalName = 'data-mce-' + name;\r
11800 \r
11801                                         // Add internal attribute if we need to we don't on a refresh of the document\r
11802                                         if (!node.attributes.map[internalName]) {       \r
11803                                                 if (name === "style")\r
11804                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));\r
11805                                                 else\r
11806                                                         node.attr(internalName, t.convertURL(value, name, node.name));\r
11807                                         }\r
11808                                 }\r
11809                         });\r
11810 \r
11811                         // Keep scripts from executing\r
11812                         t.parser.addNodeFilter('script', function(nodes, name) {\r
11813                                 var i = nodes.length, node;\r
11814 \r
11815                                 while (i--) {\r
11816                                         node = nodes[i];\r
11817                                         node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));\r
11818                                 }\r
11819                         });\r
11820 \r
11821                         t.parser.addNodeFilter('#cdata', function(nodes, name) {\r
11822                                 var i = nodes.length, node;\r
11823 \r
11824                                 while (i--) {\r
11825                                         node = nodes[i];\r
11826                                         node.type = 8;\r
11827                                         node.name = '#comment';\r
11828                                         node.value = '[CDATA[' + node.value + ']]';\r
11829                                 }\r
11830                         });\r
11831 \r
11832                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {\r
11833                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();\r
11834 \r
11835                                 while (i--) {\r
11836                                         node = nodes[i];\r
11837 \r
11838                                         if (node.isEmpty(nonEmptyElements))\r
11839                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;\r
11840                                 }\r
11841                         });\r
11842 \r
11843                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);\r
11844 \r
11845                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);\r
11846 \r
11847                         t.formatter = new tinymce.Formatter(this);\r
11848 \r
11849                         // Register default formats\r
11850                         t.formatter.register({\r
11851                                 alignleft : [\r
11852                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},\r
11853                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}\r
11854                                 ],\r
11855 \r
11856                                 aligncenter : [\r
11857                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},\r
11858                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},\r
11859                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}\r
11860                                 ],\r
11861 \r
11862                                 alignright : [\r
11863                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},\r
11864                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}\r
11865                                 ],\r
11866 \r
11867                                 alignfull : [\r
11868                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}\r
11869                                 ],\r
11870 \r
11871                                 bold : [\r
11872                                         {inline : 'strong', remove : 'all'},\r
11873                                         {inline : 'span', styles : {fontWeight : 'bold'}},\r
11874                                         {inline : 'b', remove : 'all'}\r
11875                                 ],\r
11876 \r
11877                                 italic : [\r
11878                                         {inline : 'em', remove : 'all'},\r
11879                                         {inline : 'span', styles : {fontStyle : 'italic'}},\r
11880                                         {inline : 'i', remove : 'all'}\r
11881                                 ],\r
11882 \r
11883                                 underline : [\r
11884                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},\r
11885                                         {inline : 'u', remove : 'all'}\r
11886                                 ],\r
11887 \r
11888                                 strikethrough : [\r
11889                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},\r
11890                                         {inline : 'strike', remove : 'all'}\r
11891                                 ],\r
11892 \r
11893                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},\r
11894                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},\r
11895                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},\r
11896                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},\r
11897                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},\r
11898                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},\r
11899                                 subscript : {inline : 'sub'},\r
11900                                 superscript : {inline : 'sup'},\r
11901 \r
11902                                 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,\r
11903                                         onmatch : function(node) {\r
11904                                                 return true;\r
11905                                         },\r
11906 \r
11907                                         onformat : function(elm, fmt, vars) {\r
11908                                                 each(vars, function(value, key) {\r
11909                                                         t.dom.setAttrib(elm, key, value);\r
11910                                                 });\r
11911                                         }\r
11912                                 },\r
11913 \r
11914                                 removeformat : [\r
11915                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},\r
11916                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},\r
11917                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}\r
11918                                 ]\r
11919                         });\r
11920 \r
11921                         // Register default block formats\r
11922                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {\r
11923                                 t.formatter.register(name, {block : name, remove : 'all'});\r
11924                         });\r
11925 \r
11926                         // Register user defined formats\r
11927                         t.formatter.register(t.settings.formats);\r
11928 \r
11929                         t.undoManager = new tinymce.UndoManager(t);\r
11930 \r
11931                         // Pass through\r
11932                         t.undoManager.onAdd.add(function(um, l) {\r
11933                                 if (um.hasUndo())\r
11934                                         return t.onChange.dispatch(t, l, um);\r
11935                         });\r
11936 \r
11937                         t.undoManager.onUndo.add(function(um, l) {\r
11938                                 return t.onUndo.dispatch(t, l, um);\r
11939                         });\r
11940 \r
11941                         t.undoManager.onRedo.add(function(um, l) {\r
11942                                 return t.onRedo.dispatch(t, l, um);\r
11943                         });\r
11944 \r
11945                         t.forceBlocks = new tinymce.ForceBlocks(t, {\r
11946                                 forced_root_block : s.forced_root_block\r
11947                         });\r
11948 \r
11949                         t.editorCommands = new tinymce.EditorCommands(t);\r
11950 \r
11951                         // Pass through\r
11952                         t.serializer.onPreProcess.add(function(se, o) {\r
11953                                 return t.onPreProcess.dispatch(t, o, se);\r
11954                         });\r
11955 \r
11956                         t.serializer.onPostProcess.add(function(se, o) {\r
11957                                 return t.onPostProcess.dispatch(t, o, se);\r
11958                         });\r
11959 \r
11960                         t.onPreInit.dispatch(t);\r
11961 \r
11962                         if (!s.gecko_spellcheck)\r
11963                                 t.getBody().spellcheck = 0;\r
11964 \r
11965                         if (!s.readonly)\r
11966                                 t._addEvents();\r
11967 \r
11968                         t.controlManager.onPostRender.dispatch(t, t.controlManager);\r
11969                         t.onPostRender.dispatch(t);\r
11970 \r
11971                         t.quirks = new tinymce.util.Quirks(this);\r
11972 \r
11973                         if (s.directionality)\r
11974                                 t.getBody().dir = s.directionality;\r
11975 \r
11976                         if (s.nowrap)\r
11977                                 t.getBody().style.whiteSpace = "nowrap";\r
11978 \r
11979                         if (s.handle_node_change_callback) {\r
11980                                 t.onNodeChange.add(function(ed, cm, n) {\r
11981                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());\r
11982                                 });\r
11983                         }\r
11984 \r
11985                         if (s.save_callback) {\r
11986                                 t.onSaveContent.add(function(ed, o) {\r
11987                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
11988 \r
11989                                         if (h)\r
11990                                                 o.content = h;\r
11991                                 });\r
11992                         }\r
11993 \r
11994                         if (s.onchange_callback) {\r
11995                                 t.onChange.add(function(ed, l) {\r
11996                                         t.execCallback('onchange_callback', t, l);\r
11997                                 });\r
11998                         }\r
11999 \r
12000                         if (s.protect) {\r
12001                                 t.onBeforeSetContent.add(function(ed, o) {\r
12002                                         if (s.protect) {\r
12003                                                 each(s.protect, function(pattern) {\r
12004                                                         o.content = o.content.replace(pattern, function(str) {\r
12005                                                                 return '<!--mce:protected ' + escape(str) + '-->';\r
12006                                                         });\r
12007                                                 });\r
12008                                         }\r
12009                                 });\r
12010                         }\r
12011 \r
12012                         if (s.convert_newlines_to_brs) {\r
12013                                 t.onBeforeSetContent.add(function(ed, o) {\r
12014                                         if (o.initial)\r
12015                                                 o.content = o.content.replace(/\r?\n/g, '<br />');\r
12016                                 });\r
12017                         }\r
12018 \r
12019                         if (s.preformatted) {\r
12020                                 t.onPostProcess.add(function(ed, o) {\r
12021                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');\r
12022                                         o.content = o.content.replace(/<\/pre>\s*$/, '');\r
12023 \r
12024                                         if (o.set)\r
12025                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';\r
12026                                 });\r
12027                         }\r
12028 \r
12029                         if (s.verify_css_classes) {\r
12030                                 t.serializer.attribValueFilter = function(n, v) {\r
12031                                         var s, cl;\r
12032 \r
12033                                         if (n == 'class') {\r
12034                                                 // Build regexp for classes\r
12035                                                 if (!t.classesRE) {\r
12036                                                         cl = t.dom.getClasses();\r
12037 \r
12038                                                         if (cl.length > 0) {\r
12039                                                                 s = '';\r
12040 \r
12041                                                                 each (cl, function(o) {\r
12042                                                                         s += (s ? '|' : '') + o['class'];\r
12043                                                                 });\r
12044 \r
12045                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');\r
12046                                                         }\r
12047                                                 }\r
12048 \r
12049                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';\r
12050                                         }\r
12051 \r
12052                                         return v;\r
12053                                 };\r
12054                         }\r
12055 \r
12056                         if (s.cleanup_callback) {\r
12057                                 t.onBeforeSetContent.add(function(ed, o) {\r
12058                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
12059                                 });\r
12060 \r
12061                                 t.onPreProcess.add(function(ed, o) {\r
12062                                         if (o.set)\r
12063                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);\r
12064 \r
12065                                         if (o.get)\r
12066                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);\r
12067                                 });\r
12068 \r
12069                                 t.onPostProcess.add(function(ed, o) {\r
12070                                         if (o.set)\r
12071                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
12072 \r
12073                                         if (o.get)                                              \r
12074                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);\r
12075                                 });\r
12076                         }\r
12077 \r
12078                         if (s.save_callback) {\r
12079                                 t.onGetContent.add(function(ed, o) {\r
12080                                         if (o.save)\r
12081                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
12082                                 });\r
12083                         }\r
12084 \r
12085                         if (s.handle_event_callback) {\r
12086                                 t.onEvent.add(function(ed, e, o) {\r
12087                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)\r
12088                                                 Event.cancel(e);\r
12089                                 });\r
12090                         }\r
12091 \r
12092                         // Add visual aids when new contents is added\r
12093                         t.onSetContent.add(function() {\r
12094                                 t.addVisual(t.getBody());\r
12095                         });\r
12096 \r
12097                         // Remove empty contents\r
12098                         if (s.padd_empty_editor) {\r
12099                                 t.onPostProcess.add(function(ed, o) {\r
12100                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');\r
12101                                 });\r
12102                         }\r
12103 \r
12104                         if (isGecko) {\r
12105                                 // Fix gecko link bug, when a link is placed at the end of block elements there is\r
12106                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link\r
12107                                 function fixLinks(ed, o) {\r
12108                                         each(ed.dom.select('a'), function(n) {\r
12109                                                 var pn = n.parentNode;\r
12110 \r
12111                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)\r
12112                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});\r
12113                                         });\r
12114                                 };\r
12115 \r
12116                                 t.onExecCommand.add(function(ed, cmd) {\r
12117                                         if (cmd === 'CreateLink')\r
12118                                                 fixLinks(ed);\r
12119                                 });\r
12120 \r
12121                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));\r
12122                         }\r
12123 \r
12124                         t.load({initial : true, format : 'html'});\r
12125                         t.startContent = t.getContent({format : 'raw'});\r
12126                         t.undoManager.add();\r
12127                         t.initialized = true;\r
12128 \r
12129                         t.onInit.dispatch(t);\r
12130                         t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());\r
12131                         t.execCallback('init_instance_callback', t);\r
12132                         t.focus(true);\r
12133                         t.nodeChanged({initial : 1});\r
12134 \r
12135                         // Load specified content CSS last\r
12136                         each(t.contentCSS, function(u) {\r
12137                                 t.dom.loadCSS(u);\r
12138                         });\r
12139 \r
12140                         // Handle auto focus\r
12141                         if (s.auto_focus) {\r
12142                                 setTimeout(function () {\r
12143                                         var ed = tinymce.get(s.auto_focus);\r
12144 \r
12145                                         ed.selection.select(ed.getBody(), 1);\r
12146                                         ed.selection.collapse(1);\r
12147                                         ed.getBody().focus();\r
12148                                         ed.getWin().focus();\r
12149                                 }, 100);\r
12150                         }\r
12151 \r
12152                         e = null;\r
12153                 },\r
12154 \r
12155 \r
12156                 focus : function(sf) {\r
12157                         var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();\r
12158 \r
12159                         if (!sf) {\r
12160                                 // Get selected control element\r
12161                                 ieRng = selection.getRng();\r
12162                                 if (ieRng.item) {\r
12163                                         controlElm = ieRng.item(0);\r
12164                                 }\r
12165 \r
12166                                 t._refreshContentEditable();\r
12167 \r
12168                                 // Is not content editable\r
12169                                 if (!ce)\r
12170                                         t.getWin().focus();\r
12171 \r
12172                                 // Focus the body as well since it's contentEditable\r
12173                                 if (tinymce.isGecko) {\r
12174                                         t.getBody().focus();\r
12175                                 }\r
12176 \r
12177                                 // Restore selected control element\r
12178                                 // This is needed when for example an image is selected within a\r
12179                                 // layer a call to focus will then remove the control selection\r
12180                                 if (controlElm && controlElm.ownerDocument == doc) {\r
12181                                         ieRng = doc.body.createControlRange();\r
12182                                         ieRng.addElement(controlElm);\r
12183                                         ieRng.select();\r
12184                                 }\r
12185 \r
12186                         }\r
12187 \r
12188                         if (tinymce.activeEditor != t) {\r
12189                                 if ((oed = tinymce.activeEditor) != null)\r
12190                                         oed.onDeactivate.dispatch(oed, t);\r
12191 \r
12192                                 t.onActivate.dispatch(t, oed);\r
12193                         }\r
12194 \r
12195                         tinymce._setActive(t);\r
12196                 },\r
12197 \r
12198                 execCallback : function(n) {\r
12199                         var t = this, f = t.settings[n], s;\r
12200 \r
12201                         if (!f)\r
12202                                 return;\r
12203 \r
12204                         // Look through lookup\r
12205                         if (t.callbackLookup && (s = t.callbackLookup[n])) {\r
12206                                 f = s.func;\r
12207                                 s = s.scope;\r
12208                         }\r
12209 \r
12210                         if (is(f, 'string')) {\r
12211                                 s = f.replace(/\.\w+$/, '');\r
12212                                 s = s ? tinymce.resolve(s) : 0;\r
12213                                 f = tinymce.resolve(f);\r
12214                                 t.callbackLookup = t.callbackLookup || {};\r
12215                                 t.callbackLookup[n] = {func : f, scope : s};\r
12216                         }\r
12217 \r
12218                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));\r
12219                 },\r
12220 \r
12221                 translate : function(s) {\r
12222                         var c = this.settings.language || 'en', i18n = tinymce.i18n;\r
12223 \r
12224                         if (!s)\r
12225                                 return '';\r
12226 \r
12227                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {\r
12228                                 return i18n[c + '.' + b] || '{#' + b + '}';\r
12229                         });\r
12230                 },\r
12231 \r
12232                 getLang : function(n, dv) {\r
12233                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');\r
12234                 },\r
12235 \r
12236                 getParam : function(n, dv, ty) {\r
12237                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;\r
12238 \r
12239                         if (ty === 'hash') {\r
12240                                 o = {};\r
12241 \r
12242                                 if (is(v, 'string')) {\r
12243                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {\r
12244                                                 v = v.split('=');\r
12245 \r
12246                                                 if (v.length > 1)\r
12247                                                         o[tr(v[0])] = tr(v[1]);\r
12248                                                 else\r
12249                                                         o[tr(v[0])] = tr(v);\r
12250                                         });\r
12251                                 } else\r
12252                                         o = v;\r
12253 \r
12254                                 return o;\r
12255                         }\r
12256 \r
12257                         return v;\r
12258                 },\r
12259 \r
12260                 nodeChanged : function(o) {\r
12261                         var t = this, s = t.selection, n = s.getStart() || t.getBody();\r
12262 \r
12263                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading\r
12264                         if (t.initialized) {\r
12265                                 o = o || {};\r
12266                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state\r
12267 \r
12268                                 // Get parents and add them to object\r
12269                                 o.parents = [];\r
12270                                 t.dom.getParent(n, function(node) {\r
12271                                         if (node.nodeName == 'BODY')\r
12272                                                 return true;\r
12273 \r
12274                                         o.parents.push(node);\r
12275                                 });\r
12276 \r
12277                                 t.onNodeChange.dispatch(\r
12278                                         t,\r
12279                                         o ? o.controlManager || t.controlManager : t.controlManager,\r
12280                                         n,\r
12281                                         s.isCollapsed(),\r
12282                                         o\r
12283                                 );\r
12284                         }\r
12285                 },\r
12286 \r
12287                 addButton : function(n, s) {\r
12288                         var t = this;\r
12289 \r
12290                         t.buttons = t.buttons || {};\r
12291                         t.buttons[n] = s;\r
12292                 },\r
12293 \r
12294                 addCommand : function(name, callback, scope) {\r
12295                         this.execCommands[name] = {func : callback, scope : scope || this};\r
12296                 },\r
12297 \r
12298                 addQueryStateHandler : function(name, callback, scope) {\r
12299                         this.queryStateCommands[name] = {func : callback, scope : scope || this};\r
12300                 },\r
12301 \r
12302                 addQueryValueHandler : function(name, callback, scope) {\r
12303                         this.queryValueCommands[name] = {func : callback, scope : scope || this};\r
12304                 },\r
12305 \r
12306                 addShortcut : function(pa, desc, cmd_func, sc) {\r
12307                         var t = this, c;\r
12308 \r
12309                         if (!t.settings.custom_shortcuts)\r
12310                                 return false;\r
12311 \r
12312                         t.shortcuts = t.shortcuts || {};\r
12313 \r
12314                         if (is(cmd_func, 'string')) {\r
12315                                 c = cmd_func;\r
12316 \r
12317                                 cmd_func = function() {\r
12318                                         t.execCommand(c, false, null);\r
12319                                 };\r
12320                         }\r
12321 \r
12322                         if (is(cmd_func, 'object')) {\r
12323                                 c = cmd_func;\r
12324 \r
12325                                 cmd_func = function() {\r
12326                                         t.execCommand(c[0], c[1], c[2]);\r
12327                                 };\r
12328                         }\r
12329 \r
12330                         each(explode(pa), function(pa) {\r
12331                                 var o = {\r
12332                                         func : cmd_func,\r
12333                                         scope : sc || this,\r
12334                                         desc : desc,\r
12335                                         alt : false,\r
12336                                         ctrl : false,\r
12337                                         shift : false\r
12338                                 };\r
12339 \r
12340                                 each(explode(pa, '+'), function(v) {\r
12341                                         switch (v) {\r
12342                                                 case 'alt':\r
12343                                                 case 'ctrl':\r
12344                                                 case 'shift':\r
12345                                                         o[v] = true;\r
12346                                                         break;\r
12347 \r
12348                                                 default:\r
12349                                                         o.charCode = v.charCodeAt(0);\r
12350                                                         o.keyCode = v.toUpperCase().charCodeAt(0);\r
12351                                         }\r
12352                                 });\r
12353 \r
12354                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;\r
12355                         });\r
12356 \r
12357                         return true;\r
12358                 },\r
12359 \r
12360                 execCommand : function(cmd, ui, val, a) {\r
12361                         var t = this, s = 0, o, st;\r
12362 \r
12363                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))\r
12364                                 t.focus();\r
12365 \r
12366                         a = extend({}, a);\r
12367                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);\r
12368                         if (a.terminate)\r
12369                                 return false;\r
12370 \r
12371                         // Command callback\r
12372                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {\r
12373                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12374                                 return true;\r
12375                         }\r
12376 \r
12377                         // Registred commands\r
12378                         if (o = t.execCommands[cmd]) {\r
12379                                 st = o.func.call(o.scope, ui, val);\r
12380 \r
12381                                 // Fall through on true\r
12382                                 if (st !== true) {\r
12383                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12384                                         return st;\r
12385                                 }\r
12386                         }\r
12387 \r
12388                         // Plugin commands\r
12389                         each(t.plugins, function(p) {\r
12390                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {\r
12391                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12392                                         s = 1;\r
12393                                         return false;\r
12394                                 }\r
12395                         });\r
12396 \r
12397                         if (s)\r
12398                                 return true;\r
12399 \r
12400                         // Theme commands\r
12401                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {\r
12402                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12403                                 return true;\r
12404                         }\r
12405 \r
12406                         // Editor commands\r
12407                         if (t.editorCommands.execCommand(cmd, ui, val)) {\r
12408                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12409                                 return true;\r
12410                         }\r
12411 \r
12412                         // Browser commands\r
12413                         t.getDoc().execCommand(cmd, ui, val);\r
12414                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
12415                 },\r
12416 \r
12417                 queryCommandState : function(cmd) {\r
12418                         var t = this, o, s;\r
12419 \r
12420                         // Is hidden then return undefined\r
12421                         if (t._isHidden())\r
12422                                 return;\r
12423 \r
12424                         // Registred commands\r
12425                         if (o = t.queryStateCommands[cmd]) {\r
12426                                 s = o.func.call(o.scope);\r
12427 \r
12428                                 // Fall though on true\r
12429                                 if (s !== true)\r
12430                                         return s;\r
12431                         }\r
12432 \r
12433                         // Registred commands\r
12434                         o = t.editorCommands.queryCommandState(cmd);\r
12435                         if (o !== -1)\r
12436                                 return o;\r
12437 \r
12438                         // Browser commands\r
12439                         try {\r
12440                                 return this.getDoc().queryCommandState(cmd);\r
12441                         } catch (ex) {\r
12442                                 // Fails sometimes see bug: 1896577\r
12443                         }\r
12444                 },\r
12445 \r
12446                 queryCommandValue : function(c) {\r
12447                         var t = this, o, s;\r
12448 \r
12449                         // Is hidden then return undefined\r
12450                         if (t._isHidden())\r
12451                                 return;\r
12452 \r
12453                         // Registred commands\r
12454                         if (o = t.queryValueCommands[c]) {\r
12455                                 s = o.func.call(o.scope);\r
12456 \r
12457                                 // Fall though on true\r
12458                                 if (s !== true)\r
12459                                         return s;\r
12460                         }\r
12461 \r
12462                         // Registred commands\r
12463                         o = t.editorCommands.queryCommandValue(c);\r
12464                         if (is(o))\r
12465                                 return o;\r
12466 \r
12467                         // Browser commands\r
12468                         try {\r
12469                                 return this.getDoc().queryCommandValue(c);\r
12470                         } catch (ex) {\r
12471                                 // Fails sometimes see bug: 1896577\r
12472                         }\r
12473                 },\r
12474 \r
12475                 show : function() {\r
12476                         var t = this;\r
12477 \r
12478                         DOM.show(t.getContainer());\r
12479                         DOM.hide(t.id);\r
12480                         t.load();\r
12481                 },\r
12482 \r
12483                 hide : function() {\r
12484                         var t = this, d = t.getDoc();\r
12485 \r
12486                         // Fixed bug where IE has a blinking cursor left from the editor\r
12487                         if (isIE && d)\r
12488                                 d.execCommand('SelectAll');\r
12489 \r
12490                         // We must save before we hide so Safari doesn't crash\r
12491                         t.save();\r
12492                         DOM.hide(t.getContainer());\r
12493                         DOM.setStyle(t.id, 'display', t.orgDisplay);\r
12494                 },\r
12495 \r
12496                 isHidden : function() {\r
12497                         return !DOM.isHidden(this.id);\r
12498                 },\r
12499 \r
12500                 setProgressState : function(b, ti, o) {\r
12501                         this.onSetProgressState.dispatch(this, b, ti, o);\r
12502 \r
12503                         return b;\r
12504                 },\r
12505 \r
12506                 load : function(o) {\r
12507                         var t = this, e = t.getElement(), h;\r
12508 \r
12509                         if (e) {\r
12510                                 o = o || {};\r
12511                                 o.load = true;\r
12512 \r
12513                                 // Double encode existing entities in the value\r
12514                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);\r
12515                                 o.element = e;\r
12516 \r
12517                                 if (!o.no_events)\r
12518                                         t.onLoadContent.dispatch(t, o);\r
12519 \r
12520                                 o.element = e = null;\r
12521 \r
12522                                 return h;\r
12523                         }\r
12524                 },\r
12525 \r
12526                 save : function(o) {\r
12527                         var t = this, e = t.getElement(), h, f;\r
12528 \r
12529                         if (!e || !t.initialized)\r
12530                                 return;\r
12531 \r
12532                         o = o || {};\r
12533                         o.save = true;\r
12534 \r
12535                         // Add undo level will trigger onchange event\r
12536                         if (!o.no_events) {\r
12537                                 t.undoManager.typing = false;\r
12538                                 t.undoManager.add();\r
12539                         }\r
12540 \r
12541                         o.element = e;\r
12542                         h = o.content = t.getContent(o);\r
12543 \r
12544                         if (!o.no_events)\r
12545                                 t.onSaveContent.dispatch(t, o);\r
12546 \r
12547                         h = o.content;\r
12548 \r
12549                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {\r
12550                                 e.innerHTML = h;\r
12551 \r
12552                                 // Update hidden form element\r
12553                                 if (f = DOM.getParent(t.id, 'form')) {\r
12554                                         each(f.elements, function(e) {\r
12555                                                 if (e.name == t.id) {\r
12556                                                         e.value = h;\r
12557                                                         return false;\r
12558                                                 }\r
12559                                         });\r
12560                                 }\r
12561                         } else\r
12562                                 e.value = h;\r
12563 \r
12564                         o.element = e = null;\r
12565 \r
12566                         return h;\r
12567                 },\r
12568 \r
12569                 setContent : function(content, args) {\r
12570                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;\r
12571 \r
12572                         // Setup args object\r
12573                         args = args || {};\r
12574                         args.format = args.format || 'html';\r
12575                         args.set = true;\r
12576                         args.content = content;\r
12577 \r
12578                         // Do preprocessing\r
12579                         if (!args.no_events)\r
12580                                 self.onBeforeSetContent.dispatch(self, args);\r
12581 \r
12582                         content = args.content;\r
12583 \r
12584                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content\r
12585                         // It will also be impossible to place the caret in the editor unless there is a BR element present\r
12586                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {\r
12587                                 forcedRootBlockName = self.settings.forced_root_block;\r
12588                                 if (forcedRootBlockName)\r
12589                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';\r
12590                                 else\r
12591                                         content = '<br data-mce-bogus="1">';\r
12592 \r
12593                                 body.innerHTML = content;\r
12594                                 self.selection.select(body, true);\r
12595                                 self.selection.collapse(true);\r
12596                                 return;\r
12597                         }\r
12598 \r
12599                         // Parse and serialize the html\r
12600                         if (args.format !== 'raw') {\r
12601                                 content = new tinymce.html.Serializer({}, self.schema).serialize(\r
12602                                         self.parser.parse(content)\r
12603                                 );\r
12604                         }\r
12605 \r
12606                         // Set the new cleaned contents to the editor\r
12607                         args.content = tinymce.trim(content);\r
12608                         self.dom.setHTML(body, args.content);\r
12609 \r
12610                         // Do post processing\r
12611                         if (!args.no_events)\r
12612                                 self.onSetContent.dispatch(self, args);\r
12613 \r
12614                         self.selection.normalize();\r
12615 \r
12616                         return args.content;\r
12617                 },\r
12618 \r
12619                 getContent : function(args) {\r
12620                         var self = this, content;\r
12621 \r
12622                         // Setup args object\r
12623                         args = args || {};\r
12624                         args.format = args.format || 'html';\r
12625                         args.get = true;\r
12626 \r
12627                         // Do preprocessing\r
12628                         if (!args.no_events)\r
12629                                 self.onBeforeGetContent.dispatch(self, args);\r
12630 \r
12631                         // Get raw contents or by default the cleaned contents\r
12632                         if (args.format == 'raw')\r
12633                                 content = self.getBody().innerHTML;\r
12634                         else\r
12635                                 content = self.serializer.serialize(self.getBody(), args);\r
12636 \r
12637                         args.content = tinymce.trim(content);\r
12638 \r
12639                         // Do post processing\r
12640                         if (!args.no_events)\r
12641                                 self.onGetContent.dispatch(self, args);\r
12642 \r
12643                         return args.content;\r
12644                 },\r
12645 \r
12646                 isDirty : function() {\r
12647                         var self = this;\r
12648 \r
12649                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;\r
12650                 },\r
12651 \r
12652                 getContainer : function() {\r
12653                         var t = this;\r
12654 \r
12655                         if (!t.container)\r
12656                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');\r
12657 \r
12658                         return t.container;\r
12659                 },\r
12660 \r
12661                 getContentAreaContainer : function() {\r
12662                         return this.contentAreaContainer;\r
12663                 },\r
12664 \r
12665                 getElement : function() {\r
12666                         return DOM.get(this.settings.content_element || this.id);\r
12667                 },\r
12668 \r
12669                 getWin : function() {\r
12670                         var t = this, e;\r
12671 \r
12672                         if (!t.contentWindow) {\r
12673                                 e = DOM.get(t.id + "_ifr");\r
12674 \r
12675                                 if (e)\r
12676                                         t.contentWindow = e.contentWindow;\r
12677                         }\r
12678 \r
12679                         return t.contentWindow;\r
12680                 },\r
12681 \r
12682                 getDoc : function() {\r
12683                         var t = this, w;\r
12684 \r
12685                         if (!t.contentDocument) {\r
12686                                 w = t.getWin();\r
12687 \r
12688                                 if (w)\r
12689                                         t.contentDocument = w.document;\r
12690                         }\r
12691 \r
12692                         return t.contentDocument;\r
12693                 },\r
12694 \r
12695                 getBody : function() {\r
12696                         return this.bodyElement || this.getDoc().body;\r
12697                 },\r
12698 \r
12699                 convertURL : function(u, n, e) {\r
12700                         var t = this, s = t.settings;\r
12701 \r
12702                         // Use callback instead\r
12703                         if (s.urlconverter_callback)\r
12704                                 return t.execCallback('urlconverter_callback', u, e, true, n);\r
12705 \r
12706                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs\r
12707                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)\r
12708                                 return u;\r
12709 \r
12710                         // Convert to relative\r
12711                         if (s.relative_urls)\r
12712                                 return t.documentBaseURI.toRelative(u);\r
12713 \r
12714                         // Convert to absolute\r
12715                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);\r
12716 \r
12717                         return u;\r
12718                 },\r
12719 \r
12720                 addVisual : function(e) {\r
12721                         var t = this, s = t.settings;\r
12722 \r
12723                         e = e || t.getBody();\r
12724 \r
12725                         if (!is(t.hasVisual))\r
12726                                 t.hasVisual = s.visual;\r
12727 \r
12728                         each(t.dom.select('table,a', e), function(e) {\r
12729                                 var v;\r
12730 \r
12731                                 switch (e.nodeName) {\r
12732                                         case 'TABLE':\r
12733                                                 v = t.dom.getAttrib(e, 'border');\r
12734 \r
12735                                                 if (!v || v == '0') {\r
12736                                                         if (t.hasVisual)\r
12737                                                                 t.dom.addClass(e, s.visual_table_class);\r
12738                                                         else\r
12739                                                                 t.dom.removeClass(e, s.visual_table_class);\r
12740                                                 }\r
12741 \r
12742                                                 return;\r
12743 \r
12744                                         case 'A':\r
12745                                                 v = t.dom.getAttrib(e, 'name');\r
12746 \r
12747                                                 if (v) {\r
12748                                                         if (t.hasVisual)\r
12749                                                                 t.dom.addClass(e, 'mceItemAnchor');\r
12750                                                         else\r
12751                                                                 t.dom.removeClass(e, 'mceItemAnchor');\r
12752                                                 }\r
12753 \r
12754                                                 return;\r
12755                                 }\r
12756                         });\r
12757 \r
12758                         t.onVisualAid.dispatch(t, e, t.hasVisual);\r
12759                 },\r
12760 \r
12761                 remove : function() {\r
12762                         var t = this, e = t.getContainer();\r
12763 \r
12764                         t.removed = 1; // Cancels post remove event execution\r
12765                         t.hide();\r
12766 \r
12767                         t.execCallback('remove_instance_callback', t);\r
12768                         t.onRemove.dispatch(t);\r
12769 \r
12770                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command\r
12771                         t.onExecCommand.listeners = [];\r
12772 \r
12773                         tinymce.remove(t);\r
12774                         DOM.remove(e);\r
12775                 },\r
12776 \r
12777                 destroy : function(s) {\r
12778                         var t = this;\r
12779 \r
12780                         // One time is enough\r
12781                         if (t.destroyed)\r
12782                                 return;\r
12783 \r
12784                         if (!s) {\r
12785                                 tinymce.removeUnload(t.destroy);\r
12786                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);\r
12787 \r
12788                                 // Manual destroy\r
12789                                 if (t.theme && t.theme.destroy)\r
12790                                         t.theme.destroy();\r
12791 \r
12792                                 // Destroy controls, selection and dom\r
12793                                 t.controlManager.destroy();\r
12794                                 t.selection.destroy();\r
12795                                 t.dom.destroy();\r
12796 \r
12797                                 // Remove all events\r
12798 \r
12799                                 // Don't clear the window or document if content editable\r
12800                                 // is enabled since other instances might still be present\r
12801                                 if (!t.settings.content_editable) {\r
12802                                         Event.clear(t.getWin());\r
12803                                         Event.clear(t.getDoc());\r
12804                                 }\r
12805 \r
12806                                 Event.clear(t.getBody());\r
12807                                 Event.clear(t.formElement);\r
12808                         }\r
12809 \r
12810                         if (t.formElement) {\r
12811                                 t.formElement.submit = t.formElement._mceOldSubmit;\r
12812                                 t.formElement._mceOldSubmit = null;\r
12813                         }\r
12814 \r
12815                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;\r
12816 \r
12817                         if (t.selection)\r
12818                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;\r
12819 \r
12820                         t.destroyed = 1;\r
12821                 },\r
12822 \r
12823                 // Internal functions\r
12824 \r
12825                 _addEvents : function() {\r
12826                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset\r
12827                         var t = this, i, s = t.settings, dom = t.dom, lo = {\r
12828                                 mouseup : 'onMouseUp',\r
12829                                 mousedown : 'onMouseDown',\r
12830                                 click : 'onClick',\r
12831                                 keyup : 'onKeyUp',\r
12832                                 keydown : 'onKeyDown',\r
12833                                 keypress : 'onKeyPress',\r
12834                                 submit : 'onSubmit',\r
12835                                 reset : 'onReset',\r
12836                                 contextmenu : 'onContextMenu',\r
12837                                 dblclick : 'onDblClick',\r
12838                                 paste : 'onPaste' // Doesn't work in all browsers yet\r
12839                         };\r
12840 \r
12841                         function eventHandler(e, o) {\r
12842                                 var ty = e.type;\r
12843 \r
12844                                 // Don't fire events when it's removed\r
12845                                 if (t.removed)\r
12846                                         return;\r
12847 \r
12848                                 // Generic event handler\r
12849                                 if (t.onEvent.dispatch(t, e, o) !== false) {\r
12850                                         // Specific event handler\r
12851                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);\r
12852                                 }\r
12853                         };\r
12854 \r
12855                         // Add DOM events\r
12856                         each(lo, function(v, k) {\r
12857                                 switch (k) {\r
12858                                         case 'contextmenu':\r
12859                                                 dom.bind(t.getDoc(), k, eventHandler);\r
12860                                                 break;\r
12861 \r
12862                                         case 'paste':\r
12863                                                 dom.bind(t.getBody(), k, function(e) {\r
12864                                                         eventHandler(e);\r
12865                                                 });\r
12866                                                 break;\r
12867 \r
12868                                         case 'submit':\r
12869                                         case 'reset':\r
12870                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);\r
12871                                                 break;\r
12872 \r
12873                                         default:\r
12874                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);\r
12875                                 }\r
12876                         });\r
12877 \r
12878                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {\r
12879                                 t.focus(true);\r
12880                         });\r
12881 \r
12882 \r
12883                         // Fixes bug where a specified document_base_uri could result in broken images\r
12884                         // This will also fix drag drop of images in Gecko\r
12885                         if (tinymce.isGecko) {\r
12886                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {\r
12887                                         var v;\r
12888 \r
12889                                         e = e.target;\r
12890 \r
12891                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))\r
12892                                                 e.src = t.documentBaseURI.toAbsolute(v);\r
12893                                 });\r
12894                         }\r
12895 \r
12896                         // Set various midas options in Gecko\r
12897                         if (isGecko) {\r
12898                                 function setOpts() {\r
12899                                         var t = this, d = t.getDoc(), s = t.settings;\r
12900 \r
12901                                         if (isGecko && !s.readonly) {\r
12902                                                 t._refreshContentEditable();\r
12903 \r
12904                                                 try {\r
12905                                                         // Try new Gecko method\r
12906                                                         d.execCommand("styleWithCSS", 0, false);\r
12907                                                 } catch (ex) {\r
12908                                                         // Use old method\r
12909                                                         if (!t._isHidden())\r
12910                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}\r
12911                                                 }\r
12912 \r
12913                                                 if (!s.table_inline_editing)\r
12914                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}\r
12915 \r
12916                                                 if (!s.object_resizing)\r
12917                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}\r
12918                                         }\r
12919                                 };\r
12920 \r
12921                                 t.onBeforeExecCommand.add(setOpts);\r
12922                                 t.onMouseDown.add(setOpts);\r
12923                         }\r
12924 \r
12925                         // Add node change handlers\r
12926                         t.onMouseUp.add(t.nodeChanged);\r
12927                         //t.onClick.add(t.nodeChanged);\r
12928                         t.onKeyUp.add(function(ed, e) {\r
12929                                 var c = e.keyCode;\r
12930 \r
12931                                 if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)\r
12932                                         t.nodeChanged();\r
12933                         });\r
12934 \r
12935 \r
12936                         // Add block quote deletion handler\r
12937                         t.onKeyDown.add(function(ed, e) {\r
12938                                 if (e.keyCode != VK.BACKSPACE)\r
12939                                         return;\r
12940 \r
12941                                 var rng = ed.selection.getRng();\r
12942                                 if (!rng.collapsed)\r
12943                                         return;\r
12944 \r
12945                                 var n = rng.startContainer;\r
12946                                 var offset = rng.startOffset;\r
12947 \r
12948                                 while (n && n.nodeType && n.nodeType != 1 && n.parentNode)\r
12949                                         n = n.parentNode;\r
12950 \r
12951                                 // Is the cursor at the beginning of a blockquote?\r
12952                                 if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {\r
12953                                         // Remove the blockquote\r
12954                                         ed.formatter.toggle('blockquote', null, n.parentNode);\r
12955 \r
12956                                         // Move the caret to the beginning of n\r
12957                                         rng.setStart(n, 0);\r
12958                                         rng.setEnd(n, 0);\r
12959                                         ed.selection.setRng(rng);\r
12960                                         ed.selection.collapse(false);\r
12961                                 }\r
12962                         });\r
12963 \r
12964 \r
12965 \r
12966                         // Add reset handler\r
12967                         t.onReset.add(function() {\r
12968                                 t.setContent(t.startContent, {format : 'raw'});\r
12969                         });\r
12970 \r
12971                         // Add shortcuts\r
12972                         if (s.custom_shortcuts) {\r
12973                                 if (s.custom_undo_redo_keyboard_shortcuts) {\r
12974                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');\r
12975                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');\r
12976                                 }\r
12977 \r
12978                                 // Add default shortcuts for gecko\r
12979                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');\r
12980                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');\r
12981                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');\r
12982 \r
12983                                 // BlockFormat shortcuts keys\r
12984                                 for (i=1; i<=6; i++)\r
12985                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);\r
12986 \r
12987                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);\r
12988                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);\r
12989                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);\r
12990 \r
12991                                 function find(e) {\r
12992                                         var v = null;\r
12993 \r
12994                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)\r
12995                                                 return v;\r
12996 \r
12997                                         each(t.shortcuts, function(o) {\r
12998                                                 if (tinymce.isMac && o.ctrl != e.metaKey)\r
12999                                                         return;\r
13000                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)\r
13001                                                         return;\r
13002 \r
13003                                                 if (o.alt != e.altKey)\r
13004                                                         return;\r
13005 \r
13006                                                 if (o.shift != e.shiftKey)\r
13007                                                         return;\r
13008 \r
13009                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {\r
13010                                                         v = o;\r
13011                                                         return false;\r
13012                                                 }\r
13013                                         });\r
13014 \r
13015                                         return v;\r
13016                                 };\r
13017 \r
13018                                 t.onKeyUp.add(function(ed, e) {\r
13019                                         var o = find(e);\r
13020 \r
13021                                         if (o)\r
13022                                                 return Event.cancel(e);\r
13023                                 });\r
13024 \r
13025                                 t.onKeyPress.add(function(ed, e) {\r
13026                                         var o = find(e);\r
13027 \r
13028                                         if (o)\r
13029                                                 return Event.cancel(e);\r
13030                                 });\r
13031 \r
13032                                 t.onKeyDown.add(function(ed, e) {\r
13033                                         var o = find(e);\r
13034 \r
13035                                         if (o) {\r
13036                                                 o.func.call(o.scope);\r
13037                                                 return Event.cancel(e);\r
13038                                         }\r
13039                                 });\r
13040                         }\r
13041 \r
13042                         if (tinymce.isIE) {\r
13043                                 // Fix so resize will only update the width and height attributes not the styles of an image\r
13044                                 // It will also block mceItemNoResize items\r
13045                                 dom.bind(t.getDoc(), 'controlselect', function(e) {\r
13046                                         var re = t.resizeInfo, cb;\r
13047 \r
13048                                         e = e.target;\r
13049 \r
13050                                         // Don't do this action for non image elements\r
13051                                         if (e.nodeName !== 'IMG')\r
13052                                                 return;\r
13053 \r
13054                                         if (re)\r
13055                                                 dom.unbind(re.node, re.ev, re.cb);\r
13056 \r
13057                                         if (!dom.hasClass(e, 'mceItemNoResize')) {\r
13058                                                 ev = 'resizeend';\r
13059                                                 cb = dom.bind(e, ev, function(e) {\r
13060                                                         var v;\r
13061 \r
13062                                                         e = e.target;\r
13063 \r
13064                                                         if (v = dom.getStyle(e, 'width')) {\r
13065                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));\r
13066                                                                 dom.setStyle(e, 'width', '');\r
13067                                                         }\r
13068 \r
13069                                                         if (v = dom.getStyle(e, 'height')) {\r
13070                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));\r
13071                                                                 dom.setStyle(e, 'height', '');\r
13072                                                         }\r
13073                                                 });\r
13074                                         } else {\r
13075                                                 ev = 'resizestart';\r
13076                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);\r
13077                                         }\r
13078 \r
13079                                         re = t.resizeInfo = {\r
13080                                                 node : e,\r
13081                                                 ev : ev,\r
13082                                                 cb : cb\r
13083                                         };\r
13084                                 });\r
13085                         }\r
13086 \r
13087                         if (tinymce.isOpera) {\r
13088                                 t.onClick.add(function(ed, e) {\r
13089                                         Event.prevent(e);\r
13090                                 });\r
13091                         }\r
13092 \r
13093                         // Add custom undo/redo handlers\r
13094                         if (s.custom_undo_redo) {\r
13095                                 function addUndo() {\r
13096                                         t.undoManager.typing = false;\r
13097                                         t.undoManager.add();\r
13098                                 };\r
13099 \r
13100                                 var focusLostFunc = tinymce.isGecko ? 'blur' : 'focusout';\r
13101                                 dom.bind(t.getDoc(), focusLostFunc, function(e){\r
13102                                         if (!t.removed && t.undoManager.typing)\r
13103                                                 addUndo();\r
13104                                 });\r
13105 \r
13106                                 // Add undo level when contents is drag/dropped within the editor\r
13107                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {\r
13108                                         addUndo();\r
13109                                 });\r
13110 \r
13111                                 t.onKeyUp.add(function(ed, e) {\r
13112                                         var keyCode = e.keyCode;\r
13113 \r
13114                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)\r
13115                                                 addUndo();\r
13116                                 });\r
13117 \r
13118                                 t.onKeyDown.add(function(ed, e) {\r
13119                                         var keyCode = e.keyCode, sel;\r
13120 \r
13121                                         if (keyCode == 8) {\r
13122                                                 sel = t.getDoc().selection;\r
13123 \r
13124                                                 // Fix IE control + backspace browser bug\r
13125                                                 if (sel && sel.createRange && sel.createRange().item) {\r
13126                                                         t.undoManager.beforeChange();\r
13127                                                         ed.dom.remove(sel.createRange().item(0));\r
13128                                                         addUndo();\r
13129 \r
13130                                                         return Event.cancel(e);\r
13131                                                 }\r
13132                                         }\r
13133 \r
13134                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter\r
13135                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {\r
13136                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior\r
13137                                                 // Todo: Remove this once we normalize enter behavior on IE\r
13138                                                 if (tinymce.isIE && keyCode == 13)\r
13139                                                         t.undoManager.beforeChange();\r
13140 \r
13141                                                 if (t.undoManager.typing)\r
13142                                                         addUndo();\r
13143 \r
13144                                                 return;\r
13145                                         }\r
13146 \r
13147                                         // If key isn't shift,ctrl,alt,capslock,metakey\r
13148                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {\r
13149                                                 t.undoManager.beforeChange();\r
13150                                                 t.undoManager.typing = true;\r
13151                                                 t.undoManager.add();\r
13152                                         }\r
13153                                 });\r
13154 \r
13155                                 t.onMouseDown.add(function() {\r
13156                                         if (t.undoManager.typing)\r
13157                                                 addUndo();\r
13158                                 });\r
13159                         }\r
13160 \r
13161                         // Bug fix for FireFox keeping styles from end of selection instead of start.\r
13162                         if (tinymce.isGecko) {\r
13163                                 function getAttributeApplyFunction() {\r
13164                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));\r
13165 \r
13166                                         return function() {\r
13167                                                 var target = t.selection.getStart();\r
13168 \r
13169                                                 if (target !== t.getBody()) {\r
13170                                                         t.dom.setAttrib(target, "style", null);\r
13171 \r
13172                                                 each(template, function(attr) {\r
13173                                                         target.setAttributeNode(attr.cloneNode(true));\r
13174                                                 });\r
13175                                                 }\r
13176                                         };\r
13177                                 }\r
13178 \r
13179                                 function isSelectionAcrossElements() {\r
13180                                         var s = t.selection;\r
13181 \r
13182                                         return !s.isCollapsed() && s.getStart() != s.getEnd();\r
13183                                 }\r
13184 \r
13185                                 t.onKeyPress.add(function(ed, e) {\r
13186                                         var applyAttributes;\r
13187 \r
13188                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {\r
13189                                                 applyAttributes = getAttributeApplyFunction();\r
13190                                                 t.getDoc().execCommand('delete', false, null);\r
13191                                                 applyAttributes();\r
13192 \r
13193                                                 return Event.cancel(e);\r
13194                                         }\r
13195                                 });\r
13196 \r
13197                                 t.dom.bind(t.getDoc(), 'cut', function(e) {\r
13198                                         var applyAttributes;\r
13199 \r
13200                                         if (isSelectionAcrossElements()) {\r
13201                                                 applyAttributes = getAttributeApplyFunction();\r
13202                                                 t.onKeyUp.addToTop(Event.cancel, Event);\r
13203 \r
13204                                                 setTimeout(function() {\r
13205                                                         applyAttributes();\r
13206                                                         t.onKeyUp.remove(Event.cancel, Event);\r
13207                                                 }, 0);\r
13208                                         }\r
13209                                 });\r
13210                         }\r
13211                 },\r
13212 \r
13213                 _refreshContentEditable : function() {\r
13214                         var self = this, body, parent;\r
13215 \r
13216                         // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again\r
13217                         if (self._isHidden()) {\r
13218                                 body = self.getBody();\r
13219                                 parent = body.parentNode;\r
13220 \r
13221                                 parent.removeChild(body);\r
13222                                 parent.appendChild(body);\r
13223 \r
13224                                 body.focus();\r
13225                         }\r
13226                 },\r
13227 \r
13228                 _isHidden : function() {\r
13229                         var s;\r
13230 \r
13231                         if (!isGecko)\r
13232                                 return 0;\r
13233 \r
13234                         // Weird, wheres that cursor selection?\r
13235                         s = this.selection.getSel();\r
13236                         return (!s || !s.rangeCount || s.rangeCount == 0);\r
13237                 }\r
13238         });\r
13239 })(tinymce);\r
13240 \r
13241 (function(tinymce) {\r
13242         // Added for compression purposes\r
13243         var each = tinymce.each, undefined, TRUE = true, FALSE = false;\r
13244 \r
13245         tinymce.EditorCommands = function(editor) {\r
13246                 var dom = editor.dom,\r
13247                         selection = editor.selection,\r
13248                         commands = {state: {}, exec : {}, value : {}},\r
13249                         settings = editor.settings,\r
13250                         formatter = editor.formatter,\r
13251                         bookmark;\r
13252 \r
13253                 function execCommand(command, ui, value) {\r
13254                         var func;\r
13255 \r
13256                         command = command.toLowerCase();\r
13257                         if (func = commands.exec[command]) {\r
13258                                 func(command, ui, value);\r
13259                                 return TRUE;\r
13260                         }\r
13261 \r
13262                         return FALSE;\r
13263                 };\r
13264 \r
13265                 function queryCommandState(command) {\r
13266                         var func;\r
13267 \r
13268                         command = command.toLowerCase();\r
13269                         if (func = commands.state[command])\r
13270                                 return func(command);\r
13271 \r
13272                         return -1;\r
13273                 };\r
13274 \r
13275                 function queryCommandValue(command) {\r
13276                         var func;\r
13277 \r
13278                         command = command.toLowerCase();\r
13279                         if (func = commands.value[command])\r
13280                                 return func(command);\r
13281 \r
13282                         return FALSE;\r
13283                 };\r
13284 \r
13285                 function addCommands(command_list, type) {\r
13286                         type = type || 'exec';\r
13287 \r
13288                         each(command_list, function(callback, command) {\r
13289                                 each(command.toLowerCase().split(','), function(command) {\r
13290                                         commands[type][command] = callback;\r
13291                                 });\r
13292                         });\r
13293                 };\r
13294 \r
13295                 // Expose public methods\r
13296                 tinymce.extend(this, {\r
13297                         execCommand : execCommand,\r
13298                         queryCommandState : queryCommandState,\r
13299                         queryCommandValue : queryCommandValue,\r
13300                         addCommands : addCommands\r
13301                 });\r
13302 \r
13303                 // Private methods\r
13304 \r
13305                 function execNativeCommand(command, ui, value) {\r
13306                         if (ui === undefined)\r
13307                                 ui = FALSE;\r
13308 \r
13309                         if (value === undefined)\r
13310                                 value = null;\r
13311 \r
13312                         return editor.getDoc().execCommand(command, ui, value);\r
13313                 };\r
13314 \r
13315                 function isFormatMatch(name) {\r
13316                         return formatter.match(name);\r
13317                 };\r
13318 \r
13319                 function toggleFormat(name, value) {\r
13320                         formatter.toggle(name, value ? {value : value} : undefined);\r
13321                 };\r
13322 \r
13323                 function storeSelection(type) {\r
13324                         bookmark = selection.getBookmark(type);\r
13325                 };\r
13326 \r
13327                 function restoreSelection() {\r
13328                         selection.moveToBookmark(bookmark);\r
13329                 };\r
13330 \r
13331                 // Add execCommand overrides\r
13332                 addCommands({\r
13333                         // Ignore these, added for compatibility\r
13334                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},\r
13335 \r
13336                         // Add undo manager logic\r
13337                         'mceEndUndoLevel,mceAddUndoLevel' : function() {\r
13338                                 editor.undoManager.add();\r
13339                         },\r
13340 \r
13341                         'Cut,Copy,Paste' : function(command) {\r
13342                                 var doc = editor.getDoc(), failed;\r
13343 \r
13344                                 // Try executing the native command\r
13345                                 try {\r
13346                                         execNativeCommand(command);\r
13347                                 } catch (ex) {\r
13348                                         // Command failed\r
13349                                         failed = TRUE;\r
13350                                 }\r
13351 \r
13352                                 // Present alert message about clipboard access not being available\r
13353                                 if (failed || !doc.queryCommandSupported(command)) {\r
13354                                         if (tinymce.isGecko) {\r
13355                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {\r
13356                                                         if (state)\r
13357                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');\r
13358                                                 });\r
13359                                         } else\r
13360                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));\r
13361                                 }\r
13362                         },\r
13363 \r
13364                         // Override unlink command\r
13365                         unlink : function(command) {\r
13366                                 if (selection.isCollapsed())\r
13367                                         selection.select(selection.getNode());\r
13368 \r
13369                                 execNativeCommand(command);\r
13370                                 selection.collapse(FALSE);\r
13371                         },\r
13372 \r
13373                         // Override justify commands to use the text formatter engine\r
13374                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
13375                                 var align = command.substring(7);\r
13376 \r
13377                                 // Remove all other alignments first\r
13378                                 each('left,center,right,full'.split(','), function(name) {\r
13379                                         if (align != name)\r
13380                                                 formatter.remove('align' + name);\r
13381                                 });\r
13382 \r
13383                                 toggleFormat('align' + align);\r
13384                                 execCommand('mceRepaint');\r
13385                         },\r
13386 \r
13387                         // Override list commands to fix WebKit bug\r
13388                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
13389                                 var listElm, listParent;\r
13390 \r
13391                                 execNativeCommand(command);\r
13392 \r
13393                                 // WebKit produces lists within block elements so we need to split them\r
13394                                 // we will replace the native list creation logic to custom logic later on\r
13395                                 // TODO: Remove this when the list creation logic is removed\r
13396                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');\r
13397                                 if (listElm) {\r
13398                                         listParent = listElm.parentNode;\r
13399 \r
13400                                         // If list is within a text block then split that block\r
13401                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {\r
13402                                                 storeSelection();\r
13403                                                 dom.split(listParent, listElm);\r
13404                                                 restoreSelection();\r
13405                                         }\r
13406                                 }\r
13407                         },\r
13408 \r
13409                         // Override commands to use the text formatter engine\r
13410                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
13411                                 toggleFormat(command);\r
13412                         },\r
13413 \r
13414                         // Override commands to use the text formatter engine\r
13415                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {\r
13416                                 toggleFormat(command, value);\r
13417                         },\r
13418 \r
13419                         FontSize : function(command, ui, value) {\r
13420                                 var fontClasses, fontSizes;\r
13421 \r
13422                                 // Convert font size 1-7 to styles\r
13423                                 if (value >= 1 && value <= 7) {\r
13424                                         fontSizes = tinymce.explode(settings.font_size_style_values);\r
13425                                         fontClasses = tinymce.explode(settings.font_size_classes);\r
13426 \r
13427                                         if (fontClasses)\r
13428                                                 value = fontClasses[value - 1] || value;\r
13429                                         else\r
13430                                                 value = fontSizes[value - 1] || value;\r
13431                                 }\r
13432 \r
13433                                 toggleFormat(command, value);\r
13434                         },\r
13435 \r
13436                         RemoveFormat : function(command) {\r
13437                                 formatter.remove(command);\r
13438                         },\r
13439 \r
13440                         mceBlockQuote : function(command) {\r
13441                                 toggleFormat('blockquote');\r
13442                         },\r
13443 \r
13444                         FormatBlock : function(command, ui, value) {\r
13445                                 return toggleFormat(value || 'p');\r
13446                         },\r
13447 \r
13448                         mceCleanup : function() {\r
13449                                 var bookmark = selection.getBookmark();\r
13450 \r
13451                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});\r
13452 \r
13453                                 selection.moveToBookmark(bookmark);\r
13454                         },\r
13455 \r
13456                         mceRemoveNode : function(command, ui, value) {\r
13457                                 var node = value || selection.getNode();\r
13458 \r
13459                                 // Make sure that the body node isn't removed\r
13460                                 if (node != editor.getBody()) {\r
13461                                         storeSelection();\r
13462                                         editor.dom.remove(node, TRUE);\r
13463                                         restoreSelection();\r
13464                                 }\r
13465                         },\r
13466 \r
13467                         mceSelectNodeDepth : function(command, ui, value) {\r
13468                                 var counter = 0;\r
13469 \r
13470                                 dom.getParent(selection.getNode(), function(node) {\r
13471                                         if (node.nodeType == 1 && counter++ == value) {\r
13472                                                 selection.select(node);\r
13473                                                 return FALSE;\r
13474                                         }\r
13475                                 }, editor.getBody());\r
13476                         },\r
13477 \r
13478                         mceSelectNode : function(command, ui, value) {\r
13479                                 selection.select(value);\r
13480                         },\r
13481 \r
13482                         mceInsertContent : function(command, ui, value) {\r
13483                                 var parser, serializer, parentNode, rootNode, fragment, args,\r
13484                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;\r
13485 \r
13486                                 //selection.normalize();\r
13487 \r
13488                                 // Setup parser and serializer\r
13489                                 parser = editor.parser;\r
13490                                 serializer = new tinymce.html.Serializer({}, editor.schema);\r
13491                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';\r
13492 \r
13493                                 // Run beforeSetContent handlers on the HTML to be inserted\r
13494                                 args = {content: value, format: 'html'};\r
13495                                 selection.onBeforeSetContent.dispatch(selection, args);\r
13496                                 value = args.content;\r
13497 \r
13498                                 // Add caret at end of contents if it's missing\r
13499                                 if (value.indexOf('{$caret}') == -1)\r
13500                                         value += '{$caret}';\r
13501 \r
13502                                 // Replace the caret marker with a span bookmark element\r
13503                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);\r
13504 \r
13505                                 // Insert node maker where we will insert the new HTML and get it's parent\r
13506                                 if (!selection.isCollapsed())\r
13507                                         editor.getDoc().execCommand('Delete', false, null);\r
13508 \r
13509                                 parentNode = selection.getNode();\r
13510 \r
13511                                 // Parse the fragment within the context of the parent node\r
13512                                 args = {context : parentNode.nodeName.toLowerCase()};\r
13513                                 fragment = parser.parse(value, args);\r
13514 \r
13515                                 // Move the caret to a more suitable location\r
13516                                 node = fragment.lastChild;\r
13517                                 if (node.attr('id') == 'mce_marker') {\r
13518                                         marker = node;\r
13519 \r
13520                                         for (node = node.prev; node; node = node.walk(true)) {\r
13521                                                 if (node.type == 3 || !dom.isBlock(node.name)) {\r
13522                                                         node.parent.insert(marker, node, node.name === 'br');\r
13523                                                         break;\r
13524                                                 }\r
13525                                         }\r
13526                                 }\r
13527 \r
13528                                 // If parser says valid we can insert the contents into that parent\r
13529                                 if (!args.invalid) {\r
13530                                         value = serializer.serialize(fragment);\r
13531 \r
13532                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent\r
13533                                         node = parentNode.firstChild;\r
13534                                         node2 = parentNode.lastChild;\r
13535                                         if (!node || (node === node2 && node.nodeName === 'BR'))\r
13536                                                 dom.setHTML(parentNode, value);\r
13537                                         else\r
13538                                                 selection.setContent(value);\r
13539                                 } else {\r
13540                                         // If the fragment was invalid within that context then we need\r
13541                                         // to parse and process the parent it's inserted into\r
13542 \r
13543                                         // Insert bookmark node and get the parent\r
13544                                         selection.setContent(bookmarkHtml);\r
13545                                         parentNode = editor.selection.getNode();\r
13546                                         rootNode = editor.getBody();\r
13547 \r
13548                                         // Opera will return the document node when selection is in root\r
13549                                         if (parentNode.nodeType == 9)\r
13550                                                 parentNode = node = rootNode;\r
13551                                         else\r
13552                                                 node = parentNode;\r
13553 \r
13554                                         // Find the ancestor just before the root element\r
13555                                         while (node !== rootNode) {\r
13556                                                 parentNode = node;\r
13557                                                 node = node.parentNode;\r
13558                                         }\r
13559 \r
13560                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that\r
13561                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);\r
13562                                         value = serializer.serialize(\r
13563                                                 parser.parse(\r
13564                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem\r
13565                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {\r
13566                                                                 return serializer.serialize(fragment);\r
13567                                                         })\r
13568                                                 )\r
13569                                         );\r
13570 \r
13571                                         // Set the inner/outer HTML depending on if we are in the root or not\r
13572                                         if (parentNode == rootNode)\r
13573                                                 dom.setHTML(rootNode, value);\r
13574                                         else\r
13575                                                 dom.setOuterHTML(parentNode, value);\r
13576                                 }\r
13577 \r
13578                                 marker = dom.get('mce_marker');\r
13579 \r
13580                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well\r
13581                                 nodeRect = dom.getRect(marker);\r
13582                                 viewPortRect = dom.getViewPort(editor.getWin());\r
13583 \r
13584                                 // Check if node is out side the viewport if it is then scroll to it\r
13585                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||\r
13586                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {\r
13587                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();\r
13588                                         viewportBodyElement.scrollLeft = nodeRect.x;\r
13589                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;\r
13590                                 }\r
13591 \r
13592                                 // Move selection before marker and remove it\r
13593                                 rng = dom.createRng();\r
13594 \r
13595                                 // If previous sibling is a text node set the selection to the end of that node\r
13596                                 node = marker.previousSibling;\r
13597                                 if (node && node.nodeType == 3) {\r
13598                                         rng.setStart(node, node.nodeValue.length);\r
13599                                 } else {\r
13600                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node\r
13601                                         rng.setStartBefore(marker);\r
13602                                         rng.setEndBefore(marker);\r
13603                                 }\r
13604 \r
13605                                 // Remove the marker node and set the new range\r
13606                                 dom.remove(marker);\r
13607                                 selection.setRng(rng);\r
13608 \r
13609                                 // Dispatch after event and add any visual elements needed\r
13610                                 selection.onSetContent.dispatch(selection, args);\r
13611                                 editor.addVisual();\r
13612                         },\r
13613 \r
13614                         mceInsertRawHTML : function(command, ui, value) {\r
13615                                 selection.setContent('tiny_mce_marker');\r
13616                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));\r
13617                         },\r
13618 \r
13619                         mceSetContent : function(command, ui, value) {\r
13620                                 editor.setContent(value);\r
13621                         },\r
13622 \r
13623                         'Indent,Outdent' : function(command) {\r
13624                                 var intentValue, indentUnit, value;\r
13625 \r
13626                                 // Setup indent level\r
13627                                 intentValue = settings.indentation;\r
13628                                 indentUnit = /[a-z%]+$/i.exec(intentValue);\r
13629                                 intentValue = parseInt(intentValue);\r
13630 \r
13631                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {\r
13632                                         each(selection.getSelectedBlocks(), function(element) {\r
13633                                                 if (command == 'outdent') {\r
13634                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);\r
13635                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');\r
13636                                                 } else\r
13637                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);\r
13638                                         });\r
13639                                 } else\r
13640                                         execNativeCommand(command);\r
13641                         },\r
13642 \r
13643                         mceRepaint : function() {\r
13644                                 var bookmark;\r
13645 \r
13646                                 if (tinymce.isGecko) {\r
13647                                         try {\r
13648                                                 storeSelection(TRUE);\r
13649 \r
13650                                                 if (selection.getSel())\r
13651                                                         selection.getSel().selectAllChildren(editor.getBody());\r
13652 \r
13653                                                 selection.collapse(TRUE);\r
13654                                                 restoreSelection();\r
13655                                         } catch (ex) {\r
13656                                                 // Ignore\r
13657                                         }\r
13658                                 }\r
13659                         },\r
13660 \r
13661                         mceToggleFormat : function(command, ui, value) {\r
13662                                 formatter.toggle(value);\r
13663                         },\r
13664 \r
13665                         InsertHorizontalRule : function() {\r
13666                                 editor.execCommand('mceInsertContent', false, '<hr />');\r
13667                         },\r
13668 \r
13669                         mceToggleVisualAid : function() {\r
13670                                 editor.hasVisual = !editor.hasVisual;\r
13671                                 editor.addVisual();\r
13672                         },\r
13673 \r
13674                         mceReplaceContent : function(command, ui, value) {\r
13675                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));\r
13676                         },\r
13677 \r
13678                         mceInsertLink : function(command, ui, value) {\r
13679                                 var anchor;\r
13680 \r
13681                                 if (typeof(value) == 'string')\r
13682                                         value = {href : value};\r
13683 \r
13684                                 anchor = dom.getParent(selection.getNode(), 'a');\r
13685 \r
13686                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.\r
13687                                 value.href = value.href.replace(' ', '%20');\r
13688 \r
13689                                 // Remove existing links if there could be child links or that the href isn't specified\r
13690                                 if (!anchor || !value.href) {\r
13691                                         formatter.remove('link');\r
13692                                 }               \r
13693 \r
13694                                 // Apply new link to selection\r
13695                                 if (value.href) {\r
13696                                         formatter.apply('link', value, anchor);\r
13697                                 }\r
13698                         },\r
13699 \r
13700                         selectAll : function() {\r
13701                                 var root = dom.getRoot(), rng = dom.createRng();\r
13702 \r
13703                                 rng.setStart(root, 0);\r
13704                                 rng.setEnd(root, root.childNodes.length);\r
13705 \r
13706                                 editor.selection.setRng(rng);\r
13707                         }\r
13708                 });\r
13709 \r
13710                 // Add queryCommandState overrides\r
13711                 addCommands({\r
13712                         // Override justify commands\r
13713                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
13714                                 var name = 'align' + command.substring(7);\r
13715                                 // Use Formatter.matchNode instead of Formatter.match so that we don't match on parent node. This fixes bug where for both left\r
13716                                 // and right align buttons can be active. This could occur when selected nodes have align right and the parent has align left.\r
13717                                 var nodes = selection.isCollapsed() ? [selection.getNode()] : selection.getSelectedBlocks();\r
13718                                 var matches = tinymce.map(nodes, function(node) {\r
13719                                         return !!formatter.matchNode(node, name);\r
13720                                 });\r
13721                                 return tinymce.inArray(matches, TRUE) !== -1;\r
13722                         },\r
13723 \r
13724                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
13725                                 return isFormatMatch(command);\r
13726                         },\r
13727 \r
13728                         mceBlockQuote : function() {\r
13729                                 return isFormatMatch('blockquote');\r
13730                         },\r
13731 \r
13732                         Outdent : function() {\r
13733                                 var node;\r
13734 \r
13735                                 if (settings.inline_styles) {\r
13736                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
13737                                                 return TRUE;\r
13738 \r
13739                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
13740                                                 return TRUE;\r
13741                                 }\r
13742 \r
13743                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));\r
13744                         },\r
13745 \r
13746                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
13747                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');\r
13748                         }\r
13749                 }, 'state');\r
13750 \r
13751                 // Add queryCommandValue overrides\r
13752                 addCommands({\r
13753                         'FontSize,FontName' : function(command) {\r
13754                                 var value = 0, parent;\r
13755 \r
13756                                 if (parent = dom.getParent(selection.getNode(), 'span')) {\r
13757                                         if (command == 'fontsize')\r
13758                                                 value = parent.style.fontSize;\r
13759                                         else\r
13760                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();\r
13761                                 }\r
13762 \r
13763                                 return value;\r
13764                         }\r
13765                 }, 'value');\r
13766 \r
13767                 // Add undo manager logic\r
13768                 if (settings.custom_undo_redo) {\r
13769                         addCommands({\r
13770                                 Undo : function() {\r
13771                                         editor.undoManager.undo();\r
13772                                 },\r
13773 \r
13774                                 Redo : function() {\r
13775                                         editor.undoManager.redo();\r
13776                                 }\r
13777                         });\r
13778                 }\r
13779         };\r
13780 })(tinymce);\r
13781 \r
13782 (function(tinymce) {\r
13783         var Dispatcher = tinymce.util.Dispatcher;\r
13784 \r
13785         tinymce.UndoManager = function(editor) {\r
13786                 var self, index = 0, data = [], beforeBookmark;\r
13787 \r
13788                 function getContent() {\r
13789                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));\r
13790                 };\r
13791 \r
13792                 return self = {\r
13793                         typing : false,\r
13794 \r
13795                         onAdd : new Dispatcher(self),\r
13796 \r
13797                         onUndo : new Dispatcher(self),\r
13798 \r
13799                         onRedo : new Dispatcher(self),\r
13800 \r
13801                         beforeChange : function() {\r
13802                                 beforeBookmark = editor.selection.getBookmark(2, true);\r
13803                         },\r
13804 \r
13805                         add : function(level) {\r
13806                                 var i, settings = editor.settings, lastLevel;\r
13807 \r
13808                                 level = level || {};\r
13809                                 level.content = getContent();\r
13810 \r
13811                                 // Add undo level if needed\r
13812                                 lastLevel = data[index];\r
13813                                 if (lastLevel && lastLevel.content == level.content)\r
13814                                         return null;\r
13815 \r
13816                                 // Set before bookmark on previous level\r
13817                                 if (data[index])\r
13818                                         data[index].beforeBookmark = beforeBookmark;\r
13819 \r
13820                                 // Time to compress\r
13821                                 if (settings.custom_undo_redo_levels) {\r
13822                                         if (data.length > settings.custom_undo_redo_levels) {\r
13823                                                 for (i = 0; i < data.length - 1; i++)\r
13824                                                         data[i] = data[i + 1];\r
13825 \r
13826                                                 data.length--;\r
13827                                                 index = data.length;\r
13828                                         }\r
13829                                 }\r
13830 \r
13831                                 // Get a non intrusive normalized bookmark\r
13832                                 level.bookmark = editor.selection.getBookmark(2, true);\r
13833 \r
13834                                 // Crop array if needed\r
13835                                 if (index < data.length - 1)\r
13836                                         data.length = index + 1;\r
13837 \r
13838                                 data.push(level);\r
13839                                 index = data.length - 1;\r
13840 \r
13841                                 self.onAdd.dispatch(self, level);\r
13842                                 editor.isNotDirty = 0;\r
13843 \r
13844                                 return level;\r
13845                         },\r
13846 \r
13847                         undo : function() {\r
13848                                 var level, i;\r
13849 \r
13850                                 if (self.typing) {\r
13851                                         self.add();\r
13852                                         self.typing = false;\r
13853                                 }\r
13854 \r
13855                                 if (index > 0) {\r
13856                                         level = data[--index];\r
13857 \r
13858                                         editor.setContent(level.content, {format : 'raw'});\r
13859                                         editor.selection.moveToBookmark(level.beforeBookmark);\r
13860 \r
13861                                         self.onUndo.dispatch(self, level);\r
13862                                 }\r
13863 \r
13864                                 return level;\r
13865                         },\r
13866 \r
13867                         redo : function() {\r
13868                                 var level;\r
13869 \r
13870                                 if (index < data.length - 1) {\r
13871                                         level = data[++index];\r
13872 \r
13873                                         editor.setContent(level.content, {format : 'raw'});\r
13874                                         editor.selection.moveToBookmark(level.bookmark);\r
13875 \r
13876                                         self.onRedo.dispatch(self, level);\r
13877                                 }\r
13878 \r
13879                                 return level;\r
13880                         },\r
13881 \r
13882                         clear : function() {\r
13883                                 data = [];\r
13884                                 index = 0;\r
13885                                 self.typing = false;\r
13886                         },\r
13887 \r
13888                         hasUndo : function() {\r
13889                                 return index > 0 || this.typing;\r
13890                         },\r
13891 \r
13892                         hasRedo : function() {\r
13893                                 return index < data.length - 1 && !this.typing;\r
13894                         }\r
13895                 };\r
13896         };\r
13897 })(tinymce);\r
13898 \r
13899 (function(tinymce) {\r
13900         // Shorten names\r
13901         var Event = tinymce.dom.Event,\r
13902                 isIE = tinymce.isIE,\r
13903                 isGecko = tinymce.isGecko,\r
13904                 isOpera = tinymce.isOpera,\r
13905                 each = tinymce.each,\r
13906                 extend = tinymce.extend,\r
13907                 TRUE = true,\r
13908                 FALSE = false;\r
13909 \r
13910         function cloneFormats(node) {\r
13911                 var clone, temp, inner;\r
13912 \r
13913                 do {\r
13914                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {\r
13915                                 if (clone) {\r
13916                                         temp = node.cloneNode(false);\r
13917                                         temp.appendChild(clone);\r
13918                                         clone = temp;\r
13919                                 } else {\r
13920                                         clone = inner = node.cloneNode(false);\r
13921                                 }\r
13922 \r
13923                                 clone.removeAttribute('id');\r
13924                         }\r
13925                 } while (node = node.parentNode);\r
13926 \r
13927                 if (clone)\r
13928                         return {wrapper : clone, inner : inner};\r
13929         };\r
13930 \r
13931         // Checks if the selection/caret is at the end of the specified block element\r
13932         function isAtEnd(rng, par) {\r
13933                 var rng2 = par.ownerDocument.createRange();\r
13934 \r
13935                 rng2.setStart(rng.endContainer, rng.endOffset);\r
13936                 rng2.setEndAfter(par);\r
13937 \r
13938                 // Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element\r
13939                 return rng2.cloneContents().textContent.length == 0;\r
13940         };\r
13941 \r
13942         function splitList(selection, dom, li) {\r
13943                 var listBlock, block;\r
13944 \r
13945                 if (dom.isEmpty(li)) {\r
13946                         listBlock = dom.getParent(li, 'ul,ol');\r
13947 \r
13948                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {\r
13949                                 dom.split(listBlock, li);\r
13950                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');\r
13951                                 dom.replace(block, li);\r
13952                                 selection.select(block, 1);\r
13953                         }\r
13954 \r
13955                         return FALSE;\r
13956                 }\r
13957 \r
13958                 return TRUE;\r
13959         };\r
13960 \r
13961         tinymce.create('tinymce.ForceBlocks', {\r
13962                 ForceBlocks : function(ed) {\r
13963                         var t = this, s = ed.settings, elm;\r
13964 \r
13965                         t.editor = ed;\r
13966                         t.dom = ed.dom;\r
13967                         elm = (s.forced_root_block || 'p').toLowerCase();\r
13968                         s.element = elm.toUpperCase();\r
13969 \r
13970                         ed.onPreInit.add(t.setup, t);\r
13971                 },\r
13972 \r
13973                 setup : function() {\r
13974                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();\r
13975 \r
13976                         // Force root blocks\r
13977                         if (s.forced_root_block) {\r
13978                                 function addRootBlocks() {\r
13979                                         var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;\r
13980 \r
13981                                         if (!node || node.nodeType !== 1)\r
13982                                                 return;\r
13983 \r
13984                                         // Check if node is wrapped in block\r
13985                                         while (node != rootNode) {\r
13986                                                 if (blockElements[node.nodeName])\r
13987                                                         return;\r
13988 \r
13989                                                 node = node.parentNode;\r
13990                                         }\r
13991 \r
13992                                         // Get current selection\r
13993                                         rng = selection.getRng();\r
13994                                         if (rng.setStart) {\r
13995                                                 startContainer = rng.startContainer;\r
13996                                                 startOffset = rng.startOffset;\r
13997                                                 endContainer = rng.endContainer;\r
13998                                                 endOffset = rng.endOffset;\r
13999                                         } else {\r
14000                                                 // Force control range into text range\r
14001                                                 if (rng.item) {\r
14002                                                         rng = ed.getDoc().body.createTextRange();\r
14003                                                         rng.moveToElementText(rng.item(0));\r
14004                                                 }\r
14005 \r
14006                                                 tmpRng = rng.duplicate();\r
14007                                                 tmpRng.collapse(true);\r
14008                                                 startOffset = tmpRng.move('character', offset) * -1;\r
14009 \r
14010                                                 if (!tmpRng.collapsed) {\r
14011                                                         tmpRng = rng.duplicate();\r
14012                                                         tmpRng.collapse(false);\r
14013                                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;\r
14014                                                 }\r
14015                                         }\r
14016 \r
14017                                         // Wrap non block elements and text nodes\r
14018                                         for (node = rootNode.firstChild; node; node) {\r
14019                                                 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {\r
14020                                                         if (!rootBlockNode) {\r
14021                                                                 rootBlockNode = dom.create(s.forced_root_block);\r
14022                                                                 node.parentNode.insertBefore(rootBlockNode, node);\r
14023                                                         }\r
14024 \r
14025                                                         tempNode = node;\r
14026                                                         node = node.nextSibling;\r
14027                                                         rootBlockNode.appendChild(tempNode);\r
14028                                                 } else {\r
14029                                                         rootBlockNode = null;\r
14030                                                         node = node.nextSibling;\r
14031                                                 }\r
14032                                         }\r
14033 \r
14034                                         if (rng.setStart) {\r
14035                                                 rng.setStart(startContainer, startOffset);\r
14036                                                 rng.setEnd(endContainer, endOffset);\r
14037                                                 selection.setRng(rng);\r
14038                                         } else {\r
14039                                                 try {\r
14040                                                         rng = ed.getDoc().body.createTextRange();\r
14041                                                         rng.moveToElementText(rootNode);\r
14042                                                         rng.collapse(true);\r
14043                                                         rng.moveStart('character', startOffset);\r
14044 \r
14045                                                         if (endOffset > 0)\r
14046                                                                 rng.moveEnd('character', endOffset);\r
14047 \r
14048                                                         rng.select();\r
14049                                                 } catch (ex) {\r
14050                                                         // Ignore\r
14051                                                 }\r
14052                                         }\r
14053 \r
14054                                         ed.nodeChanged();\r
14055                                 };\r
14056 \r
14057                                 ed.onKeyUp.add(addRootBlocks);\r
14058                                 ed.onClick.add(addRootBlocks);\r
14059                         }\r
14060 \r
14061                         if (s.force_br_newlines) {\r
14062                                 // Force IE to produce BRs on enter\r
14063                                 if (isIE) {\r
14064                                         ed.onKeyPress.add(function(ed, e) {\r
14065                                                 var n;\r
14066 \r
14067                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {\r
14068                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});\r
14069                                                         n = dom.get('__');\r
14070                                                         n.removeAttribute('id');\r
14071                                                         selection.select(n);\r
14072                                                         selection.collapse();\r
14073                                                         return Event.cancel(e);\r
14074                                                 }\r
14075                                         });\r
14076                                 }\r
14077                         }\r
14078 \r
14079                         if (s.force_p_newlines) {\r
14080                                 if (!isIE) {\r
14081                                         ed.onKeyPress.add(function(ed, e) {\r
14082                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))\r
14083                                                         Event.cancel(e);\r
14084                                         });\r
14085                                 } else {\r
14086                                         // Ungly hack to for IE to preserve the formatting when you press\r
14087                                         // enter at the end of a block element with formatted contents\r
14088                                         // This logic overrides the browsers default logic with\r
14089                                         // custom logic that enables us to control the output\r
14090                                         tinymce.addUnload(function() {\r
14091                                                 t._previousFormats = 0; // Fix IE leak\r
14092                                         });\r
14093 \r
14094                                         ed.onKeyPress.add(function(ed, e) {\r
14095                                                 t._previousFormats = 0;\r
14096 \r
14097                                                 // Clone the current formats, this will later be applied to the new block contents\r
14098                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)\r
14099                                                         t._previousFormats = cloneFormats(ed.selection.getStart());\r
14100                                         });\r
14101 \r
14102                                         ed.onKeyUp.add(function(ed, e) {\r
14103                                                 // Let IE break the element and the wrap the new caret location in the previous formats\r
14104                                                 if (e.keyCode == 13 && !e.shiftKey) {\r
14105                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;\r
14106 \r
14107                                                         // Parent is an empty block\r
14108                                                         if (!parent.hasChildNodes() && fmt) {\r
14109                                                                 parent = dom.getParent(parent, dom.isBlock);\r
14110 \r
14111                                                                 if (parent && parent.nodeName != 'LI') {\r
14112                                                                         parent.innerHTML = '';\r
14113 \r
14114                                                                         if (t._previousFormats) {\r
14115                                                                                 parent.appendChild(fmt.wrapper);\r
14116                                                                                 fmt.inner.innerHTML = '\uFEFF';\r
14117                                                                         } else\r
14118                                                                                 parent.innerHTML = '\uFEFF';\r
14119 \r
14120                                                                         selection.select(parent, 1);\r
14121                                                                         selection.collapse(true);\r
14122                                                                         ed.getDoc().execCommand('Delete', false, null);\r
14123                                                                         t._previousFormats = 0;\r
14124                                                                 }\r
14125                                                         }\r
14126                                                 }\r
14127                                         });\r
14128                                 }\r
14129 \r
14130                                 if (isGecko) {\r
14131                                         ed.onKeyDown.add(function(ed, e) {\r
14132                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)\r
14133                                                         t.backspaceDelete(e, e.keyCode == 8);\r
14134                                         });\r
14135                                 }\r
14136                         }\r
14137 \r
14138                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973\r
14139                         if (tinymce.isWebKit) {\r
14140                                 function insertBr(ed) {\r
14141                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;\r
14142 \r
14143                                         // Insert BR element\r
14144                                         rng.insertNode(br = dom.create('br'));\r
14145 \r
14146                                         // Place caret after BR\r
14147                                         rng.setStartAfter(br);\r
14148                                         rng.setEndAfter(br);\r
14149                                         selection.setRng(rng);\r
14150 \r
14151                                         // Could not place caret after BR then insert an nbsp entity and move the caret\r
14152                                         if (selection.getSel().focusNode == br.previousSibling) {\r
14153                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));\r
14154                                                 selection.collapse(TRUE);\r
14155                                         }\r
14156 \r
14157                                         // Create a temporary DIV after the BR and get the position as it\r
14158                                         // seems like getPos() returns 0 for text nodes and BR elements.\r
14159                                         dom.insertAfter(div, br);\r
14160                                         divYPos = dom.getPos(div).y;\r
14161                                         dom.remove(div);\r
14162 \r
14163                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117\r
14164                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.\r
14165                                                 ed.getWin().scrollTo(0, divYPos);\r
14166                                 };\r
14167 \r
14168                                 ed.onKeyPress.add(function(ed, e) {\r
14169                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {\r
14170                                                 insertBr(ed);\r
14171                                                 Event.cancel(e);\r
14172                                         }\r
14173                                 });\r
14174                         }\r
14175 \r
14176                         // IE specific fixes\r
14177                         if (isIE) {\r
14178                                 // Replaces IE:s auto generated paragraphs with the specified element name\r
14179                                 if (s.element != 'P') {\r
14180                                         ed.onKeyPress.add(function(ed, e) {\r
14181                                                 t.lastElm = selection.getNode().nodeName;\r
14182                                         });\r
14183 \r
14184                                         ed.onKeyUp.add(function(ed, e) {\r
14185                                                 var bl, n = selection.getNode(), b = ed.getBody();\r
14186 \r
14187                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {\r
14188                                                         n = dom.rename(n, s.element);\r
14189                                                         selection.select(n);\r
14190                                                         selection.collapse();\r
14191                                                         ed.nodeChanged();\r
14192                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {\r
14193                                                         bl = dom.getParent(n, 'p');\r
14194 \r
14195                                                         if (bl) {\r
14196                                                                 dom.rename(bl, s.element);\r
14197                                                                 ed.nodeChanged();\r
14198                                                         }\r
14199                                                 }\r
14200                                         });\r
14201                                 }\r
14202                         }\r
14203                 },\r
14204 \r
14205                 getParentBlock : function(n) {\r
14206                         var d = this.dom;\r
14207 \r
14208                         return d.getParent(n, d.isBlock);\r
14209                 },\r
14210 \r
14211                 insertPara : function(e) {\r
14212                         var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;\r
14213                         var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;\r
14214 \r
14215                         ed.undoManager.beforeChange();\r
14216 \r
14217                         // If root blocks are forced then use Operas default behavior since it's really good\r
14218 // Removed due to bug: #1853816\r
14219 //                      if (se.forced_root_block && isOpera)\r
14220 //                              return TRUE;\r
14221 \r
14222                         // Setup before range\r
14223                         rb = d.createRange();\r
14224 \r
14225                         // If is before the first block element and in body, then move it into first block element\r
14226                         rb.setStart(s.anchorNode, s.anchorOffset);\r
14227                         rb.collapse(TRUE);\r
14228 \r
14229                         // Setup after range\r
14230                         ra = d.createRange();\r
14231 \r
14232                         // If is before the first block element and in body, then move it into first block element\r
14233                         ra.setStart(s.focusNode, s.focusOffset);\r
14234                         ra.collapse(TRUE);\r
14235 \r
14236                         // Setup start/end points\r
14237                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;\r
14238                         sn = dir ? s.anchorNode : s.focusNode;\r
14239                         so = dir ? s.anchorOffset : s.focusOffset;\r
14240                         en = dir ? s.focusNode : s.anchorNode;\r
14241                         eo = dir ? s.focusOffset : s.anchorOffset;\r
14242 \r
14243                         // If selection is in empty table cell\r
14244                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {\r
14245                                 if (sn.firstChild.nodeName == 'BR')\r
14246                                         dom.remove(sn.firstChild); // Remove BR\r
14247 \r
14248                                 // Create two new block elements\r
14249                                 if (sn.childNodes.length == 0) {\r
14250                                         ed.dom.add(sn, se.element, null, '<br />');\r
14251                                         aft = ed.dom.add(sn, se.element, null, '<br />');\r
14252                                 } else {\r
14253                                         n = sn.innerHTML;\r
14254                                         sn.innerHTML = '';\r
14255                                         ed.dom.add(sn, se.element, null, n);\r
14256                                         aft = ed.dom.add(sn, se.element, null, '<br />');\r
14257                                 }\r
14258 \r
14259                                 // Move caret into the last one\r
14260                                 r = d.createRange();\r
14261                                 r.selectNodeContents(aft);\r
14262                                 r.collapse(1);\r
14263                                 ed.selection.setRng(r);\r
14264 \r
14265                                 return FALSE;\r
14266                         }\r
14267 \r
14268                         // If the caret is in an invalid location in FF we need to move it into the first block\r
14269                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {\r
14270                                 sn = en = sn.firstChild;\r
14271                                 so = eo = 0;\r
14272                                 rb = d.createRange();\r
14273                                 rb.setStart(sn, 0);\r
14274                                 ra = d.createRange();\r
14275                                 ra.setStart(en, 0);\r
14276                         }\r
14277 \r
14278                         // If the body is totally empty add a BR element this might happen on webkit\r
14279                         if (!d.body.hasChildNodes()) {\r
14280                                 d.body.appendChild(dom.create('br'));\r
14281                         }\r
14282 \r
14283                         // Never use body as start or end node\r
14284                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
14285                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;\r
14286                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
14287                         en = en.nodeName == "BODY" ? en.firstChild : en;\r
14288 \r
14289                         // Get start and end blocks\r
14290                         sb = t.getParentBlock(sn);\r
14291                         eb = t.getParentBlock(en);\r
14292                         bn = sb ? sb.nodeName : se.element; // Get block name to create\r
14293 \r
14294                         // Return inside list use default browser behavior\r
14295                         if (n = t.dom.getParent(sb, 'li,pre')) {\r
14296                                 if (n.nodeName == 'LI')\r
14297                                         return splitList(ed.selection, t.dom, n);\r
14298 \r
14299                                 return TRUE;\r
14300                         }\r
14301 \r
14302                         // If caption or absolute layers then always generate new blocks within\r
14303                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
14304                                 bn = se.element;\r
14305                                 sb = null;\r
14306                         }\r
14307 \r
14308                         // If caption or absolute layers then always generate new blocks within\r
14309                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
14310                                 bn = se.element;\r
14311                                 eb = null;\r
14312                         }\r
14313 \r
14314                         // Use P instead\r
14315                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {\r
14316                                 bn = se.element;\r
14317                                 sb = eb = null;\r
14318                         }\r
14319 \r
14320                         // Setup new before and after blocks\r
14321                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);\r
14322                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);\r
14323 \r
14324                         // Remove id from after clone\r
14325                         aft.removeAttribute('id');\r
14326 \r
14327                         // Is header and cursor is at the end, then force paragraph under\r
14328                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))\r
14329                                 aft = ed.dom.create(se.element);\r
14330 \r
14331                         // Find start chop node\r
14332                         n = sc = sn;\r
14333                         do {\r
14334                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
14335                                         break;\r
14336 \r
14337                                 sc = n;\r
14338                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));\r
14339 \r
14340                         // Find end chop node\r
14341                         n = ec = en;\r
14342                         do {\r
14343                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
14344                                         break;\r
14345 \r
14346                                 ec = n;\r
14347                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));\r
14348 \r
14349                         // Place first chop part into before block element\r
14350                         if (sc.nodeName == bn)\r
14351                                 rb.setStart(sc, 0);\r
14352                         else\r
14353                                 rb.setStartBefore(sc);\r
14354 \r
14355                         rb.setEnd(sn, so);\r
14356                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
14357 \r
14358                         // Place secnd chop part within new block element\r
14359                         try {\r
14360                                 ra.setEndAfter(ec);\r
14361                         } catch(ex) {\r
14362                                 //console.debug(s.focusNode, s.focusOffset);\r
14363                         }\r
14364 \r
14365                         ra.setStart(en, eo);\r
14366                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
14367 \r
14368                         // Create range around everything\r
14369                         r = d.createRange();\r
14370                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {\r
14371                                 r.setStartBefore(sc.parentNode);\r
14372                         } else {\r
14373                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)\r
14374                                         r.setStartBefore(rb.startContainer);\r
14375                                 else\r
14376                                         r.setStart(rb.startContainer, rb.startOffset);\r
14377                         }\r
14378 \r
14379                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)\r
14380                                 r.setEndAfter(ec.parentNode);\r
14381                         else\r
14382                                 r.setEnd(ra.endContainer, ra.endOffset);\r
14383 \r
14384                         // Delete and replace it with new block elements\r
14385                         r.deleteContents();\r
14386 \r
14387                         if (isOpera)\r
14388                                 ed.getWin().scrollTo(0, vp.y);\r
14389 \r
14390                         // Never wrap blocks in blocks\r
14391                         if (bef.firstChild && bef.firstChild.nodeName == bn)\r
14392                                 bef.innerHTML = bef.firstChild.innerHTML;\r
14393 \r
14394                         if (aft.firstChild && aft.firstChild.nodeName == bn)\r
14395                                 aft.innerHTML = aft.firstChild.innerHTML;\r
14396 \r
14397                         function appendStyles(e, en) {\r
14398                                 var nl = [], nn, n, i;\r
14399 \r
14400                                 e.innerHTML = '';\r
14401 \r
14402                                 // Make clones of style elements\r
14403                                 if (se.keep_styles) {\r
14404                                         n = en;\r
14405                                         do {\r
14406                                                 // We only want style specific elements\r
14407                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {\r
14408                                                         nn = n.cloneNode(FALSE);\r
14409                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique\r
14410                                                         nl.push(nn);\r
14411                                                 }\r
14412                                         } while (n = n.parentNode);\r
14413                                 }\r
14414 \r
14415                                 // Append style elements to aft\r
14416                                 if (nl.length > 0) {\r
14417                                         for (i = nl.length - 1, nn = e; i >= 0; i--)\r
14418                                                 nn = nn.appendChild(nl[i]);\r
14419 \r
14420                                         // Padd most inner style element\r
14421                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
14422                                         return nl[0]; // Move caret to most inner element\r
14423                                 } else\r
14424                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
14425                         };\r
14426                                 \r
14427                         // Padd empty blocks\r
14428                         if (dom.isEmpty(bef))\r
14429                                 appendStyles(bef, sn);\r
14430 \r
14431                         // Fill empty afterblook with current style\r
14432                         if (dom.isEmpty(aft))\r
14433                                 car = appendStyles(aft, en);\r
14434 \r
14435                         // Opera needs this one backwards for older versions\r
14436                         if (isOpera && parseFloat(opera.version()) < 9.5) {\r
14437                                 r.insertNode(bef);\r
14438                                 r.insertNode(aft);\r
14439                         } else {\r
14440                                 r.insertNode(aft);\r
14441                                 r.insertNode(bef);\r
14442                         }\r
14443 \r
14444                         // Normalize\r
14445                         aft.normalize();\r
14446                         bef.normalize();\r
14447 \r
14448                         // Move cursor and scroll into view\r
14449                         ed.selection.select(aft, true);\r
14450                         ed.selection.collapse(true);\r
14451 \r
14452                         // 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\r
14453                         y = ed.dom.getPos(aft).y;\r
14454                         //ch = aft.clientHeight;\r
14455 \r
14456                         // Is element within viewport\r
14457                         if (y < vp.y || y + 25 > vp.y + vp.h) {\r
14458                                 ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks\r
14459 \r
14460                                 /*console.debug(\r
14461                                         'Element: y=' + y + ', h=' + ch + ', ' +\r
14462                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)\r
14463                                 );*/\r
14464                         }\r
14465 \r
14466                         ed.undoManager.add();\r
14467 \r
14468                         return FALSE;\r
14469                 },\r
14470 \r
14471                 backspaceDelete : function(e, bs) {\r
14472                         var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;\r
14473 \r
14474                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651\r
14475                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {\r
14476                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);\r
14477 \r
14478                                 // Walk the dom backwards until we find a text node\r
14479                                 for (n = sc.lastChild; n; n = walker.prev()) {\r
14480                                         if (n.nodeType == 3) {\r
14481                                                 r.setStart(n, n.nodeValue.length);\r
14482                                                 r.collapse(true);\r
14483                                                 se.setRng(r);\r
14484                                                 return;\r
14485                                         }\r
14486                                 }\r
14487                         }\r
14488 \r
14489                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs\r
14490                         // This workaround removes the element by hand and moves the caret to the previous element\r
14491                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {\r
14492                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {\r
14493                                         // Find previous block element\r
14494                                         n = sc;\r
14495                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;\r
14496 \r
14497                                         if (n) {\r
14498                                                 if (sc != b.firstChild) {\r
14499                                                         // Find last text node\r
14500                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);\r
14501                                                         while (tn = w.nextNode())\r
14502                                                                 n = tn;\r
14503 \r
14504                                                         // Place caret at the end of last text node\r
14505                                                         r = ed.getDoc().createRange();\r
14506                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);\r
14507                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);\r
14508                                                         se.setRng(r);\r
14509 \r
14510                                                         // Remove the target container\r
14511                                                         ed.dom.remove(sc);\r
14512                                                 }\r
14513 \r
14514                                                 return Event.cancel(e);\r
14515                                         }\r
14516                                 }\r
14517                         }\r
14518                 }\r
14519         });\r
14520 })(tinymce);\r
14521 \r
14522 (function(tinymce) {\r
14523         // Shorten names\r
14524         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;\r
14525 \r
14526         tinymce.create('tinymce.ControlManager', {\r
14527                 ControlManager : function(ed, s) {\r
14528                         var t = this, i;\r
14529 \r
14530                         s = s || {};\r
14531                         t.editor = ed;\r
14532                         t.controls = {};\r
14533                         t.onAdd = new tinymce.util.Dispatcher(t);\r
14534                         t.onPostRender = new tinymce.util.Dispatcher(t);\r
14535                         t.prefix = s.prefix || ed.id + '_';\r
14536                         t._cls = {};\r
14537 \r
14538                         t.onPostRender.add(function() {\r
14539                                 each(t.controls, function(c) {\r
14540                                         c.postRender();\r
14541                                 });\r
14542                         });\r
14543                 },\r
14544 \r
14545                 get : function(id) {\r
14546                         return this.controls[this.prefix + id] || this.controls[id];\r
14547                 },\r
14548 \r
14549                 setActive : function(id, s) {\r
14550                         var c = null;\r
14551 \r
14552                         if (c = this.get(id))\r
14553                                 c.setActive(s);\r
14554 \r
14555                         return c;\r
14556                 },\r
14557 \r
14558                 setDisabled : function(id, s) {\r
14559                         var c = null;\r
14560 \r
14561                         if (c = this.get(id))\r
14562                                 c.setDisabled(s);\r
14563 \r
14564                         return c;\r
14565                 },\r
14566 \r
14567                 add : function(c) {\r
14568                         var t = this;\r
14569 \r
14570                         if (c) {\r
14571                                 t.controls[c.id] = c;\r
14572                                 t.onAdd.dispatch(c, t);\r
14573                         }\r
14574 \r
14575                         return c;\r
14576                 },\r
14577 \r
14578                 createControl : function(n) {\r
14579                         var c, t = this, ed = t.editor;\r
14580 \r
14581                         each(ed.plugins, function(p) {\r
14582                                 if (p.createControl) {\r
14583                                         c = p.createControl(n, t);\r
14584 \r
14585                                         if (c)\r
14586                                                 return false;\r
14587                                 }\r
14588                         });\r
14589 \r
14590                         switch (n) {\r
14591                                 case "|":\r
14592                                 case "separator":\r
14593                                         return t.createSeparator();\r
14594                         }\r
14595 \r
14596                         if (!c && ed.buttons && (c = ed.buttons[n]))\r
14597                                 return t.createButton(n, c);\r
14598 \r
14599                         return t.add(c);\r
14600                 },\r
14601 \r
14602                 createDropMenu : function(id, s, cc) {\r
14603                         var t = this, ed = t.editor, c, bm, v, cls;\r
14604 \r
14605                         s = extend({\r
14606                                 'class' : 'mceDropDown',\r
14607                                 constrain : ed.settings.constrain_menus\r
14608                         }, s);\r
14609 \r
14610                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';\r
14611                         if (v = ed.getParam('skin_variant'))\r
14612                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);\r
14613 \r
14614                         id = t.prefix + id;\r
14615                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;\r
14616                         c = t.controls[id] = new cls(id, s);\r
14617                         c.onAddItem.add(function(c, o) {\r
14618                                 var s = o.settings;\r
14619 \r
14620                                 s.title = ed.getLang(s.title, s.title);\r
14621 \r
14622                                 if (!s.onclick) {\r
14623                                         s.onclick = function(v) {\r
14624                                                 if (s.cmd)\r
14625                                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
14626                                         };\r
14627                                 }\r
14628                         });\r
14629 \r
14630                         ed.onRemove.add(function() {\r
14631                                 c.destroy();\r
14632                         });\r
14633 \r
14634                         // Fix for bug #1897785, #1898007\r
14635                         if (tinymce.isIE) {\r
14636                                 c.onShowMenu.add(function() {\r
14637                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
14638                                         ed.focus();\r
14639 \r
14640                                         bm = ed.selection.getBookmark(1);\r
14641                                 });\r
14642 \r
14643                                 c.onHideMenu.add(function() {\r
14644                                         if (bm) {\r
14645                                                 ed.selection.moveToBookmark(bm);\r
14646                                                 bm = 0;\r
14647                                         }\r
14648                                 });\r
14649                         }\r
14650 \r
14651                         return t.add(c);\r
14652                 },\r
14653 \r
14654                 createListBox : function(id, s, cc) {\r
14655                         var t = this, ed = t.editor, cmd, c, cls;\r
14656 \r
14657                         if (t.get(id))\r
14658                                 return null;\r
14659 \r
14660                         s.title = ed.translate(s.title);\r
14661                         s.scope = s.scope || ed;\r
14662 \r
14663                         if (!s.onselect) {\r
14664                                 s.onselect = function(v) {\r
14665                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14666                                 };\r
14667                         }\r
14668 \r
14669                         s = extend({\r
14670                                 title : s.title,\r
14671                                 'class' : 'mce_' + id,\r
14672                                 scope : s.scope,\r
14673                                 control_manager : t\r
14674                         }, s);\r
14675 \r
14676                         id = t.prefix + id;\r
14677 \r
14678 \r
14679                         function useNativeListForAccessibility(ed) {\r
14680                                 return ed.settings.use_accessible_selects && !tinymce.isGecko\r
14681                         }\r
14682 \r
14683                         if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))\r
14684                                 c = new tinymce.ui.NativeListBox(id, s);\r
14685                         else {\r
14686                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;\r
14687                                 c = new cls(id, s, ed);\r
14688                         }\r
14689 \r
14690                         t.controls[id] = c;\r
14691 \r
14692                         // Fix focus problem in Safari\r
14693                         if (tinymce.isWebKit) {\r
14694                                 c.onPostRender.add(function(c, n) {\r
14695                                         // Store bookmark on mousedown\r
14696                                         Event.add(n, 'mousedown', function() {\r
14697                                                 ed.bookmark = ed.selection.getBookmark(1);\r
14698                                         });\r
14699 \r
14700                                         // Restore on focus, since it might be lost\r
14701                                         Event.add(n, 'focus', function() {\r
14702                                                 ed.selection.moveToBookmark(ed.bookmark);\r
14703                                                 ed.bookmark = null;\r
14704                                         });\r
14705                                 });\r
14706                         }\r
14707 \r
14708                         if (c.hideMenu)\r
14709                                 ed.onMouseDown.add(c.hideMenu, c);\r
14710 \r
14711                         return t.add(c);\r
14712                 },\r
14713 \r
14714                 createButton : function(id, s, cc) {\r
14715                         var t = this, ed = t.editor, o, c, cls;\r
14716 \r
14717                         if (t.get(id))\r
14718                                 return null;\r
14719 \r
14720                         s.title = ed.translate(s.title);\r
14721                         s.label = ed.translate(s.label);\r
14722                         s.scope = s.scope || ed;\r
14723 \r
14724                         if (!s.onclick && !s.menu_button) {\r
14725                                 s.onclick = function() {\r
14726                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
14727                                 };\r
14728                         }\r
14729 \r
14730                         s = extend({\r
14731                                 title : s.title,\r
14732                                 'class' : 'mce_' + id,\r
14733                                 unavailable_prefix : ed.getLang('unavailable', ''),\r
14734                                 scope : s.scope,\r
14735                                 control_manager : t\r
14736                         }, s);\r
14737 \r
14738                         id = t.prefix + id;\r
14739 \r
14740                         if (s.menu_button) {\r
14741                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;\r
14742                                 c = new cls(id, s, ed);\r
14743                                 ed.onMouseDown.add(c.hideMenu, c);\r
14744                         } else {\r
14745                                 cls = t._cls.button || tinymce.ui.Button;\r
14746                                 c = new cls(id, s, ed);\r
14747                         }\r
14748 \r
14749                         return t.add(c);\r
14750                 },\r
14751 \r
14752                 createMenuButton : function(id, s, cc) {\r
14753                         s = s || {};\r
14754                         s.menu_button = 1;\r
14755 \r
14756                         return this.createButton(id, s, cc);\r
14757                 },\r
14758 \r
14759                 createSplitButton : function(id, s, cc) {\r
14760                         var t = this, ed = t.editor, cmd, c, cls;\r
14761 \r
14762                         if (t.get(id))\r
14763                                 return null;\r
14764 \r
14765                         s.title = ed.translate(s.title);\r
14766                         s.scope = s.scope || ed;\r
14767 \r
14768                         if (!s.onclick) {\r
14769                                 s.onclick = function(v) {\r
14770                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14771                                 };\r
14772                         }\r
14773 \r
14774                         if (!s.onselect) {\r
14775                                 s.onselect = function(v) {\r
14776                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14777                                 };\r
14778                         }\r
14779 \r
14780                         s = extend({\r
14781                                 title : s.title,\r
14782                                 'class' : 'mce_' + id,\r
14783                                 scope : s.scope,\r
14784                                 control_manager : t\r
14785                         }, s);\r
14786 \r
14787                         id = t.prefix + id;\r
14788                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;\r
14789                         c = t.add(new cls(id, s, ed));\r
14790                         ed.onMouseDown.add(c.hideMenu, c);\r
14791 \r
14792                         return c;\r
14793                 },\r
14794 \r
14795                 createColorSplitButton : function(id, s, cc) {\r
14796                         var t = this, ed = t.editor, cmd, c, cls, bm;\r
14797 \r
14798                         if (t.get(id))\r
14799                                 return null;\r
14800 \r
14801                         s.title = ed.translate(s.title);\r
14802                         s.scope = s.scope || ed;\r
14803 \r
14804                         if (!s.onclick) {\r
14805                                 s.onclick = function(v) {\r
14806                                         if (tinymce.isIE)\r
14807                                                 bm = ed.selection.getBookmark(1);\r
14808 \r
14809                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14810                                 };\r
14811                         }\r
14812 \r
14813                         if (!s.onselect) {\r
14814                                 s.onselect = function(v) {\r
14815                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14816                                 };\r
14817                         }\r
14818 \r
14819                         s = extend({\r
14820                                 title : s.title,\r
14821                                 'class' : 'mce_' + id,\r
14822                                 'menu_class' : ed.getParam('skin') + 'Skin',\r
14823                                 scope : s.scope,\r
14824                                 more_colors_title : ed.getLang('more_colors')\r
14825                         }, s);\r
14826 \r
14827                         id = t.prefix + id;\r
14828                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;\r
14829                         c = new cls(id, s, ed);\r
14830                         ed.onMouseDown.add(c.hideMenu, c);\r
14831 \r
14832                         // Remove the menu element when the editor is removed\r
14833                         ed.onRemove.add(function() {\r
14834                                 c.destroy();\r
14835                         });\r
14836 \r
14837                         // Fix for bug #1897785, #1898007\r
14838                         if (tinymce.isIE) {\r
14839                                 c.onShowMenu.add(function() {\r
14840                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
14841                                         ed.focus();\r
14842                                         bm = ed.selection.getBookmark(1);\r
14843                                 });\r
14844 \r
14845                                 c.onHideMenu.add(function() {\r
14846                                         if (bm) {\r
14847                                                 ed.selection.moveToBookmark(bm);\r
14848                                                 bm = 0;\r
14849                                         }\r
14850                                 });\r
14851                         }\r
14852 \r
14853                         return t.add(c);\r
14854                 },\r
14855 \r
14856                 createToolbar : function(id, s, cc) {\r
14857                         var c, t = this, cls;\r
14858 \r
14859                         id = t.prefix + id;\r
14860                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;\r
14861                         c = new cls(id, s, t.editor);\r
14862 \r
14863                         if (t.get(id))\r
14864                                 return null;\r
14865 \r
14866                         return t.add(c);\r
14867                 },\r
14868                 \r
14869                 createToolbarGroup : function(id, s, cc) {\r
14870                         var c, t = this, cls;\r
14871                         id = t.prefix + id;\r
14872                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;\r
14873                         c = new cls(id, s, t.editor);\r
14874                         \r
14875                         if (t.get(id))\r
14876                                 return null;\r
14877                         \r
14878                         return t.add(c);\r
14879                 },\r
14880 \r
14881                 createSeparator : function(cc) {\r
14882                         var cls = cc || this._cls.separator || tinymce.ui.Separator;\r
14883 \r
14884                         return new cls();\r
14885                 },\r
14886 \r
14887                 setControlType : function(n, c) {\r
14888                         return this._cls[n.toLowerCase()] = c;\r
14889                 },\r
14890         \r
14891                 destroy : function() {\r
14892                         each(this.controls, function(c) {\r
14893                                 c.destroy();\r
14894                         });\r
14895 \r
14896                         this.controls = null;\r
14897                 }\r
14898         });\r
14899 })(tinymce);\r
14900 \r
14901 (function(tinymce) {\r
14902         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;\r
14903 \r
14904         tinymce.create('tinymce.WindowManager', {\r
14905                 WindowManager : function(ed) {\r
14906                         var t = this;\r
14907 \r
14908                         t.editor = ed;\r
14909                         t.onOpen = new Dispatcher(t);\r
14910                         t.onClose = new Dispatcher(t);\r
14911                         t.params = {};\r
14912                         t.features = {};\r
14913                 },\r
14914 \r
14915                 open : function(s, p) {\r
14916                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;\r
14917 \r
14918                         // Default some options\r
14919                         s = s || {};\r
14920                         p = p || {};\r
14921                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window\r
14922                         sh = isOpera ? vp.h : screen.height;\r
14923                         s.name = s.name || 'mc_' + new Date().getTime();\r
14924                         s.width = parseInt(s.width || 320);\r
14925                         s.height = parseInt(s.height || 240);\r
14926                         s.resizable = true;\r
14927                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);\r
14928                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);\r
14929                         p.inline = false;\r
14930                         p.mce_width = s.width;\r
14931                         p.mce_height = s.height;\r
14932                         p.mce_auto_focus = s.auto_focus;\r
14933 \r
14934                         if (mo) {\r
14935                                 if (isIE) {\r
14936                                         s.center = true;\r
14937                                         s.help = false;\r
14938                                         s.dialogWidth = s.width + 'px';\r
14939                                         s.dialogHeight = s.height + 'px';\r
14940                                         s.scroll = s.scrollbars || false;\r
14941                                 }\r
14942                         }\r
14943 \r
14944                         // Build features string\r
14945                         each(s, function(v, k) {\r
14946                                 if (tinymce.is(v, 'boolean'))\r
14947                                         v = v ? 'yes' : 'no';\r
14948 \r
14949                                 if (!/^(name|url)$/.test(k)) {\r
14950                                         if (isIE && mo)\r
14951                                                 f += (f ? ';' : '') + k + ':' + v;\r
14952                                         else\r
14953                                                 f += (f ? ',' : '') + k + '=' + v;\r
14954                                 }\r
14955                         });\r
14956 \r
14957                         t.features = s;\r
14958                         t.params = p;\r
14959                         t.onOpen.dispatch(t, s, p);\r
14960 \r
14961                         u = s.url || s.file;\r
14962                         u = tinymce._addVer(u);\r
14963 \r
14964                         try {\r
14965                                 if (isIE && mo) {\r
14966                                         w = 1;\r
14967                                         window.showModalDialog(u, window, f);\r
14968                                 } else\r
14969                                         w = window.open(u, s.name, f);\r
14970                         } catch (ex) {\r
14971                                 // Ignore\r
14972                         }\r
14973 \r
14974                         if (!w)\r
14975                                 alert(t.editor.getLang('popup_blocked'));\r
14976                 },\r
14977 \r
14978                 close : function(w) {\r
14979                         w.close();\r
14980                         this.onClose.dispatch(this);\r
14981                 },\r
14982 \r
14983                 createInstance : function(cl, a, b, c, d, e) {\r
14984                         var f = tinymce.resolve(cl);\r
14985 \r
14986                         return new f(a, b, c, d, e);\r
14987                 },\r
14988 \r
14989                 confirm : function(t, cb, s, w) {\r
14990                         w = w || window;\r
14991 \r
14992                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));\r
14993                 },\r
14994 \r
14995                 alert : function(tx, cb, s, w) {\r
14996                         var t = this;\r
14997 \r
14998                         w = w || window;\r
14999                         w.alert(t._decode(t.editor.getLang(tx, tx)));\r
15000 \r
15001                         if (cb)\r
15002                                 cb.call(s || t);\r
15003                 },\r
15004 \r
15005                 resizeBy : function(dw, dh, win) {\r
15006                         win.resizeBy(dw, dh);\r
15007                 },\r
15008 \r
15009                 // Internal functions\r
15010 \r
15011                 _decode : function(s) {\r
15012                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');\r
15013                 }\r
15014         });\r
15015 }(tinymce));\r
15016 (function(tinymce) {\r
15017         tinymce.Formatter = function(ed) {\r
15018                 var formats = {},\r
15019                         each = tinymce.each,\r
15020                         dom = ed.dom,\r
15021                         selection = ed.selection,\r
15022                         TreeWalker = tinymce.dom.TreeWalker,\r
15023                         rangeUtils = new tinymce.dom.RangeUtils(dom),\r
15024                         isValid = ed.schema.isValidChild,\r
15025                         isBlock = dom.isBlock,\r
15026                         forcedRootBlock = ed.settings.forced_root_block,\r
15027                         nodeIndex = dom.nodeIndex,\r
15028                         INVISIBLE_CHAR = '\uFEFF',\r
15029                         MCE_ATTR_RE = /^(src|href|style)$/,\r
15030                         FALSE = false,\r
15031                         TRUE = true,\r
15032                         undefined;\r
15033 \r
15034                 function isArray(obj) {\r
15035                         return obj instanceof Array;\r
15036                 };\r
15037 \r
15038                 function getParents(node, selector) {\r
15039                         return dom.getParents(node, selector, dom.getRoot());\r
15040                 };\r
15041 \r
15042                 function isCaretNode(node) {\r
15043                         return node.nodeType === 1 && node.id === '_mce_caret';\r
15044                 };\r
15045 \r
15046                 // Public functions\r
15047 \r
15048                 function get(name) {\r
15049                         return name ? formats[name] : formats;\r
15050                 };\r
15051 \r
15052                 function register(name, format) {\r
15053                         if (name) {\r
15054                                 if (typeof(name) !== 'string') {\r
15055                                         each(name, function(format, name) {\r
15056                                                 register(name, format);\r
15057                                         });\r
15058                                 } else {\r
15059                                         // Force format into array and add it to internal collection\r
15060                                         format = format.length ? format : [format];\r
15061 \r
15062                                         each(format, function(format) {\r
15063                                                 // Set deep to false by default on selector formats this to avoid removing\r
15064                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs\r
15065                                                 if (format.deep === undefined)\r
15066                                                         format.deep = !format.selector;\r
15067 \r
15068                                                 // Default to true\r
15069                                                 if (format.split === undefined)\r
15070                                                         format.split = !format.selector || format.inline;\r
15071 \r
15072                                                 // Default to true\r
15073                                                 if (format.remove === undefined && format.selector && !format.inline)\r
15074                                                         format.remove = 'none';\r
15075 \r
15076                                                 // Mark format as a mixed format inline + block level\r
15077                                                 if (format.selector && format.inline) {\r
15078                                                         format.mixed = true;\r
15079                                                         format.block_expand = true;\r
15080                                                 }\r
15081 \r
15082                                                 // Split classes if needed\r
15083                                                 if (typeof(format.classes) === 'string')\r
15084                                                         format.classes = format.classes.split(/\s+/);\r
15085                                         });\r
15086 \r
15087                                         formats[name] = format;\r
15088                                 }\r
15089                         }\r
15090                 };\r
15091 \r
15092                 var getTextDecoration = function(node) {\r
15093                         var decoration;\r
15094 \r
15095                         ed.dom.getParent(node, function(n) {\r
15096                                 decoration = ed.dom.getStyle(n, 'text-decoration');\r
15097                                 return decoration && decoration !== 'none';\r
15098                         });\r
15099 \r
15100                         return decoration;\r
15101                 };\r
15102 \r
15103                 var processUnderlineAndColor = function(node) {\r
15104                         var textDecoration;\r
15105                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {\r
15106                                 textDecoration = getTextDecoration(node.parentNode);\r
15107                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {\r
15108                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);\r
15109                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {\r
15110                                         ed.dom.setStyle(node, 'text-decoration', null);\r
15111                                 }\r
15112                         }\r
15113                 };\r
15114 \r
15115                 function apply(name, vars, node) {\r
15116                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();\r
15117 \r
15118                         function setElementFormat(elm, fmt) {\r
15119                                 fmt = fmt || format;\r
15120 \r
15121                                 if (elm) {\r
15122                                         if (fmt.onformat) {\r
15123                                                 fmt.onformat(elm, fmt, vars, node);\r
15124                                         }\r
15125 \r
15126                                         each(fmt.styles, function(value, name) {\r
15127                                                 dom.setStyle(elm, name, replaceVars(value, vars));\r
15128                                         });\r
15129 \r
15130                                         each(fmt.attributes, function(value, name) {\r
15131                                                 dom.setAttrib(elm, name, replaceVars(value, vars));\r
15132                                         });\r
15133 \r
15134                                         each(fmt.classes, function(value) {\r
15135                                                 value = replaceVars(value, vars);\r
15136 \r
15137                                                 if (!dom.hasClass(elm, value))\r
15138                                                         dom.addClass(elm, value);\r
15139                                         });\r
15140                                 }\r
15141                         };\r
15142                         function adjustSelectionToVisibleSelection() {\r
15143                                 function findSelectionEnd(start, end) {\r
15144                                         var walker = new TreeWalker(end);\r
15145                                         for (node = walker.current(); node; node = walker.prev()) {\r
15146                                                 if (node.childNodes.length > 1 || node == start) {\r
15147                                                         return node;\r
15148                                                 }\r
15149                                         }\r
15150                                 };\r
15151 \r
15152                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection\r
15153                                 // as this isn't visible to the user.\r
15154                                 var rng = ed.selection.getRng();\r
15155                                 var start = rng.startContainer;\r
15156                                 var end = rng.endContainer;\r
15157 \r
15158                                 if (start != end && rng.endOffset == 0) {\r
15159                                         var newEnd = findSelectionEnd(start, end);\r
15160                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;\r
15161 \r
15162                                         rng.setEnd(newEnd, endOffset);\r
15163                                 }\r
15164 \r
15165                                 return rng;\r
15166                         }\r
15167                         \r
15168                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){\r
15169                                 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;\r
15170                                 \r
15171                                 // find the index of the first child list.\r
15172                                 each(node.childNodes, function(n, index) {\r
15173                                         if (n.nodeName === "UL" || n.nodeName === "OL") {\r
15174                                                 listIndex = index;\r
15175                                                 list = n;\r
15176                                                 return false;\r
15177                                         }\r
15178                                 });\r
15179                                 \r
15180                                 // get the index of the bookmarks\r
15181                                 each(node.childNodes, function(n, index) {\r
15182                                         if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {\r
15183                                                 if (n.id == bookmark.id + "_start") {\r
15184                                                         startIndex = index;\r
15185                                                 } else if (n.id == bookmark.id + "_end") {\r
15186                                                         endIndex = index;\r
15187                                                 }\r
15188                                         }\r
15189                                 });\r
15190                                 \r
15191                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally\r
15192                                 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {\r
15193                                         each(tinymce.grep(node.childNodes), process);\r
15194                                         return 0;\r
15195                                 } else {\r
15196                                         currentWrapElm = wrapElm.cloneNode(FALSE);\r
15197                                         \r
15198                                         // create a list of the nodes on the same side of the list as the selection\r
15199                                         each(tinymce.grep(node.childNodes), function(n, index) {\r
15200                                                 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {\r
15201                                                         nodes.push(n); \r
15202                                                         n.parentNode.removeChild(n);\r
15203                                                 }\r
15204                                         });\r
15205                                         \r
15206                                         // insert the wrapping element either before or after the list.\r
15207                                         if (startIndex < listIndex) {\r
15208                                                 node.insertBefore(currentWrapElm, list);\r
15209                                         } else if (startIndex > listIndex) {\r
15210                                                 node.insertBefore(currentWrapElm, list.nextSibling);\r
15211                                         }\r
15212                                         \r
15213                                         // add the new nodes to the list.\r
15214                                         newWrappers.push(currentWrapElm);\r
15215 \r
15216                                         each(nodes, function(node) {\r
15217                                                 currentWrapElm.appendChild(node);\r
15218                                         });\r
15219 \r
15220                                         return currentWrapElm;\r
15221                                 }\r
15222                         };\r
15223                         \r
15224                         function applyRngStyle(rng, bookmark, node_specific) {\r
15225                                 var newWrappers = [], wrapName, wrapElm;\r
15226 \r
15227                                 // Setup wrapper element\r
15228                                 wrapName = format.inline || format.block;\r
15229                                 wrapElm = dom.create(wrapName);\r
15230                                 setElementFormat(wrapElm);\r
15231 \r
15232                                 rangeUtils.walk(rng, function(nodes) {\r
15233                                         var currentWrapElm;\r
15234 \r
15235                                         function process(node) {\r
15236                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;\r
15237 \r
15238                                                 // Stop wrapping on br elements\r
15239                                                 if (isEq(nodeName, 'br')) {\r
15240                                                         currentWrapElm = 0;\r
15241 \r
15242                                                         // Remove any br elements when we wrap things\r
15243                                                         if (format.block)\r
15244                                                                 dom.remove(node);\r
15245 \r
15246                                                         return;\r
15247                                                 }\r
15248 \r
15249                                                 // If node is wrapper type\r
15250                                                 if (format.wrapper && matchNode(node, name, vars)) {\r
15251                                                         currentWrapElm = 0;\r
15252                                                         return;\r
15253                                                 }\r
15254 \r
15255                                                 // Can we rename the block\r
15256                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {\r
15257                                                         node = dom.rename(node, wrapName);\r
15258                                                         setElementFormat(node);\r
15259                                                         newWrappers.push(node);\r
15260                                                         currentWrapElm = 0;\r
15261                                                         return;\r
15262                                                 }\r
15263 \r
15264                                                 // Handle selector patterns\r
15265                                                 if (format.selector) {\r
15266                                                         // Look for matching formats\r
15267                                                         each(formatList, function(format) {\r
15268                                                                 // Check collapsed state if it exists\r
15269                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {\r
15270                                                                         return;\r
15271                                                                 }\r
15272 \r
15273                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {\r
15274                                                                         setElementFormat(node, format);\r
15275                                                                         found = true;\r
15276                                                                 }\r
15277                                                         });\r
15278 \r
15279                                                         // Continue processing if a selector match wasn't found and a inline element is defined\r
15280                                                         if (!format.inline || found) {\r
15281                                                                 currentWrapElm = 0;\r
15282                                                                 return;\r
15283                                                         }\r
15284                                                 }\r
15285 \r
15286                                                 // Is it valid to wrap this item\r
15287                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
15288                                                                 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {\r
15289                                                         // Start wrapping\r
15290                                                         if (!currentWrapElm) {\r
15291                                                                 // Wrap the node\r
15292                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);\r
15293                                                                 node.parentNode.insertBefore(currentWrapElm, node);\r
15294                                                                 newWrappers.push(currentWrapElm);\r
15295                                                         }\r
15296 \r
15297                                                         currentWrapElm.appendChild(node);\r
15298                                                 } else if (nodeName == 'li' && bookmark) {\r
15299                                                         // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.\r
15300                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);\r
15301                                                 } else {\r
15302                                                         // Start a new wrapper for possible children\r
15303                                                         currentWrapElm = 0;\r
15304 \r
15305                                                         each(tinymce.grep(node.childNodes), process);\r
15306 \r
15307                                                         // End the last wrapper\r
15308                                                         currentWrapElm = 0;\r
15309                                                 }\r
15310                                         };\r
15311 \r
15312                                         // Process siblings from range\r
15313                                         each(nodes, process);\r
15314                                 });\r
15315 \r
15316                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link\r
15317                                 if (format.wrap_links === false) {\r
15318                                         each(newWrappers, function(node) {\r
15319                                                 function process(node) {\r
15320                                                         var i, currentWrapElm, children;\r
15321 \r
15322                                                         if (node.nodeName === 'A') {\r
15323                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);\r
15324                                                                 newWrappers.push(currentWrapElm);\r
15325 \r
15326                                                                 children = tinymce.grep(node.childNodes);\r
15327                                                                 for (i = 0; i < children.length; i++)\r
15328                                                                         currentWrapElm.appendChild(children[i]);\r
15329 \r
15330                                                                 node.appendChild(currentWrapElm);\r
15331                                                         }\r
15332 \r
15333                                                         each(tinymce.grep(node.childNodes), process);\r
15334                                                 };\r
15335 \r
15336                                                 process(node);\r
15337                                         });\r
15338                                 }\r
15339 \r
15340                                 // Cleanup\r
15341                                 each(newWrappers, function(node) {\r
15342                                         var childCount;\r
15343 \r
15344                                         function getChildCount(node) {\r
15345                                                 var count = 0;\r
15346 \r
15347                                                 each(node.childNodes, function(node) {\r
15348                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))\r
15349                                                                 count++;\r
15350                                                 });\r
15351 \r
15352                                                 return count;\r
15353                                         };\r
15354 \r
15355                                         function mergeStyles(node) {\r
15356                                                 var child, clone;\r
15357 \r
15358                                                 each(node.childNodes, function(node) {\r
15359                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {\r
15360                                                                 child = node;\r
15361                                                                 return FALSE; // break loop\r
15362                                                         }\r
15363                                                 });\r
15364 \r
15365                                                 // If child was found and of the same type as the current node\r
15366                                                 if (child && matchName(child, format)) {\r
15367                                                         clone = child.cloneNode(FALSE);\r
15368                                                         setElementFormat(clone);\r
15369 \r
15370                                                         dom.replace(clone, node, TRUE);\r
15371                                                         dom.remove(child, 1);\r
15372                                                 }\r
15373 \r
15374                                                 return clone || node;\r
15375                                         };\r
15376 \r
15377                                         childCount = getChildCount(node);\r
15378 \r
15379                                         // Remove empty nodes but only if there is multiple wrappers and they are not block\r
15380                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at\r
15381                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {\r
15382                                                 dom.remove(node, 1);\r
15383                                                 return;\r
15384                                         }\r
15385 \r
15386                                         if (format.inline || format.wrapper) {\r
15387                                                 // Merges the current node with it's children of similar type to reduce the number of elements\r
15388                                                 if (!format.exact && childCount === 1)\r
15389                                                         node = mergeStyles(node);\r
15390 \r
15391                                                 // Remove/merge children\r
15392                                                 each(formatList, function(format) {\r
15393                                                         // Merge all children of similar type will move styles from child to parent\r
15394                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>\r
15395                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>\r
15396                                                         each(dom.select(format.inline, node), function(child) {\r
15397                                                                 var parent;\r
15398 \r
15399                                                                 // When wrap_links is set to false we don't want\r
15400                                                                 // to remove the format on children within links\r
15401                                                                 if (format.wrap_links === false) {\r
15402                                                                         parent = child.parentNode;\r
15403 \r
15404                                                                         do {\r
15405                                                                                 if (parent.nodeName === 'A')\r
15406                                                                                         return;\r
15407                                                                         } while (parent = parent.parentNode);\r
15408                                                                 }\r
15409 \r
15410                                                                 removeFormat(format, vars, child, format.exact ? child : null);\r
15411                                                         });\r
15412                                                 });\r
15413 \r
15414                                                 // Remove child if direct parent is of same type\r
15415                                                 if (matchNode(node.parentNode, name, vars)) {\r
15416                                                         dom.remove(node, 1);\r
15417                                                         node = 0;\r
15418                                                         return TRUE;\r
15419                                                 }\r
15420 \r
15421                                                 // Look for parent with similar style format\r
15422                                                 if (format.merge_with_parents) {\r
15423                                                         dom.getParent(node.parentNode, function(parent) {\r
15424                                                                 if (matchNode(parent, name, vars)) {\r
15425                                                                         dom.remove(node, 1);\r
15426                                                                         node = 0;\r
15427                                                                         return TRUE;\r
15428                                                                 }\r
15429                                                         });\r
15430                                                 }\r
15431 \r
15432                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>\r
15433                                                 if (node && format.merge_siblings !== false) {\r
15434                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);\r
15435                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));\r
15436                                                 }\r
15437                                         }\r
15438                                 });\r
15439                         };\r
15440 \r
15441                         if (format) {\r
15442                                 if (node) {\r
15443                                         if (node.nodeType) {\r
15444                                                 rng = dom.createRng();\r
15445                                                 rng.setStartBefore(node);\r
15446                                                 rng.setEndAfter(node);\r
15447                                                 applyRngStyle(expandRng(rng, formatList), null, true);\r
15448                                         } else {\r
15449                                                 applyRngStyle(node, null, true);\r
15450                                         }\r
15451                                 } else {\r
15452                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
15453                                                 // Obtain selection node before selection is unselected by applyRngStyle()\r
15454                                                 var curSelNode = ed.selection.getNode();\r
15455 \r
15456                                                 // Apply formatting to selection\r
15457                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());\r
15458                                                 bookmark = selection.getBookmark();\r
15459                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);\r
15460 \r
15461                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.\r
15462                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {\r
15463                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');\r
15464                                                         processUnderlineAndColor(curSelNode);\r
15465                                                 }\r
15466 \r
15467                                                 selection.moveToBookmark(bookmark);\r
15468                                                 moveStart(selection.getRng(TRUE));\r
15469                                                 ed.nodeChanged();\r
15470                                         } else\r
15471                                                 performCaretAction('apply', name, vars);\r
15472                                 }\r
15473                         }\r
15474                 };\r
15475 \r
15476                 function remove(name, vars, node) {\r
15477                         var formatList = get(name), format = formatList[0], bookmark, i, rng;\r
15478 \r
15479                         // Merges the styles for each node\r
15480                         function process(node) {\r
15481                                 var children, i, l;\r
15482 \r
15483                                 // Grab the children first since the nodelist might be changed\r
15484                                 children = tinymce.grep(node.childNodes);\r
15485 \r
15486                                 // Process current node\r
15487                                 for (i = 0, l = formatList.length; i < l; i++) {\r
15488                                         if (removeFormat(formatList[i], vars, node, node))\r
15489                                                 break;\r
15490                                 }\r
15491 \r
15492                                 // Process the children\r
15493                                 if (format.deep) {\r
15494                                         for (i = 0, l = children.length; i < l; i++)\r
15495                                                 process(children[i]);\r
15496                                 }\r
15497                         };\r
15498 \r
15499                         function findFormatRoot(container) {\r
15500                                 var formatRoot;\r
15501 \r
15502                                 // Find format root\r
15503                                 each(getParents(container.parentNode).reverse(), function(parent) {\r
15504                                         var format;\r
15505 \r
15506                                         // Find format root element\r
15507                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {\r
15508                                                 // Is the node matching the format we are looking for\r
15509                                                 format = matchNode(parent, name, vars);\r
15510                                                 if (format && format.split !== false)\r
15511                                                         formatRoot = parent;\r
15512                                         }\r
15513                                 });\r
15514 \r
15515                                 return formatRoot;\r
15516                         };\r
15517 \r
15518                         function wrapAndSplit(format_root, container, target, split) {\r
15519                                 var parent, clone, lastClone, firstClone, i, formatRootParent;\r
15520 \r
15521                                 // Format root found then clone formats and split it\r
15522                                 if (format_root) {\r
15523                                         formatRootParent = format_root.parentNode;\r
15524 \r
15525                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {\r
15526                                                 clone = parent.cloneNode(FALSE);\r
15527 \r
15528                                                 for (i = 0; i < formatList.length; i++) {\r
15529                                                         if (removeFormat(formatList[i], vars, clone, clone)) {\r
15530                                                                 clone = 0;\r
15531                                                                 break;\r
15532                                                         }\r
15533                                                 }\r
15534 \r
15535                                                 // Build wrapper node\r
15536                                                 if (clone) {\r
15537                                                         if (lastClone)\r
15538                                                                 clone.appendChild(lastClone);\r
15539 \r
15540                                                         if (!firstClone)\r
15541                                                                 firstClone = clone;\r
15542 \r
15543                                                         lastClone = clone;\r
15544                                                 }\r
15545                                         }\r
15546 \r
15547                                         // Never split block elements if the format is mixed\r
15548                                         if (split && (!format.mixed || !isBlock(format_root)))\r
15549                                                 container = dom.split(format_root, container);\r
15550 \r
15551                                         // Wrap container in cloned formats\r
15552                                         if (lastClone) {\r
15553                                                 target.parentNode.insertBefore(lastClone, target);\r
15554                                                 firstClone.appendChild(target);\r
15555                                         }\r
15556                                 }\r
15557 \r
15558                                 return container;\r
15559                         };\r
15560 \r
15561                         function splitToFormatRoot(container) {\r
15562                                 return wrapAndSplit(findFormatRoot(container), container, container, true);\r
15563                         };\r
15564 \r
15565                         function unwrap(start) {\r
15566                                 var node = dom.get(start ? '_start' : '_end'),\r
15567                                         out = node[start ? 'firstChild' : 'lastChild'];\r
15568 \r
15569                                 // If the end is placed within the start the result will be removed\r
15570                                 // So this checks if the out node is a bookmark node if it is it\r
15571                                 // checks for another more suitable node\r
15572                                 if (isBookmarkNode(out))\r
15573                                         out = out[start ? 'firstChild' : 'lastChild'];\r
15574 \r
15575                                 dom.remove(node, true);\r
15576 \r
15577                                 return out;\r
15578                         };\r
15579 \r
15580                         function removeRngStyle(rng) {\r
15581                                 var startContainer, endContainer;\r
15582 \r
15583                                 rng = expandRng(rng, formatList, TRUE);\r
15584 \r
15585                                 if (format.split) {\r
15586                                         startContainer = getContainer(rng, TRUE);\r
15587                                         endContainer = getContainer(rng);\r
15588 \r
15589                                         if (startContainer != endContainer) {\r
15590                                                 // Wrap start/end nodes in span element since these might be cloned/moved\r
15591                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});\r
15592                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});\r
15593 \r
15594                                                 // Split start/end\r
15595                                                 splitToFormatRoot(startContainer);\r
15596                                                 splitToFormatRoot(endContainer);\r
15597 \r
15598                                                 // Unwrap start/end to get real elements again\r
15599                                                 startContainer = unwrap(TRUE);\r
15600                                                 endContainer = unwrap();\r
15601                                         } else\r
15602                                                 startContainer = endContainer = splitToFormatRoot(startContainer);\r
15603 \r
15604                                         // Update range positions since they might have changed after the split operations\r
15605                                         rng.startContainer = startContainer.parentNode;\r
15606                                         rng.startOffset = nodeIndex(startContainer);\r
15607                                         rng.endContainer = endContainer.parentNode;\r
15608                                         rng.endOffset = nodeIndex(endContainer) + 1;\r
15609                                 }\r
15610 \r
15611                                 // Remove items between start/end\r
15612                                 rangeUtils.walk(rng, function(nodes) {\r
15613                                         each(nodes, function(node) {\r
15614                                                 process(node);\r
15615 \r
15616                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.\r
15617                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {\r
15618                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);\r
15619                                                 }\r
15620                                         });\r
15621                                 });\r
15622                         };\r
15623 \r
15624                         // Handle node\r
15625                         if (node) {\r
15626                                 if (node.nodeType) {\r
15627                                         rng = dom.createRng();\r
15628                                         rng.setStartBefore(node);\r
15629                                         rng.setEndAfter(node);\r
15630                                         removeRngStyle(rng);\r
15631                                 } else {\r
15632                                         removeRngStyle(node);\r
15633                                 }\r
15634 \r
15635                                 return;\r
15636                         }\r
15637 \r
15638                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
15639                                 bookmark = selection.getBookmark();\r
15640                                 removeRngStyle(selection.getRng(TRUE));\r
15641                                 selection.moveToBookmark(bookmark);\r
15642 \r
15643                                 // 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\r
15644                                 if (format.inline && match(name, vars, selection.getStart())) {\r
15645                                         moveStart(selection.getRng(true));\r
15646                                 }\r
15647 \r
15648                                 ed.nodeChanged();\r
15649                         } else\r
15650                                 performCaretAction('remove', name, vars);\r
15651 \r
15652                         // When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width\r
15653                         if (tinymce.isWebKit) {\r
15654                                 ed.execCommand('mceCleanup');\r
15655                         }\r
15656                 };\r
15657 \r
15658                 function toggle(name, vars, node) {\r
15659                         var fmt = get(name);\r
15660 \r
15661                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))\r
15662                                 remove(name, vars, node);\r
15663                         else\r
15664                                 apply(name, vars, node);\r
15665                 };\r
15666 \r
15667                 function matchNode(node, name, vars, similar) {\r
15668                         var formatList = get(name), format, i, classes;\r
15669 \r
15670                         function matchItems(node, format, item_name) {\r
15671                                 var key, value, items = format[item_name], i;\r
15672 \r
15673                                 // Custom match\r
15674                                 if (format.onmatch) {\r
15675                                         return format.onmatch(node, format, item_name);\r
15676                                 }\r
15677 \r
15678                                 // Check all items\r
15679                                 if (items) {\r
15680                                         // Non indexed object\r
15681                                         if (items.length === undefined) {\r
15682                                                 for (key in items) {\r
15683                                                         if (items.hasOwnProperty(key)) {\r
15684                                                                 if (item_name === 'attributes')\r
15685                                                                         value = dom.getAttrib(node, key);\r
15686                                                                 else\r
15687                                                                         value = getStyle(node, key);\r
15688 \r
15689                                                                 if (similar && !value && !format.exact)\r
15690                                                                         return;\r
15691 \r
15692                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))\r
15693                                                                         return;\r
15694                                                         }\r
15695                                                 }\r
15696                                         } else {\r
15697                                                 // Only one match needed for indexed arrays\r
15698                                                 for (i = 0; i < items.length; i++) {\r
15699                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))\r
15700                                                                 return format;\r
15701                                                 }\r
15702                                         }\r
15703                                 }\r
15704 \r
15705                                 return format;\r
15706                         };\r
15707 \r
15708                         if (formatList && node) {\r
15709                                 // Check each format in list\r
15710                                 for (i = 0; i < formatList.length; i++) {\r
15711                                         format = formatList[i];\r
15712 \r
15713                                         // Name name, attributes, styles and classes\r
15714                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {\r
15715                                                 // Match classes\r
15716                                                 if (classes = format.classes) {\r
15717                                                         for (i = 0; i < classes.length; i++) {\r
15718                                                                 if (!dom.hasClass(node, classes[i]))\r
15719                                                                         return;\r
15720                                                         }\r
15721                                                 }\r
15722 \r
15723                                                 return format;\r
15724                                         }\r
15725                                 }\r
15726                         }\r
15727                 };\r
15728 \r
15729                 function match(name, vars, node) {\r
15730                         var startNode;\r
15731 \r
15732                         function matchParents(node) {\r
15733                                 // Find first node with similar format settings\r
15734                                 node = dom.getParent(node, function(node) {\r
15735                                         return !!matchNode(node, name, vars, true);\r
15736                                 });\r
15737 \r
15738                                 // Do an exact check on the similar format element\r
15739                                 return matchNode(node, name, vars);\r
15740                         };\r
15741 \r
15742                         // Check specified node\r
15743                         if (node)\r
15744                                 return matchParents(node);\r
15745 \r
15746                         // Check selected node\r
15747                         node = selection.getNode();\r
15748                         if (matchParents(node))\r
15749                                 return TRUE;\r
15750 \r
15751                         // Check start node if it's different\r
15752                         startNode = selection.getStart();\r
15753                         if (startNode != node) {\r
15754                                 if (matchParents(startNode))\r
15755                                         return TRUE;\r
15756                         }\r
15757 \r
15758                         return FALSE;\r
15759                 };\r
15760 \r
15761                 function matchAll(names, vars) {\r
15762                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;\r
15763 \r
15764                         // Check start of selection for formats\r
15765                         startElement = selection.getStart();\r
15766                         dom.getParent(startElement, function(node) {\r
15767                                 var i, name;\r
15768 \r
15769                                 for (i = 0; i < names.length; i++) {\r
15770                                         name = names[i];\r
15771 \r
15772                                         if (!checkedMap[name] && matchNode(node, name, vars)) {\r
15773                                                 checkedMap[name] = true;\r
15774                                                 matchedFormatNames.push(name);\r
15775                                         }\r
15776                                 }\r
15777                         });\r
15778 \r
15779                         return matchedFormatNames;\r
15780                 };\r
15781 \r
15782                 function canApply(name) {\r
15783                         var formatList = get(name), startNode, parents, i, x, selector;\r
15784 \r
15785                         if (formatList) {\r
15786                                 startNode = selection.getStart();\r
15787                                 parents = getParents(startNode);\r
15788 \r
15789                                 for (x = formatList.length - 1; x >= 0; x--) {\r
15790                                         selector = formatList[x].selector;\r
15791 \r
15792                                         // Format is not selector based, then always return TRUE\r
15793                                         if (!selector)\r
15794                                                 return TRUE;\r
15795 \r
15796                                         for (i = parents.length - 1; i >= 0; i--) {\r
15797                                                 if (dom.is(parents[i], selector))\r
15798                                                         return TRUE;\r
15799                                         }\r
15800                                 }\r
15801                         }\r
15802 \r
15803                         return FALSE;\r
15804                 };\r
15805 \r
15806                 // Expose to public\r
15807                 tinymce.extend(this, {\r
15808                         get : get,\r
15809                         register : register,\r
15810                         apply : apply,\r
15811                         remove : remove,\r
15812                         toggle : toggle,\r
15813                         match : match,\r
15814                         matchAll : matchAll,\r
15815                         matchNode : matchNode,\r
15816                         canApply : canApply\r
15817                 });\r
15818 \r
15819                 // Private functions\r
15820 \r
15821                 function matchName(node, format) {\r
15822                         // Check for inline match\r
15823                         if (isEq(node, format.inline))\r
15824                                 return TRUE;\r
15825 \r
15826                         // Check for block match\r
15827                         if (isEq(node, format.block))\r
15828                                 return TRUE;\r
15829 \r
15830                         // Check for selector match\r
15831                         if (format.selector)\r
15832                                 return dom.is(node, format.selector);\r
15833                 };\r
15834 \r
15835                 function isEq(str1, str2) {\r
15836                         str1 = str1 || '';\r
15837                         str2 = str2 || '';\r
15838 \r
15839                         str1 = '' + (str1.nodeName || str1);\r
15840                         str2 = '' + (str2.nodeName || str2);\r
15841 \r
15842                         return str1.toLowerCase() == str2.toLowerCase();\r
15843                 };\r
15844 \r
15845                 function getStyle(node, name) {\r
15846                         var styleVal = dom.getStyle(node, name);\r
15847 \r
15848                         // Force the format to hex\r
15849                         if (name == 'color' || name == 'backgroundColor')\r
15850                                 styleVal = dom.toHex(styleVal);\r
15851 \r
15852                         // Opera will return bold as 700\r
15853                         if (name == 'fontWeight' && styleVal == 700)\r
15854                                 styleVal = 'bold';\r
15855 \r
15856                         return '' + styleVal;\r
15857                 };\r
15858 \r
15859                 function replaceVars(value, vars) {\r
15860                         if (typeof(value) != "string")\r
15861                                 value = value(vars);\r
15862                         else if (vars) {\r
15863                                 value = value.replace(/%(\w+)/g, function(str, name) {\r
15864                                         return vars[name] || str;\r
15865                                 });\r
15866                         }\r
15867 \r
15868                         return value;\r
15869                 };\r
15870 \r
15871                 function isWhiteSpaceNode(node) {\r
15872                         return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);\r
15873                 };\r
15874 \r
15875                 function wrap(node, name, attrs) {\r
15876                         var wrapper = dom.create(name, attrs);\r
15877 \r
15878                         node.parentNode.insertBefore(wrapper, node);\r
15879                         wrapper.appendChild(node);\r
15880 \r
15881                         return wrapper;\r
15882                 };\r
15883 \r
15884                 function expandRng(rng, format, remove) {\r
15885                         var startContainer = rng.startContainer,\r
15886                                 startOffset = rng.startOffset,\r
15887                                 endContainer = rng.endContainer,\r
15888                                 endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint;\r
15889 \r
15890                         // This function walks up the tree if there is no siblings before/after the node\r
15891                         function findParentContainer(start) {\r
15892                                 var container, parent, child, sibling, siblingName;\r
15893 \r
15894                                 container = parent = start ? startContainer : endContainer;\r
15895                                 siblingName = start ? 'previousSibling' : 'nextSibling';\r
15896                                 root = dom.getRoot();\r
15897 \r
15898                                 // If it's a text node and the offset is inside the text\r
15899                                 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {\r
15900                                         if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {\r
15901                                                 return container;\r
15902                                         }\r
15903                                 }\r
15904 \r
15905                                 for (;;) {\r
15906                                         // Stop expanding on block elements\r
15907                                         if (!format[0].block_expand && isBlock(parent))\r
15908                                                 return parent;\r
15909 \r
15910                                         // Walk left/right\r
15911                                         for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {\r
15912                                                 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {\r
15913                                                         return parent;\r
15914                                                 }\r
15915                                         }\r
15916 \r
15917                                         // Check if we can move up are we at root level or body level\r
15918                                         if (parent.parentNode == root) {\r
15919                                                 container = parent;\r
15920                                                 break;\r
15921                                         }\r
15922 \r
15923                                         parent = parent.parentNode;\r
15924                                 }\r
15925 \r
15926                                 return container;\r
15927                         };\r
15928 \r
15929                         // This function walks down the tree to find the leaf at the selection.\r
15930                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.\r
15931                         function findLeaf(node, offset) {\r
15932                                 if (offset === undefined)\r
15933                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
15934                                 while (node && node.hasChildNodes()) {\r
15935                                         node = node.childNodes[offset];\r
15936                                         if (node)\r
15937                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
15938                                 }\r
15939                                 return { node: node, offset: offset };\r
15940                         }\r
15941 \r
15942                         // If index based start position then resolve it\r
15943                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {\r
15944                                 lastIdx = startContainer.childNodes.length - 1;\r
15945                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];\r
15946 \r
15947                                 if (startContainer.nodeType == 3)\r
15948                                         startOffset = 0;\r
15949                         }\r
15950 \r
15951                         // If index based end position then resolve it\r
15952                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {\r
15953                                 lastIdx = endContainer.childNodes.length - 1;\r
15954                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];\r
15955 \r
15956                                 if (endContainer.nodeType == 3)\r
15957                                         endOffset = endContainer.nodeValue.length;\r
15958                         }\r
15959 \r
15960                         // Exclude bookmark nodes if possible\r
15961                         if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {\r
15962                                 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;\r
15963                                 startContainer = startContainer.nextSibling || startContainer;\r
15964 \r
15965                                 if (startContainer.nodeType == 3)\r
15966                                         startOffset = 0;\r
15967                         }\r
15968 \r
15969                         if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {\r
15970                                 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;\r
15971                                 endContainer = endContainer.previousSibling || endContainer;\r
15972 \r
15973                                 if (endContainer.nodeType == 3)\r
15974                                         endOffset = endContainer.length;\r
15975                         }\r
15976 \r
15977                         if (format[0].inline) {\r
15978                                 if (rng.collapsed) {\r
15979                                         function findWordEndPoint(container, offset, start) {\r
15980                                                 var walker, node, pos, lastTextNode;\r
15981 \r
15982                                                 function findSpace(node, offset) {\r
15983                                                         var pos, pos2, str = node.nodeValue;\r
15984 \r
15985                                                         if (typeof(offset) == "undefined") {\r
15986                                                                 offset = start ? str.length : 0;\r
15987                                                         }\r
15988 \r
15989                                                         if (start) {\r
15990                                                                 pos = str.lastIndexOf(' ', offset);\r
15991                                                                 pos2 = str.lastIndexOf('\u00a0', offset);\r
15992                                                                 pos = pos > pos2 ? pos : pos2;\r
15993 \r
15994                                                                 // Include the space on remove to avoid tag soup\r
15995                                                                 if (pos !== -1 && !remove) {\r
15996                                                                         pos++;\r
15997                                                                 }\r
15998                                                         } else {\r
15999                                                                 pos = str.indexOf(' ', offset);\r
16000                                                                 pos2 = str.indexOf('\u00a0', offset);\r
16001                                                                 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;\r
16002                                                         }\r
16003 \r
16004                                                         return pos;\r
16005                                                 };\r
16006 \r
16007                                                 if (container.nodeType === 3) {\r
16008                                                         pos = findSpace(container, offset);\r
16009 \r
16010                                                         if (pos !== -1) {\r
16011                                                                 return {container : container, offset : pos};\r
16012                                                         }\r
16013 \r
16014                                                         lastTextNode = container;\r
16015                                                 }\r
16016 \r
16017                                                 // Walk the nodes inside the block\r
16018                                                 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());\r
16019                                                 while (node = walker[start ? 'prev' : 'next']()) {\r
16020                                                         if (node.nodeType === 3) {\r
16021                                                                 lastTextNode = node;\r
16022                                                                 pos = findSpace(node);\r
16023 \r
16024                                                                 if (pos !== -1) {\r
16025                                                                         return {container : node, offset : pos};\r
16026                                                                 }\r
16027                                                         } else if (isBlock(node)) {\r
16028                                                                 break;\r
16029                                                         }\r
16030                                                 }\r
16031 \r
16032                                                 if (lastTextNode) {\r
16033                                                         if (start) {\r
16034                                                                 offset = 0;\r
16035                                                         } else {\r
16036                                                                 offset = lastTextNode.length;\r
16037                                                         }\r
16038 \r
16039                                                         return {container: lastTextNode, offset: offset};\r
16040                                                 }\r
16041                                         }\r
16042 \r
16043                                         // Expand left to closest word boundery\r
16044                                         endPoint = findWordEndPoint(startContainer, startOffset, true);\r
16045                                         if (endPoint) {\r
16046                                                 startContainer = endPoint.container;\r
16047                                                 startOffset = endPoint.offset;\r
16048                                         }\r
16049 \r
16050                                         // Expand right to closest word boundery\r
16051                                         endPoint = findWordEndPoint(endContainer, endOffset);\r
16052                                         if (endPoint) {\r
16053                                                 endContainer = endPoint.container;\r
16054                                                 endOffset = endPoint.offset;\r
16055                                         }\r
16056                                 }\r
16057 \r
16058                                 // Avoid applying formatting to a trailing space.\r
16059                                 leaf = findLeaf(endContainer, endOffset);\r
16060                                 if (leaf.node) {\r
16061                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)\r
16062                                                 leaf = findLeaf(leaf.node.previousSibling);\r
16063 \r
16064                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&\r
16065                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {\r
16066 \r
16067                                                 if (leaf.offset > 1) {\r
16068                                                         endContainer = leaf.node;\r
16069                                                         endContainer.splitText(leaf.offset - 1);\r
16070                                                 } else if (leaf.node.previousSibling) {\r
16071                                                         // TODO: Figure out why this is in here\r
16072                                                         //endContainer = leaf.node.previousSibling;\r
16073                                                 }\r
16074                                         }\r
16075                                 }\r
16076                         }\r
16077 \r
16078                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers\r
16079                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!\r
16080                         // This will reduce the number of wrapper elements that needs to be created\r
16081                         // Move start point up the tree\r
16082                         if (format[0].inline || format[0].block_expand) {\r
16083                                 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {\r
16084                                         startContainer = findParentContainer(true);\r
16085                                 }\r
16086 \r
16087                                 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {\r
16088                                         endContainer = findParentContainer();\r
16089                                 }\r
16090                         }\r
16091 \r
16092                         // Expand start/end container to matching selector\r
16093                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {\r
16094                                 function findSelectorEndPoint(container, sibling_name) {\r
16095                                         var parents, i, y, curFormat;\r
16096 \r
16097                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])\r
16098                                                 container = container[sibling_name];\r
16099 \r
16100                                         parents = getParents(container);\r
16101                                         for (i = 0; i < parents.length; i++) {\r
16102                                                 for (y = 0; y < format.length; y++) {\r
16103                                                         curFormat = format[y];\r
16104 \r
16105                                                         // If collapsed state is set then skip formats that doesn't match that\r
16106                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)\r
16107                                                                 continue;\r
16108 \r
16109                                                         if (dom.is(parents[i], curFormat.selector))\r
16110                                                                 return parents[i];\r
16111                                                 }\r
16112                                         }\r
16113 \r
16114                                         return container;\r
16115                                 };\r
16116 \r
16117                                 // Find new startContainer/endContainer if there is better one\r
16118                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');\r
16119                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');\r
16120                         }\r
16121 \r
16122                         // Expand start/end container to matching block element or text node\r
16123                         if (format[0].block || format[0].selector) {\r
16124                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {\r
16125                                         var node;\r
16126 \r
16127                                         // Expand to block of similar type\r
16128                                         if (!format[0].wrapper)\r
16129                                                 node = dom.getParent(container, format[0].block);\r
16130 \r
16131                                         // Expand to first wrappable block element or any block element\r
16132                                         if (!node)\r
16133                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);\r
16134 \r
16135                                         // Exclude inner lists from wrapping\r
16136                                         if (node && format[0].wrapper)\r
16137                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;\r
16138 \r
16139                                         // Didn't find a block element look for first/last wrappable element\r
16140                                         if (!node) {\r
16141                                                 node = container;\r
16142 \r
16143                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {\r
16144                                                         node = node[sibling_name];\r
16145 \r
16146                                                         // Break on BR but include it will be removed later on\r
16147                                                         // we can't remove it now since we need to check if it can be wrapped\r
16148                                                         if (isEq(node, 'br'))\r
16149                                                                 break;\r
16150                                                 }\r
16151                                         }\r
16152 \r
16153                                         return node || container;\r
16154                                 };\r
16155 \r
16156                                 // Find new startContainer/endContainer if there is better one\r
16157                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');\r
16158                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');\r
16159 \r
16160                                 // Non block element then try to expand up the leaf\r
16161                                 if (format[0].block) {\r
16162                                         if (!isBlock(startContainer))\r
16163                                                 startContainer = findParentContainer(true);\r
16164 \r
16165                                         if (!isBlock(endContainer))\r
16166                                                 endContainer = findParentContainer();\r
16167                                 }\r
16168                         }\r
16169 \r
16170                         // Setup index for startContainer\r
16171                         if (startContainer.nodeType == 1) {\r
16172                                 startOffset = nodeIndex(startContainer);\r
16173                                 startContainer = startContainer.parentNode;\r
16174                         }\r
16175 \r
16176                         // Setup index for endContainer\r
16177                         if (endContainer.nodeType == 1) {\r
16178                                 endOffset = nodeIndex(endContainer) + 1;\r
16179                                 endContainer = endContainer.parentNode;\r
16180                         }\r
16181 \r
16182                         // Return new range like object\r
16183                         return {\r
16184                                 startContainer : startContainer,\r
16185                                 startOffset : startOffset,\r
16186                                 endContainer : endContainer,\r
16187                                 endOffset : endOffset\r
16188                         };\r
16189                 }\r
16190 \r
16191                 function removeFormat(format, vars, node, compare_node) {\r
16192                         var i, attrs, stylesModified;\r
16193 \r
16194                         // Check if node matches format\r
16195                         if (!matchName(node, format))\r
16196                                 return FALSE;\r
16197 \r
16198                         // Should we compare with format attribs and styles\r
16199                         if (format.remove != 'all') {\r
16200                                 // Remove styles\r
16201                                 each(format.styles, function(value, name) {\r
16202                                         value = replaceVars(value, vars);\r
16203 \r
16204                                         // Indexed array\r
16205                                         if (typeof(name) === 'number') {\r
16206                                                 name = value;\r
16207                                                 compare_node = 0;\r
16208                                         }\r
16209 \r
16210                                         if (!compare_node || isEq(getStyle(compare_node, name), value))\r
16211                                                 dom.setStyle(node, name, '');\r
16212 \r
16213                                         stylesModified = 1;\r
16214                                 });\r
16215 \r
16216                                 // Remove style attribute if it's empty\r
16217                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {\r
16218                                         node.removeAttribute('style');\r
16219                                         node.removeAttribute('data-mce-style');\r
16220                                 }\r
16221 \r
16222                                 // Remove attributes\r
16223                                 each(format.attributes, function(value, name) {\r
16224                                         var valueOut;\r
16225 \r
16226                                         value = replaceVars(value, vars);\r
16227 \r
16228                                         // Indexed array\r
16229                                         if (typeof(name) === 'number') {\r
16230                                                 name = value;\r
16231                                                 compare_node = 0;\r
16232                                         }\r
16233 \r
16234                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {\r
16235                                                 // Keep internal classes\r
16236                                                 if (name == 'class') {\r
16237                                                         value = dom.getAttrib(node, name);\r
16238                                                         if (value) {\r
16239                                                                 // Build new class value where everything is removed except the internal prefixed classes\r
16240                                                                 valueOut = '';\r
16241                                                                 each(value.split(/\s+/), function(cls) {\r
16242                                                                         if (/mce\w+/.test(cls))\r
16243                                                                                 valueOut += (valueOut ? ' ' : '') + cls;\r
16244                                                                 });\r
16245 \r
16246                                                                 // We got some internal classes left\r
16247                                                                 if (valueOut) {\r
16248                                                                         dom.setAttrib(node, name, valueOut);\r
16249                                                                         return;\r
16250                                                                 }\r
16251                                                         }\r
16252                                                 }\r
16253 \r
16254                                                 // IE6 has a bug where the attribute doesn't get removed correctly\r
16255                                                 if (name == "class")\r
16256                                                         node.removeAttribute('className');\r
16257 \r
16258                                                 // Remove mce prefixed attributes\r
16259                                                 if (MCE_ATTR_RE.test(name))\r
16260                                                         node.removeAttribute('data-mce-' + name);\r
16261 \r
16262                                                 node.removeAttribute(name);\r
16263                                         }\r
16264                                 });\r
16265 \r
16266                                 // Remove classes\r
16267                                 each(format.classes, function(value) {\r
16268                                         value = replaceVars(value, vars);\r
16269 \r
16270                                         if (!compare_node || dom.hasClass(compare_node, value))\r
16271                                                 dom.removeClass(node, value);\r
16272                                 });\r
16273 \r
16274                                 // Check for non internal attributes\r
16275                                 attrs = dom.getAttribs(node);\r
16276                                 for (i = 0; i < attrs.length; i++) {\r
16277                                         if (attrs[i].nodeName.indexOf('_') !== 0)\r
16278                                                 return FALSE;\r
16279                                 }\r
16280                         }\r
16281 \r
16282                         // Remove the inline child if it's empty for example <b> or <span>\r
16283                         if (format.remove != 'none') {\r
16284                                 removeNode(node, format);\r
16285                                 return TRUE;\r
16286                         }\r
16287                 };\r
16288 \r
16289                 function removeNode(node, format) {\r
16290                         var parentNode = node.parentNode, rootBlockElm;\r
16291 \r
16292                         if (format.block) {\r
16293                                 if (!forcedRootBlock) {\r
16294                                         function find(node, next, inc) {\r
16295                                                 node = getNonWhiteSpaceSibling(node, next, inc);\r
16296 \r
16297                                                 return !node || (node.nodeName == 'BR' || isBlock(node));\r
16298                                         };\r
16299 \r
16300                                         // Append BR elements if needed before we remove the block\r
16301                                         if (isBlock(node) && !isBlock(parentNode)) {\r
16302                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))\r
16303                                                         node.insertBefore(dom.create('br'), node.firstChild);\r
16304 \r
16305                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))\r
16306                                                         node.appendChild(dom.create('br'));\r
16307                                         }\r
16308                                 } else {\r
16309                                         // Wrap the block in a forcedRootBlock if we are at the root of document\r
16310                                         if (parentNode == dom.getRoot()) {\r
16311                                                 if (!format.list_block || !isEq(node, format.list_block)) {\r
16312                                                         each(tinymce.grep(node.childNodes), function(node) {\r
16313                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {\r
16314                                                                         if (!rootBlockElm)\r
16315                                                                                 rootBlockElm = wrap(node, forcedRootBlock);\r
16316                                                                         else\r
16317                                                                                 rootBlockElm.appendChild(node);\r
16318                                                                 } else\r
16319                                                                         rootBlockElm = 0;\r
16320                                                         });\r
16321                                                 }\r
16322                                         }\r
16323                                 }\r
16324                         }\r
16325 \r
16326                         // Never remove nodes that isn't the specified inline element if a selector is specified too\r
16327                         if (format.selector && format.inline && !isEq(format.inline, node))\r
16328                                 return;\r
16329 \r
16330                         dom.remove(node, 1);\r
16331                 };\r
16332 \r
16333                 function getNonWhiteSpaceSibling(node, next, inc) {\r
16334                         if (node) {\r
16335                                 next = next ? 'nextSibling' : 'previousSibling';\r
16336 \r
16337                                 for (node = inc ? node : node[next]; node; node = node[next]) {\r
16338                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))\r
16339                                                 return node;\r
16340                                 }\r
16341                         }\r
16342                 };\r
16343 \r
16344                 function isBookmarkNode(node) {\r
16345                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';\r
16346                 };\r
16347 \r
16348                 function mergeSiblings(prev, next) {\r
16349                         var marker, sibling, tmpSibling;\r
16350 \r
16351                         function compareElements(node1, node2) {\r
16352                                 // Not the same name\r
16353                                 if (node1.nodeName != node2.nodeName)\r
16354                                         return FALSE;\r
16355 \r
16356                                 function getAttribs(node) {\r
16357                                         var attribs = {};\r
16358 \r
16359                                         each(dom.getAttribs(node), function(attr) {\r
16360                                                 var name = attr.nodeName.toLowerCase();\r
16361 \r
16362                                                 // Don't compare internal attributes or style\r
16363                                                 if (name.indexOf('_') !== 0 && name !== 'style')\r
16364                                                         attribs[name] = dom.getAttrib(node, name);\r
16365                                         });\r
16366 \r
16367                                         return attribs;\r
16368                                 };\r
16369 \r
16370                                 function compareObjects(obj1, obj2) {\r
16371                                         var value, name;\r
16372 \r
16373                                         for (name in obj1) {\r
16374                                                 // Obj1 has item obj2 doesn't have\r
16375                                                 if (obj1.hasOwnProperty(name)) {\r
16376                                                         value = obj2[name];\r
16377 \r
16378                                                         // Obj2 doesn't have obj1 item\r
16379                                                         if (value === undefined)\r
16380                                                                 return FALSE;\r
16381 \r
16382                                                         // Obj2 item has a different value\r
16383                                                         if (obj1[name] != value)\r
16384                                                                 return FALSE;\r
16385 \r
16386                                                         // Delete similar value\r
16387                                                         delete obj2[name];\r
16388                                                 }\r
16389                                         }\r
16390 \r
16391                                         // Check if obj 2 has something obj 1 doesn't have\r
16392                                         for (name in obj2) {\r
16393                                                 // Obj2 has item obj1 doesn't have\r
16394                                                 if (obj2.hasOwnProperty(name))\r
16395                                                         return FALSE;\r
16396                                         }\r
16397 \r
16398                                         return TRUE;\r
16399                                 };\r
16400 \r
16401                                 // Attribs are not the same\r
16402                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))\r
16403                                         return FALSE;\r
16404 \r
16405                                 // Styles are not the same\r
16406                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))\r
16407                                         return FALSE;\r
16408 \r
16409                                 return TRUE;\r
16410                         };\r
16411 \r
16412                         // Check if next/prev exists and that they are elements\r
16413                         if (prev && next) {\r
16414                                 function findElementSibling(node, sibling_name) {\r
16415                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {\r
16416                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)\r
16417                                                         return node;\r
16418 \r
16419                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))\r
16420                                                         return sibling;\r
16421                                         }\r
16422 \r
16423                                         return node;\r
16424                                 };\r
16425 \r
16426                                 // If previous sibling is empty then jump over it\r
16427                                 prev = findElementSibling(prev, 'previousSibling');\r
16428                                 next = findElementSibling(next, 'nextSibling');\r
16429 \r
16430                                 // Compare next and previous nodes\r
16431                                 if (compareElements(prev, next)) {\r
16432                                         // Append nodes between\r
16433                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {\r
16434                                                 tmpSibling = sibling;\r
16435                                                 sibling = sibling.nextSibling;\r
16436                                                 prev.appendChild(tmpSibling);\r
16437                                         }\r
16438 \r
16439                                         // Remove next node\r
16440                                         dom.remove(next);\r
16441 \r
16442                                         // Move children into prev node\r
16443                                         each(tinymce.grep(next.childNodes), function(node) {\r
16444                                                 prev.appendChild(node);\r
16445                                         });\r
16446 \r
16447                                         return prev;\r
16448                                 }\r
16449                         }\r
16450 \r
16451                         return next;\r
16452                 };\r
16453 \r
16454                 function isTextBlock(name) {\r
16455                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);\r
16456                 };\r
16457 \r
16458                 function getContainer(rng, start) {\r
16459                         var container, offset, lastIdx, walker;\r
16460 \r
16461                         container = rng[start ? 'startContainer' : 'endContainer'];\r
16462                         offset = rng[start ? 'startOffset' : 'endOffset'];\r
16463 \r
16464                         if (container.nodeType == 1) {\r
16465                                 lastIdx = container.childNodes.length - 1;\r
16466 \r
16467                                 if (!start && offset)\r
16468                                         offset--;\r
16469 \r
16470                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];\r
16471                         }\r
16472 \r
16473                         // If start text node is excluded then walk to the next node\r
16474                         if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {\r
16475                                 container = new TreeWalker(container, ed.getBody()).next() || container;\r
16476                         }\r
16477 \r
16478                         // If end text node is excluded then walk to the previous node\r
16479                         if (container.nodeType === 3 && !start && offset == 0) {\r
16480                                 container = new TreeWalker(container, ed.getBody()).prev() || container;\r
16481                         }\r
16482 \r
16483                         return container;\r
16484                 };\r
16485 \r
16486                 function performCaretAction(type, name, vars) {\r
16487                         var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;\r
16488 \r
16489                         // Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container\r
16490                         invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR;\r
16491 \r
16492                         // Creates a caret container bogus element\r
16493                         function createCaretContainer(fill) {\r
16494                                 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});\r
16495 \r
16496                                 if (fill) {\r
16497                                         caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar));\r
16498                                 }\r
16499 \r
16500                                 return caretContainer;\r
16501                         };\r
16502 \r
16503                         function isCaretContainerEmpty(node, nodes) {\r
16504                                 while (node) {\r
16505                                         if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) {\r
16506                                                 return false;\r
16507                                         }\r
16508 \r
16509                                         // Collect nodes\r
16510                                         if (nodes && node.nodeType === 1) {\r
16511                                                 nodes.push(node);\r
16512                                         }\r
16513 \r
16514                                         node = node.firstChild;\r
16515                                 }\r
16516 \r
16517                                 return true;\r
16518                         };\r
16519                         \r
16520                         // Returns any parent caret container element\r
16521                         function getParentCaretContainer(node) {\r
16522                                 while (node) {\r
16523                                         if (node.id === caretContainerId) {\r
16524                                                 return node;\r
16525                                         }\r
16526 \r
16527                                         node = node.parentNode;\r
16528                                 }\r
16529                         };\r
16530 \r
16531                         // Finds the first text node in the specified node\r
16532                         function findFirstTextNode(node) {\r
16533                                 var walker;\r
16534 \r
16535                                 if (node) {\r
16536                                         walker = new TreeWalker(node, node);\r
16537 \r
16538                                         for (node = walker.current(); node; node = walker.next()) {\r
16539                                                 if (node.nodeType === 3) {\r
16540                                                         return node;\r
16541                                                 }\r
16542                                         }\r
16543                                 }\r
16544                         };\r
16545 \r
16546                         // Removes the caret container for the specified node or all on the current document\r
16547                         function removeCaretContainer(node, move_caret) {\r
16548                                 var child, rng;\r
16549 \r
16550                                 if (!node) {\r
16551                                         node = getParentCaretContainer(selection.getStart());\r
16552 \r
16553                                         if (!node) {\r
16554                                                 while (node = dom.get(caretContainerId)) {\r
16555                                                         removeCaretContainer(node, false);\r
16556                                                 }\r
16557                                         }\r
16558                                 } else {\r
16559                                         rng = selection.getRng(true);\r
16560 \r
16561                                         if (isCaretContainerEmpty(node)) {\r
16562                                                 if (move_caret !== false) {\r
16563                                                         rng.setStartBefore(node);\r
16564                                                         rng.setEndBefore(node);\r
16565                                                 }\r
16566 \r
16567                                                 dom.remove(node);\r
16568                                         } else {\r
16569                                                 child = findFirstTextNode(node);\r
16570 \r
16571                                                 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {\r
16572                                                         child = child.deleteData(0, 1);\r
16573                                                 }\r
16574 \r
16575                                                 dom.remove(node, 1);\r
16576                                         }\r
16577 \r
16578                                         selection.setRng(rng);\r
16579                                 }\r
16580                         };\r
16581                         \r
16582                         // Applies formatting to the caret postion\r
16583                         function applyCaretFormat() {\r
16584                                 var rng, caretContainer, textNode, offset, bookmark, container, text;\r
16585 \r
16586                                 rng = selection.getRng(true);\r
16587                                 offset = rng.startOffset;\r
16588                                 container = rng.startContainer;\r
16589                                 text = container.nodeValue;\r
16590 \r
16591                                 caretContainer = getParentCaretContainer(selection.getStart());\r
16592                                 if (caretContainer) {\r
16593                                         textNode = findFirstTextNode(caretContainer);\r
16594                                 }\r
16595 \r
16596                                 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character\r
16597                                 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {\r
16598                                         // Get bookmark of caret position\r
16599                                         bookmark = selection.getBookmark();\r
16600 \r
16601                                         // Collapse bookmark range (WebKit)\r
16602                                         rng.collapse(true);\r
16603 \r
16604                                         // Expand the range to the closest word and split it at those points\r
16605                                         rng = expandRng(rng, get(name));\r
16606                                         rng = rangeUtils.split(rng);\r
16607 \r
16608                                         // Apply the format to the range\r
16609                                         apply(name, vars, rng);\r
16610 \r
16611                                         // Move selection back to caret position\r
16612                                         selection.moveToBookmark(bookmark);\r
16613                                 } else {\r
16614                                         if (!caretContainer || textNode.nodeValue !== invisibleChar) {\r
16615                                                 caretContainer = createCaretContainer(true);\r
16616                                                 textNode = caretContainer.firstChild;\r
16617 \r
16618                                                 rng.insertNode(caretContainer);\r
16619                                                 offset = 1;\r
16620 \r
16621                                                 apply(name, vars, caretContainer);\r
16622                                         } else {\r
16623                                                 apply(name, vars, caretContainer);\r
16624                                         }\r
16625 \r
16626                                         // Move selection to text node\r
16627                                         selection.setCursorLocation(textNode, offset);\r
16628                                 }\r
16629                         };\r
16630 \r
16631                         function removeCaretFormat() {\r
16632                                 var rng = selection.getRng(true), container, offset, bookmark,\r
16633                                         hasContentAfter, node, formatNode, parents = [], i, caretContainer;\r
16634 \r
16635                                 container = rng.startContainer;\r
16636                                 offset = rng.startOffset;\r
16637                                 node = container;\r
16638 \r
16639                                 if (container.nodeType == 3) {\r
16640                                         if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) {\r
16641                                                 hasContentAfter = true;\r
16642                                         }\r
16643 \r
16644                                         node = node.parentNode;\r
16645                                 }\r
16646 \r
16647                                 while (node) {\r
16648                                         if (matchNode(node, name, vars)) {\r
16649                                                 formatNode = node;\r
16650                                                 break;\r
16651                                         }\r
16652 \r
16653                                         if (node.nextSibling) {\r
16654                                                 hasContentAfter = true;\r
16655                                         }\r
16656 \r
16657                                         parents.push(node);\r
16658                                         node = node.parentNode;\r
16659                                 }\r
16660 \r
16661                                 // Node doesn't have the specified format\r
16662                                 if (!formatNode) {\r
16663                                         return;\r
16664                                 }\r
16665 \r
16666                                 // Is there contents after the caret then remove the format on the element\r
16667                                 if (hasContentAfter) {\r
16668                                         // Get bookmark of caret position\r
16669                                         bookmark = selection.getBookmark();\r
16670 \r
16671                                         // Collapse bookmark range (WebKit)\r
16672                                         rng.collapse(true);\r
16673 \r
16674                                         // Expand the range to the closest word and split it at those points\r
16675                                         rng = expandRng(rng, get(name), true);\r
16676                                         rng = rangeUtils.split(rng);\r
16677 \r
16678                                         // Remove the format from the range\r
16679                                         remove(name, vars, rng);\r
16680 \r
16681                                         // Move selection back to caret position\r
16682                                         selection.moveToBookmark(bookmark);\r
16683                                 } else {\r
16684                                         caretContainer = createCaretContainer();\r
16685 \r
16686                                         node = caretContainer;\r
16687                                         for (i = parents.length - 1; i >= 0; i--) {\r
16688                                                 node.appendChild(parents[i].cloneNode(false));\r
16689                                                 node = node.firstChild;\r
16690                                         }\r
16691 \r
16692                                         // Insert invisible character into inner most format element\r
16693                                         node.appendChild(dom.doc.createTextNode(invisibleChar));\r
16694                                         node = node.firstChild;\r
16695 \r
16696                                         // Insert caret container after the formated node\r
16697                                         dom.insertAfter(caretContainer, formatNode);\r
16698 \r
16699                                         // Move selection to text node\r
16700                                         selection.setCursorLocation(node, 1);\r
16701                                 }\r
16702                         };\r
16703 \r
16704                         // Only bind the caret events once\r
16705                         if (!self._hasCaretEvents) {\r
16706                                 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements\r
16707                                 ed.onBeforeGetContent.addToTop(function() {\r
16708                                         var nodes = [], i;\r
16709 \r
16710                                         if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {\r
16711                                                 // Mark children\r
16712                                                 i = nodes.length;\r
16713                                                 while (i--) {\r
16714                                                         dom.setAttrib(nodes[i], 'data-mce-bogus', '1');\r
16715                                                 }\r
16716                                         }\r
16717                                 });\r
16718 \r
16719                                 // Remove caret container on mouse up and on key up\r
16720                                 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {\r
16721                                         ed[name].addToTop(function() {\r
16722                                                 removeCaretContainer();\r
16723                                         });\r
16724                                 });\r
16725 \r
16726                                 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys\r
16727                                 ed.onKeyDown.addToTop(function(ed, e) {\r
16728                                         var keyCode = e.keyCode;\r
16729 \r
16730                                         if (keyCode == 8 || keyCode == 37 || keyCode == 39) {\r
16731                                                 removeCaretContainer(getParentCaretContainer(selection.getStart()));\r
16732                                         }\r
16733                                 });\r
16734 \r
16735                                 self._hasCaretEvents = true;\r
16736                         }\r
16737 \r
16738                         // Do apply or remove caret format\r
16739                         if (type == "apply") {\r
16740                                 applyCaretFormat();\r
16741                         } else {\r
16742                                 removeCaretFormat();\r
16743                         }\r
16744                 };\r
16745 \r
16746                 function moveStart(rng) {\r
16747                         var container = rng.startContainer,\r
16748                                         offset = rng.startOffset,\r
16749                                         walker, node, nodes, tmpNode;\r
16750 \r
16751                         // Convert text node into index if possible\r
16752                         if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {\r
16753                                 container = container.parentNode;\r
16754                                 offset = nodeIndex(container) + 1;\r
16755                         }\r
16756 \r
16757                         // Move startContainer/startOffset in to a suitable node\r
16758                         if (container.nodeType == 1) {\r
16759                                 nodes = container.childNodes;\r
16760                                 container = nodes[Math.min(offset, nodes.length - 1)];\r
16761                                 walker = new TreeWalker(container);\r
16762 \r
16763                                 // If offset is at end of the parent node walk to the next one\r
16764                                 if (offset > nodes.length - 1)\r
16765                                         walker.next();\r
16766 \r
16767                                 for (node = walker.current(); node; node = walker.next()) {\r
16768                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
16769                                                 // IE has a "neat" feature where it moves the start node into the closest element\r
16770                                                 // we can avoid this by inserting an element before it and then remove it after we set the selection\r
16771                                                 tmpNode = dom.create('a', null, INVISIBLE_CHAR);\r
16772                                                 node.parentNode.insertBefore(tmpNode, node);\r
16773 \r
16774                                                 // Set selection and remove tmpNode\r
16775                                                 rng.setStart(node, 0);\r
16776                                                 selection.setRng(rng);\r
16777                                                 dom.remove(tmpNode);\r
16778 \r
16779                                                 return;\r
16780                                         }\r
16781                                 }\r
16782                         }\r
16783                 };\r
16784 \r
16785         };\r
16786 })(tinymce);\r
16787 \r
16788 tinymce.onAddEditor.add(function(tinymce, ed) {\r
16789         var filters, fontSizes, dom, settings = ed.settings;\r
16790 \r
16791         if (settings.inline_styles) {\r
16792                 fontSizes = tinymce.explode(settings.font_size_legacy_values);\r
16793 \r
16794                 function replaceWithSpan(node, styles) {\r
16795                         tinymce.each(styles, function(value, name) {\r
16796                                 if (value)\r
16797                                         dom.setStyle(node, name, value);\r
16798                         });\r
16799 \r
16800                         dom.rename(node, 'span');\r
16801                 };\r
16802 \r
16803                 filters = {\r
16804                         font : function(dom, node) {\r
16805                                 replaceWithSpan(node, {\r
16806                                         backgroundColor : node.style.backgroundColor,\r
16807                                         color : node.color,\r
16808                                         fontFamily : node.face,\r
16809                                         fontSize : fontSizes[parseInt(node.size) - 1]\r
16810                                 });\r
16811                         },\r
16812 \r
16813                         u : function(dom, node) {\r
16814                                 replaceWithSpan(node, {\r
16815                                         textDecoration : 'underline'\r
16816                                 });\r
16817                         },\r
16818 \r
16819                         strike : function(dom, node) {\r
16820                                 replaceWithSpan(node, {\r
16821                                         textDecoration : 'line-through'\r
16822                                 });\r
16823                         }\r
16824                 };\r
16825 \r
16826                 function convert(editor, params) {\r
16827                         dom = editor.dom;\r
16828 \r
16829                         if (settings.convert_fonts_to_spans) {\r
16830                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {\r
16831                                         filters[node.nodeName.toLowerCase()](ed.dom, node);\r
16832                                 });\r
16833                         }\r
16834                 };\r
16835 \r
16836                 ed.onPreProcess.add(convert);\r
16837                 ed.onSetContent.add(convert);\r
16838 \r
16839                 ed.onInit.add(function() {\r
16840                         ed.selection.onSetContent.add(convert);\r
16841                 });\r
16842         }\r
16843 });\r
16844 \r