]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/TinyMCE/js/tiny_mce_src.js
upgrade TinyMCE to 3.4.x
[quix0rs-gnu-social.git] / plugins / TinyMCE / js / 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.3.1',\r
9 \r
10                 releaseDate : '2011-06-16',\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.isGecko = !t.isWebKit && /Gecko/.test(ua);\r
24 \r
25                         t.isMac = ua.indexOf('Mac') != -1;\r
26 \r
27                         t.isAir = /adobeair/i.test(ua);\r
28 \r
29                         t.isIDevice = /(iPad|iPhone)/.test(ua);\r
30                         \r
31                         t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;\r
32 \r
33                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE\r
34                         if (win.tinyMCEPreInit) {\r
35                                 t.suffix = tinyMCEPreInit.suffix;\r
36                                 t.baseURL = tinyMCEPreInit.base;\r
37                                 t.query = tinyMCEPreInit.query;\r
38                                 return;\r
39                         }\r
40 \r
41                         // Get suffix and base\r
42                         t.suffix = '';\r
43 \r
44                         // If base element found, add that infront of baseURL\r
45                         nl = d.getElementsByTagName('base');\r
46                         for (i=0; i<nl.length; i++) {\r
47                                 if (v = nl[i].href) {\r
48                                         // Host only value like http://site.com or http://site.com:8008\r
49                                         if (/^https?:\/\/[^\/]+$/.test(v))\r
50                                                 v += '/';\r
51 \r
52                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory\r
53                                 }\r
54                         }\r
55 \r
56                         function getBase(n) {\r
57                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {\r
58                                         if (/_(src|dev)\.js/g.test(n.src))\r
59                                                 t.suffix = '_src';\r
60 \r
61                                         if ((p = n.src.indexOf('?')) != -1)\r
62                                                 t.query = n.src.substring(p + 1);\r
63 \r
64                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));\r
65 \r
66                                         // If path to script is relative and a base href was found add that one infront\r
67                                         // the src property will always be an absolute one on non IE browsers and IE 8\r
68                                         // so this logic will basically only be executed on older IE versions\r
69                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)\r
70                                                 t.baseURL = base + t.baseURL;\r
71 \r
72                                         return t.baseURL;\r
73                                 }\r
74 \r
75                                 return null;\r
76                         };\r
77 \r
78                         // Check document\r
79                         nl = d.getElementsByTagName('script');\r
80                         for (i=0; i<nl.length; i++) {\r
81                                 if (getBase(nl[i]))\r
82                                         return;\r
83                         }\r
84 \r
85                         // Check head\r
86                         n = d.getElementsByTagName('head')[0];\r
87                         if (n) {\r
88                                 nl = n.getElementsByTagName('script');\r
89                                 for (i=0; i<nl.length; i++) {\r
90                                         if (getBase(nl[i]))\r
91                                                 return;\r
92                                 }\r
93                         }\r
94 \r
95                         return;\r
96                 },\r
97 \r
98                 is : function(o, t) {\r
99                         if (!t)\r
100                                 return o !== undefined;\r
101 \r
102                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))\r
103                                 return true;\r
104 \r
105                         return typeof(o) == t;\r
106                 },\r
107 \r
108                 makeMap : function(items, delim, map) {\r
109                         var i;\r
110 \r
111                         items = items || [];\r
112                         delim = delim || ',';\r
113 \r
114                         if (typeof(items) == "string")\r
115                                 items = items.split(delim);\r
116 \r
117                         map = map || {};\r
118 \r
119                         i = items.length;\r
120                         while (i--)\r
121                                 map[items[i]] = {};\r
122 \r
123                         return map;\r
124                 },\r
125 \r
126                 each : function(o, cb, s) {\r
127                         var n, l;\r
128 \r
129                         if (!o)\r
130                                 return 0;\r
131 \r
132                         s = s || o;\r
133 \r
134                         if (o.length !== undefined) {\r
135                                 // Indexed arrays, needed for Safari\r
136                                 for (n=0, l = o.length; n < l; n++) {\r
137                                         if (cb.call(s, o[n], n, o) === false)\r
138                                                 return 0;\r
139                                 }\r
140                         } else {\r
141                                 // Hashtables\r
142                                 for (n in o) {\r
143                                         if (o.hasOwnProperty(n)) {\r
144                                                 if (cb.call(s, o[n], n, o) === false)\r
145                                                         return 0;\r
146                                         }\r
147                                 }\r
148                         }\r
149 \r
150                         return 1;\r
151                 },\r
152 \r
153 \r
154                 map : function(a, f) {\r
155                         var o = [];\r
156 \r
157                         tinymce.each(a, function(v) {\r
158                                 o.push(f(v));\r
159                         });\r
160 \r
161                         return o;\r
162                 },\r
163 \r
164                 grep : function(a, f) {\r
165                         var o = [];\r
166 \r
167                         tinymce.each(a, function(v) {\r
168                                 if (!f || f(v))\r
169                                         o.push(v);\r
170                         });\r
171 \r
172                         return o;\r
173                 },\r
174 \r
175                 inArray : function(a, v) {\r
176                         var i, l;\r
177 \r
178                         if (a) {\r
179                                 for (i = 0, l = a.length; i < l; i++) {\r
180                                         if (a[i] === v)\r
181                                                 return i;\r
182                                 }\r
183                         }\r
184 \r
185                         return -1;\r
186                 },\r
187 \r
188                 extend : function(o, e) {\r
189                         var i, l, a = arguments;\r
190 \r
191                         for (i = 1, l = a.length; i < l; i++) {\r
192                                 e = a[i];\r
193 \r
194                                 tinymce.each(e, function(v, n) {\r
195                                         if (v !== undefined)\r
196                                                 o[n] = v;\r
197                                 });\r
198                         }\r
199 \r
200                         return o;\r
201                 },\r
202 \r
203 \r
204                 trim : function(s) {\r
205                         return (s ? '' + s : '').replace(whiteSpaceRe, '');\r
206                 },\r
207 \r
208                 create : function(s, p, root) {\r
209                         var t = this, sp, ns, cn, scn, c, de = 0;\r
210 \r
211                         // Parse : <prefix> <class>:<super class>\r
212                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);\r
213                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name\r
214 \r
215                         // Create namespace for new class\r
216                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);\r
217 \r
218                         // Class already exists\r
219                         if (ns[cn])\r
220                                 return;\r
221 \r
222                         // Make pure static class\r
223                         if (s[2] == 'static') {\r
224                                 ns[cn] = p;\r
225 \r
226                                 if (this.onCreate)\r
227                                         this.onCreate(s[2], s[3], ns[cn]);\r
228 \r
229                                 return;\r
230                         }\r
231 \r
232                         // Create default constructor\r
233                         if (!p[cn]) {\r
234                                 p[cn] = function() {};\r
235                                 de = 1;\r
236                         }\r
237 \r
238                         // Add constructor and methods\r
239                         ns[cn] = p[cn];\r
240                         t.extend(ns[cn].prototype, p);\r
241 \r
242                         // Extend\r
243                         if (s[5]) {\r
244                                 sp = t.resolve(s[5]).prototype;\r
245                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name\r
246 \r
247                                 // Extend constructor\r
248                                 c = ns[cn];\r
249                                 if (de) {\r
250                                         // Add passthrough constructor\r
251                                         ns[cn] = function() {\r
252                                                 return sp[scn].apply(this, arguments);\r
253                                         };\r
254                                 } else {\r
255                                         // Add inherit constructor\r
256                                         ns[cn] = function() {\r
257                                                 this.parent = sp[scn];\r
258                                                 return c.apply(this, arguments);\r
259                                         };\r
260                                 }\r
261                                 ns[cn].prototype[cn] = ns[cn];\r
262 \r
263                                 // Add super methods\r
264                                 t.each(sp, function(f, n) {\r
265                                         ns[cn].prototype[n] = sp[n];\r
266                                 });\r
267 \r
268                                 // Add overridden methods\r
269                                 t.each(p, function(f, n) {\r
270                                         // Extend methods if needed\r
271                                         if (sp[n]) {\r
272                                                 ns[cn].prototype[n] = function() {\r
273                                                         this.parent = sp[n];\r
274                                                         return f.apply(this, arguments);\r
275                                                 };\r
276                                         } else {\r
277                                                 if (n != cn)\r
278                                                         ns[cn].prototype[n] = f;\r
279                                         }\r
280                                 });\r
281                         }\r
282 \r
283                         // Add static methods\r
284                         t.each(p['static'], function(f, n) {\r
285                                 ns[cn][n] = f;\r
286                         });\r
287 \r
288                         if (this.onCreate)\r
289                                 this.onCreate(s[2], s[3], ns[cn].prototype);\r
290                 },\r
291 \r
292                 walk : function(o, f, n, s) {\r
293                         s = s || this;\r
294 \r
295                         if (o) {\r
296                                 if (n)\r
297                                         o = o[n];\r
298 \r
299                                 tinymce.each(o, function(o, i) {\r
300                                         if (f.call(s, o, i, n) === false)\r
301                                                 return false;\r
302 \r
303                                         tinymce.walk(o, f, n, s);\r
304                                 });\r
305                         }\r
306                 },\r
307 \r
308                 createNS : function(n, o) {\r
309                         var i, v;\r
310 \r
311                         o = o || win;\r
312 \r
313                         n = n.split('.');\r
314                         for (i=0; i<n.length; i++) {\r
315                                 v = n[i];\r
316 \r
317                                 if (!o[v])\r
318                                         o[v] = {};\r
319 \r
320                                 o = o[v];\r
321                         }\r
322 \r
323                         return o;\r
324                 },\r
325 \r
326                 resolve : function(n, o) {\r
327                         var i, l;\r
328 \r
329                         o = o || win;\r
330 \r
331                         n = n.split('.');\r
332                         for (i = 0, l = n.length; i < l; i++) {\r
333                                 o = o[n[i]];\r
334 \r
335                                 if (!o)\r
336                                         break;\r
337                         }\r
338 \r
339                         return o;\r
340                 },\r
341 \r
342                 addUnload : function(f, s) {\r
343                         var t = this;\r
344 \r
345                         f = {func : f, scope : s || this};\r
346 \r
347                         if (!t.unloads) {\r
348                                 function unload() {\r
349                                         var li = t.unloads, o, n;\r
350 \r
351                                         if (li) {\r
352                                                 // Call unload handlers\r
353                                                 for (n in li) {\r
354                                                         o = li[n];\r
355 \r
356                                                         if (o && o.func)\r
357                                                                 o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy\r
358                                                 }\r
359 \r
360                                                 // Detach unload function\r
361                                                 if (win.detachEvent) {\r
362                                                         win.detachEvent('onbeforeunload', fakeUnload);\r
363                                                         win.detachEvent('onunload', unload);\r
364                                                 } else if (win.removeEventListener)\r
365                                                         win.removeEventListener('unload', unload, false);\r
366 \r
367                                                 // Destroy references\r
368                                                 t.unloads = o = li = w = unload = 0;\r
369 \r
370                                                 // Run garbarge collector on IE\r
371                                                 if (win.CollectGarbage)\r
372                                                         CollectGarbage();\r
373                                         }\r
374                                 };\r
375 \r
376                                 function fakeUnload() {\r
377                                         var d = document;\r
378 \r
379                                         // Is there things still loading, then do some magic\r
380                                         if (d.readyState == 'interactive') {\r
381                                                 function stop() {\r
382                                                         // Prevent memory leak\r
383                                                         d.detachEvent('onstop', stop);\r
384 \r
385                                                         // Call unload handler\r
386                                                         if (unload)\r
387                                                                 unload();\r
388 \r
389                                                         d = 0;\r
390                                                 };\r
391 \r
392                                                 // Fire unload when the currently loading page is stopped\r
393                                                 if (d)\r
394                                                         d.attachEvent('onstop', stop);\r
395 \r
396                                                 // Remove onstop listener after a while to prevent the unload function\r
397                                                 // to execute if the user presses cancel in an onbeforeunload\r
398                                                 // confirm dialog and then presses the browser stop button\r
399                                                 win.setTimeout(function() {\r
400                                                         if (d)\r
401                                                                 d.detachEvent('onstop', stop);\r
402                                                 }, 0);\r
403                                         }\r
404                                 };\r
405 \r
406                                 // Attach unload handler\r
407                                 if (win.attachEvent) {\r
408                                         win.attachEvent('onunload', unload);\r
409                                         win.attachEvent('onbeforeunload', fakeUnload);\r
410                                 } else if (win.addEventListener)\r
411                                         win.addEventListener('unload', unload, false);\r
412 \r
413                                 // Setup initial unload handler array\r
414                                 t.unloads = [f];\r
415                         } else\r
416                                 t.unloads.push(f);\r
417 \r
418                         return f;\r
419                 },\r
420 \r
421                 removeUnload : function(f) {\r
422                         var u = this.unloads, r = null;\r
423 \r
424                         tinymce.each(u, function(o, i) {\r
425                                 if (o && o.func == f) {\r
426                                         u.splice(i, 1);\r
427                                         r = f;\r
428                                         return false;\r
429                                 }\r
430                         });\r
431 \r
432                         return r;\r
433                 },\r
434 \r
435                 explode : function(s, d) {\r
436                         return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;\r
437                 },\r
438 \r
439                 _addVer : function(u) {\r
440                         var v;\r
441 \r
442                         if (!this.query)\r
443                                 return u;\r
444 \r
445                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;\r
446 \r
447                         if (u.indexOf('#') == -1)\r
448                                 return u + v;\r
449 \r
450                         return u.replace('#', v + '#');\r
451                 },\r
452 \r
453                 // Fix function for IE 9 where regexps isn't working correctly\r
454                 // Todo: remove me once MS fixes the bug\r
455                 _replace : function(find, replace, str) {\r
456                         // On IE9 we have to fake $x replacement\r
457                         if (isRegExpBroken) {\r
458                                 return str.replace(find, function() {\r
459                                         var val = replace, args = arguments, i;\r
460 \r
461                                         for (i = 0; i < args.length - 2; i++) {\r
462                                                 if (args[i] === undefined) {\r
463                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');\r
464                                                 } else {\r
465                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);\r
466                                                 }\r
467                                         }\r
468 \r
469                                         return val;\r
470                                 });\r
471                         }\r
472 \r
473                         return str.replace(find, replace);\r
474                 }\r
475 \r
476                 };\r
477 \r
478         // Initialize the API\r
479         tinymce._init();\r
480 \r
481         // Expose tinymce namespace to the global namespace (window)\r
482         win.tinymce = win.tinyMCE = tinymce;\r
483 \r
484         // Describe the different namespaces\r
485 \r
486         })(window);\r
487 \r
488 \r
489 \r
490 tinymce.create('tinymce.util.Dispatcher', {\r
491         scope : null,\r
492         listeners : null,\r
493 \r
494         Dispatcher : function(s) {\r
495                 this.scope = s || this;\r
496                 this.listeners = [];\r
497         },\r
498 \r
499         add : function(cb, s) {\r
500                 this.listeners.push({cb : cb, scope : s || this.scope});\r
501 \r
502                 return cb;\r
503         },\r
504 \r
505         addToTop : function(cb, s) {\r
506                 this.listeners.unshift({cb : cb, scope : s || this.scope});\r
507 \r
508                 return cb;\r
509         },\r
510 \r
511         remove : function(cb) {\r
512                 var l = this.listeners, o = null;\r
513 \r
514                 tinymce.each(l, function(c, i) {\r
515                         if (cb == c.cb) {\r
516                                 o = cb;\r
517                                 l.splice(i, 1);\r
518                                 return false;\r
519                         }\r
520                 });\r
521 \r
522                 return o;\r
523         },\r
524 \r
525         dispatch : function() {\r
526                 var s, a = arguments, i, li = this.listeners, c;\r
527 \r
528                 // Needs to be a real loop since the listener count might change while looping\r
529                 // And this is also more efficient\r
530                 for (i = 0; i<li.length; i++) {\r
531                         c = li[i];\r
532                         s = c.cb.apply(c.scope, a);\r
533 \r
534                         if (s === false)\r
535                                 break;\r
536                 }\r
537 \r
538                 return s;\r
539         }\r
540 \r
541         });\r
542 \r
543 (function() {\r
544         var each = tinymce.each;\r
545 \r
546         tinymce.create('tinymce.util.URI', {\r
547                 URI : function(u, s) {\r
548                         var t = this, o, a, b, base_url;\r
549 \r
550                         // Trim whitespace\r
551                         u = tinymce.trim(u);\r
552 \r
553                         // Default settings\r
554                         s = t.settings = s || {};\r
555 \r
556                         // Strange app protocol or local anchor\r
557                         if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {\r
558                                 t.source = u;\r
559                                 return;\r
560                         }\r
561 \r
562                         // Absolute path with no host, fake host and protocol\r
563                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)\r
564                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;\r
565 \r
566                         // Relative path http:// or protocol relative //path\r
567                         if (!/^[\w-]*:?\/\//.test(u)) {\r
568                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;\r
569                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);\r
570                         }\r
571 \r
572                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)\r
573                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something\r
574                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);\r
575                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {\r
576                                 var s = u[i];\r
577 \r
578                                 // Zope 3 workaround, they use @@something\r
579                                 if (s)\r
580                                         s = s.replace(/\(mce_at\)/g, '@@');\r
581 \r
582                                 t[v] = s;\r
583                         });\r
584 \r
585                         if (b = s.base_uri) {\r
586                                 if (!t.protocol)\r
587                                         t.protocol = b.protocol;\r
588 \r
589                                 if (!t.userInfo)\r
590                                         t.userInfo = b.userInfo;\r
591 \r
592                                 if (!t.port && t.host == 'mce_host')\r
593                                         t.port = b.port;\r
594 \r
595                                 if (!t.host || t.host == 'mce_host')\r
596                                         t.host = b.host;\r
597 \r
598                                 t.source = '';\r
599                         }\r
600 \r
601                         //t.path = t.path || '/';\r
602                 },\r
603 \r
604                 setPath : function(p) {\r
605                         var t = this;\r
606 \r
607                         p = /^(.*?)\/?(\w+)?$/.exec(p);\r
608 \r
609                         // Update path parts\r
610                         t.path = p[0];\r
611                         t.directory = p[1];\r
612                         t.file = p[2];\r
613 \r
614                         // Rebuild source\r
615                         t.source = '';\r
616                         t.getURI();\r
617                 },\r
618 \r
619                 toRelative : function(u) {\r
620                         var t = this, o;\r
621 \r
622                         if (u === "./")\r
623                                 return u;\r
624 \r
625                         u = new tinymce.util.URI(u, {base_uri : t});\r
626 \r
627                         // Not on same domain/port or protocol\r
628                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)\r
629                                 return u.getURI();\r
630 \r
631                         o = t.toRelPath(t.path, u.path);\r
632 \r
633                         // Add query\r
634                         if (u.query)\r
635                                 o += '?' + u.query;\r
636 \r
637                         // Add anchor\r
638                         if (u.anchor)\r
639                                 o += '#' + u.anchor;\r
640 \r
641                         return o;\r
642                 },\r
643         \r
644                 toAbsolute : function(u, nh) {\r
645                         var u = new tinymce.util.URI(u, {base_uri : this});\r
646 \r
647                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);\r
648                 },\r
649 \r
650                 toRelPath : function(base, path) {\r
651                         var items, bp = 0, out = '', i, l;\r
652 \r
653                         // Split the paths\r
654                         base = base.substring(0, base.lastIndexOf('/'));\r
655                         base = base.split('/');\r
656                         items = path.split('/');\r
657 \r
658                         if (base.length >= items.length) {\r
659                                 for (i = 0, l = base.length; i < l; i++) {\r
660                                         if (i >= items.length || base[i] != items[i]) {\r
661                                                 bp = i + 1;\r
662                                                 break;\r
663                                         }\r
664                                 }\r
665                         }\r
666 \r
667                         if (base.length < items.length) {\r
668                                 for (i = 0, l = items.length; i < l; i++) {\r
669                                         if (i >= base.length || base[i] != items[i]) {\r
670                                                 bp = i + 1;\r
671                                                 break;\r
672                                         }\r
673                                 }\r
674                         }\r
675 \r
676                         if (bp == 1)\r
677                                 return path;\r
678 \r
679                         for (i = 0, l = base.length - (bp - 1); i < l; i++)\r
680                                 out += "../";\r
681 \r
682                         for (i = bp - 1, l = items.length; i < l; i++) {\r
683                                 if (i != bp - 1)\r
684                                         out += "/" + items[i];\r
685                                 else\r
686                                         out += items[i];\r
687                         }\r
688 \r
689                         return out;\r
690                 },\r
691 \r
692                 toAbsPath : function(base, path) {\r
693                         var i, nb = 0, o = [], tr, outPath;\r
694 \r
695                         // Split paths\r
696                         tr = /\/$/.test(path) ? '/' : '';\r
697                         base = base.split('/');\r
698                         path = path.split('/');\r
699 \r
700                         // Remove empty chunks\r
701                         each(base, function(k) {\r
702                                 if (k)\r
703                                         o.push(k);\r
704                         });\r
705 \r
706                         base = o;\r
707 \r
708                         // Merge relURLParts chunks\r
709                         for (i = path.length - 1, o = []; i >= 0; i--) {\r
710                                 // Ignore empty or .\r
711                                 if (path[i].length == 0 || path[i] == ".")\r
712                                         continue;\r
713 \r
714                                 // Is parent\r
715                                 if (path[i] == '..') {\r
716                                         nb++;\r
717                                         continue;\r
718                                 }\r
719 \r
720                                 // Move up\r
721                                 if (nb > 0) {\r
722                                         nb--;\r
723                                         continue;\r
724                                 }\r
725 \r
726                                 o.push(path[i]);\r
727                         }\r
728 \r
729                         i = base.length - nb;\r
730 \r
731                         // If /a/b/c or /\r
732                         if (i <= 0)\r
733                                 outPath = o.reverse().join('/');\r
734                         else\r
735                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');\r
736 \r
737                         // Add front / if it's needed\r
738                         if (outPath.indexOf('/') !== 0)\r
739                                 outPath = '/' + outPath;\r
740 \r
741                         // Add traling / if it's needed\r
742                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)\r
743                                 outPath += tr;\r
744 \r
745                         return outPath;\r
746                 },\r
747 \r
748                 getURI : function(nh) {\r
749                         var s, t = this;\r
750 \r
751                         // Rebuild source\r
752                         if (!t.source || nh) {\r
753                                 s = '';\r
754 \r
755                                 if (!nh) {\r
756                                         if (t.protocol)\r
757                                                 s += t.protocol + '://';\r
758 \r
759                                         if (t.userInfo)\r
760                                                 s += t.userInfo + '@';\r
761 \r
762                                         if (t.host)\r
763                                                 s += t.host;\r
764 \r
765                                         if (t.port)\r
766                                                 s += ':' + t.port;\r
767                                 }\r
768 \r
769                                 if (t.path)\r
770                                         s += t.path;\r
771 \r
772                                 if (t.query)\r
773                                         s += '?' + t.query;\r
774 \r
775                                 if (t.anchor)\r
776                                         s += '#' + t.anchor;\r
777 \r
778                                 t.source = s;\r
779                         }\r
780 \r
781                         return t.source;\r
782                 }\r
783         });\r
784 })();\r
785 \r
786 (function() {\r
787         var each = tinymce.each;\r
788 \r
789         tinymce.create('static tinymce.util.Cookie', {\r
790                 getHash : function(n) {\r
791                         var v = this.get(n), h;\r
792 \r
793                         if (v) {\r
794                                 each(v.split('&'), function(v) {\r
795                                         v = v.split('=');\r
796                                         h = h || {};\r
797                                         h[unescape(v[0])] = unescape(v[1]);\r
798                                 });\r
799                         }\r
800 \r
801                         return h;\r
802                 },\r
803 \r
804                 setHash : function(n, v, e, p, d, s) {\r
805                         var o = '';\r
806 \r
807                         each(v, function(v, k) {\r
808                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);\r
809                         });\r
810 \r
811                         this.set(n, o, e, p, d, s);\r
812                 },\r
813 \r
814                 get : function(n) {\r
815                         var c = document.cookie, e, p = n + "=", b;\r
816 \r
817                         // Strict mode\r
818                         if (!c)\r
819                                 return;\r
820 \r
821                         b = c.indexOf("; " + p);\r
822 \r
823                         if (b == -1) {\r
824                                 b = c.indexOf(p);\r
825 \r
826                                 if (b != 0)\r
827                                         return null;\r
828                         } else\r
829                                 b += 2;\r
830 \r
831                         e = c.indexOf(";", b);\r
832 \r
833                         if (e == -1)\r
834                                 e = c.length;\r
835 \r
836                         return unescape(c.substring(b + p.length, e));\r
837                 },\r
838 \r
839                 set : function(n, v, e, p, d, s) {\r
840                         document.cookie = n + "=" + escape(v) +\r
841                                 ((e) ? "; expires=" + e.toGMTString() : "") +\r
842                                 ((p) ? "; path=" + escape(p) : "") +\r
843                                 ((d) ? "; domain=" + d : "") +\r
844                                 ((s) ? "; secure" : "");\r
845                 },\r
846 \r
847                 remove : function(n, p) {\r
848                         var d = new Date();\r
849 \r
850                         d.setTime(d.getTime() - 1000);\r
851 \r
852                         this.set(n, '', d, p, d);\r
853                 }\r
854         });\r
855 })();\r
856 \r
857 (function() {\r
858         function serialize(o, quote) {\r
859                 var i, v, t;\r
860 \r
861                 quote = quote || '"';\r
862 \r
863                 if (o == null)\r
864                         return 'null';\r
865 \r
866                 t = typeof o;\r
867 \r
868                 if (t == 'string') {\r
869                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';\r
870 \r
871                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {\r
872                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility\r
873                                 if (quote === '"' && a === "'")\r
874                                         return a;\r
875 \r
876                                 i = v.indexOf(b);\r
877 \r
878                                 if (i + 1)\r
879                                         return '\\' + v.charAt(i + 1);\r
880 \r
881                                 a = b.charCodeAt().toString(16);\r
882 \r
883                                 return '\\u' + '0000'.substring(a.length) + a;\r
884                         }) + quote;\r
885                 }\r
886 \r
887                 if (t == 'object') {\r
888                         if (o.hasOwnProperty && o instanceof Array) {\r
889                                         for (i=0, v = '['; i<o.length; i++)\r
890                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);\r
891 \r
892                                         return v + ']';\r
893                                 }\r
894 \r
895                                 v = '{';\r
896 \r
897                                 for (i in o)\r
898                                         v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';\r
899 \r
900                                 return v + '}';\r
901                 }\r
902 \r
903                 return '' + o;\r
904         };\r
905 \r
906         tinymce.util.JSON = {\r
907                 serialize: serialize,\r
908 \r
909                 parse: function(s) {\r
910                         try {\r
911                                 return eval('(' + s + ')');\r
912                         } catch (ex) {\r
913                                 // Ignore\r
914                         }\r
915                 }\r
916 \r
917                 };\r
918 })();\r
919 tinymce.create('static tinymce.util.XHR', {\r
920         send : function(o) {\r
921                 var x, t, w = window, c = 0;\r
922 \r
923                 // Default settings\r
924                 o.scope = o.scope || this;\r
925                 o.success_scope = o.success_scope || o.scope;\r
926                 o.error_scope = o.error_scope || o.scope;\r
927                 o.async = o.async === false ? false : true;\r
928                 o.data = o.data || '';\r
929 \r
930                 function get(s) {\r
931                         x = 0;\r
932 \r
933                         try {\r
934                                 x = new ActiveXObject(s);\r
935                         } catch (ex) {\r
936                         }\r
937 \r
938                         return x;\r
939                 };\r
940 \r
941                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');\r
942 \r
943                 if (x) {\r
944                         if (x.overrideMimeType)\r
945                                 x.overrideMimeType(o.content_type);\r
946 \r
947                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);\r
948 \r
949                         if (o.content_type)\r
950                                 x.setRequestHeader('Content-Type', o.content_type);\r
951 \r
952                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r
953 \r
954                         x.send(o.data);\r
955 \r
956                         function ready() {\r
957                                 if (!o.async || x.readyState == 4 || c++ > 10000) {\r
958                                         if (o.success && c < 10000 && x.status == 200)\r
959                                                 o.success.call(o.success_scope, '' + x.responseText, x, o);\r
960                                         else if (o.error)\r
961                                                 o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);\r
962 \r
963                                         x = null;\r
964                                 } else\r
965                                         w.setTimeout(ready, 10);\r
966                         };\r
967 \r
968                         // Syncronous request\r
969                         if (!o.async)\r
970                                 return ready();\r
971 \r
972                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE\r
973                         t = w.setTimeout(ready, 10);\r
974                 }\r
975         }\r
976 });\r
977 \r
978 (function() {\r
979         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;\r
980 \r
981         tinymce.create('tinymce.util.JSONRequest', {\r
982                 JSONRequest : function(s) {\r
983                         this.settings = extend({\r
984                         }, s);\r
985                         this.count = 0;\r
986                 },\r
987 \r
988                 send : function(o) {\r
989                         var ecb = o.error, scb = o.success;\r
990 \r
991                         o = extend(this.settings, o);\r
992 \r
993                         o.success = function(c, x) {\r
994                                 c = JSON.parse(c);\r
995 \r
996                                 if (typeof(c) == 'undefined') {\r
997                                         c = {\r
998                                                 error : 'JSON Parse error.'\r
999                                         };\r
1000                                 }\r
1001 \r
1002                                 if (c.error)\r
1003                                         ecb.call(o.error_scope || o.scope, c.error, x);\r
1004                                 else\r
1005                                         scb.call(o.success_scope || o.scope, c.result);\r
1006                         };\r
1007 \r
1008                         o.error = function(ty, x) {\r
1009                                 if (ecb)\r
1010                                         ecb.call(o.error_scope || o.scope, ty, x);\r
1011                         };\r
1012 \r
1013                         o.data = JSON.serialize({\r
1014                                 id : o.id || 'c' + (this.count++),\r
1015                                 method : o.method,\r
1016                                 params : o.params\r
1017                         });\r
1018 \r
1019                         // JSON content type for Ruby on rails. Bug: #1883287\r
1020                         o.content_type = 'application/json';\r
1021 \r
1022                         XHR.send(o);\r
1023                 },\r
1024 \r
1025                 'static' : {\r
1026                         sendRPC : function(o) {\r
1027                                 return new tinymce.util.JSONRequest().send(o);\r
1028                         }\r
1029                 }\r
1030         });\r
1031 }());\r
1032 (function(tinymce) {\r
1033         var namedEntities, baseEntities, reverseEntities,\r
1034                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1035                 textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1036                 rawCharsRegExp = /[<>&\"\']/g,\r
1037                 entityRegExp = /&(#x|#)?([\w]+);/g,\r
1038                 asciiMap = {\r
1039                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",\r
1040                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",\r
1041                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",\r
1042                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",\r
1043                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"\r
1044                 };\r
1045 \r
1046         // Raw entities\r
1047         baseEntities = {\r
1048                 '"' : '&quot;',\r
1049                 "'" : '&#39;',\r
1050                 '<' : '&lt;',\r
1051                 '>' : '&gt;',\r
1052                 '&' : '&amp;'\r
1053         };\r
1054 \r
1055         // Reverse lookup table for raw entities\r
1056         reverseEntities = {\r
1057                 '&lt;' : '<',\r
1058                 '&gt;' : '>',\r
1059                 '&amp;' : '&',\r
1060                 '&quot;' : '"',\r
1061                 '&apos;' : "'"\r
1062         };\r
1063 \r
1064         // Decodes text by using the browser\r
1065         function nativeDecode(text) {\r
1066                 var elm;\r
1067 \r
1068                 elm = document.createElement("div");\r
1069                 elm.innerHTML = text;\r
1070 \r
1071                 return elm.textContent || elm.innerText || text;\r
1072         };\r
1073 \r
1074         // Build a two way lookup table for the entities\r
1075         function buildEntitiesLookup(items, radix) {\r
1076                 var i, chr, entity, lookup = {};\r
1077 \r
1078                 if (items) {\r
1079                         items = items.split(',');\r
1080                         radix = radix || 10;\r
1081 \r
1082                         // Build entities lookup table\r
1083                         for (i = 0; i < items.length; i += 2) {\r
1084                                 chr = String.fromCharCode(parseInt(items[i], radix));\r
1085 \r
1086                                 // Only add non base entities\r
1087                                 if (!baseEntities[chr]) {\r
1088                                         entity = '&' + items[i + 1] + ';';\r
1089                                         lookup[chr] = entity;\r
1090                                         lookup[entity] = chr;\r
1091                                 }\r
1092                         }\r
1093 \r
1094                         return lookup;\r
1095                 }\r
1096         };\r
1097 \r
1098         // Unpack entities lookup where the numbers are in radix 32 to reduce the size\r
1099         namedEntities = buildEntitiesLookup(\r
1100                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +\r
1101                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +\r
1102                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +\r
1103                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +\r
1104                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +\r
1105                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +\r
1106                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +\r
1107                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +\r
1108                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +\r
1109                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +\r
1110                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +\r
1111                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +\r
1112                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +\r
1113                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +\r
1114                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +\r
1115                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +\r
1116                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +\r
1117                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +\r
1118                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +\r
1119                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +\r
1120                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +\r
1121                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +\r
1122                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +\r
1123                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +\r
1124                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'\r
1125         , 32);\r
1126 \r
1127         tinymce.html = tinymce.html || {};\r
1128 \r
1129         tinymce.html.Entities = {\r
1130                 encodeRaw : function(text, attr) {\r
1131                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1132                                 return baseEntities[chr] || chr;\r
1133                         });\r
1134                 },\r
1135 \r
1136                 encodeAllRaw : function(text) {\r
1137                         return ('' + text).replace(rawCharsRegExp, function(chr) {\r
1138                                 return baseEntities[chr] || chr;\r
1139                         });\r
1140                 },\r
1141 \r
1142                 encodeNumeric : function(text, attr) {\r
1143                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1144                                 // Multi byte sequence convert it to a single entity\r
1145                                 if (chr.length > 1)\r
1146                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';\r
1147 \r
1148                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';\r
1149                         });\r
1150                 },\r
1151 \r
1152                 encodeNamed : function(text, attr, entities) {\r
1153                         entities = entities || namedEntities;\r
1154 \r
1155                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1156                                 return baseEntities[chr] || entities[chr] || chr;\r
1157                         });\r
1158                 },\r
1159 \r
1160                 getEncodeFunc : function(name, entities) {\r
1161                         var Entities = tinymce.html.Entities;\r
1162 \r
1163                         entities = buildEntitiesLookup(entities) || namedEntities;\r
1164 \r
1165                         function encodeNamedAndNumeric(text, attr) {\r
1166                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1167                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;\r
1168                                 });\r
1169                         };\r
1170 \r
1171                         function encodeCustomNamed(text, attr) {\r
1172                                 return Entities.encodeNamed(text, attr, entities);\r
1173                         };\r
1174 \r
1175                         // Replace + with , to be compatible with previous TinyMCE versions\r
1176                         name = tinymce.makeMap(name.replace(/\+/g, ','));\r
1177 \r
1178                         // Named and numeric encoder\r
1179                         if (name.named && name.numeric)\r
1180                                 return encodeNamedAndNumeric;\r
1181 \r
1182                         // Named encoder\r
1183                         if (name.named) {\r
1184                                 // Custom names\r
1185                                 if (entities)\r
1186                                         return encodeCustomNamed;\r
1187 \r
1188                                 return Entities.encodeNamed;\r
1189                         }\r
1190 \r
1191                         // Numeric\r
1192                         if (name.numeric)\r
1193                                 return Entities.encodeNumeric;\r
1194 \r
1195                         // Raw encoder\r
1196                         return Entities.encodeRaw;\r
1197                 },\r
1198 \r
1199                 decode : function(text) {\r
1200                         return text.replace(entityRegExp, function(all, numeric, value) {\r
1201                                 if (numeric) {\r
1202                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);\r
1203 \r
1204                                         // Support upper UTF\r
1205                                         if (value > 0xFFFF) {\r
1206                                                 value -= 0x10000;\r
1207 \r
1208                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));\r
1209                                         } else\r
1210                                                 return asciiMap[value] || String.fromCharCode(value);\r
1211                                 }\r
1212 \r
1213                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);\r
1214                         });\r
1215                 }\r
1216         };\r
1217 })(tinymce);\r
1218 \r
1219 tinymce.html.Styles = function(settings, schema) {\r
1220         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,\r
1221                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,\r
1222                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,\r
1223                 trimRightRegExp = /\s+$/,\r
1224                 urlColorRegExp = /rgb/,\r
1225                 undef, i, encodingLookup = {}, encodingItems;\r
1226 \r
1227         settings = settings || {};\r
1228 \r
1229         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');\r
1230         for (i = 0; i < encodingItems.length; i++) {\r
1231                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;\r
1232                 encodingLookup['\uFEFF' + i] = encodingItems[i];\r
1233         }\r
1234 \r
1235         function toHex(match, r, g, b) {\r
1236                 function hex(val) {\r
1237                         val = parseInt(val).toString(16);\r
1238 \r
1239                         return val.length > 1 ? val : '0' + val; // 0 -> 00\r
1240                 };\r
1241 \r
1242                 return '#' + hex(r) + hex(g) + hex(b);\r
1243         };\r
1244 \r
1245         return {\r
1246                 toHex : function(color) {\r
1247                         return color.replace(rgbRegExp, toHex);\r
1248                 },\r
1249 \r
1250                 parse : function(css) {\r
1251                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;\r
1252 \r
1253                         function compress(prefix, suffix) {\r
1254                                 var top, right, bottom, left;\r
1255 \r
1256                                 // Get values and check it it needs compressing\r
1257                                 top = styles[prefix + '-top' + suffix];\r
1258                                 if (!top)\r
1259                                         return;\r
1260 \r
1261                                 right = styles[prefix + '-right' + suffix];\r
1262                                 if (top != right)\r
1263                                         return;\r
1264 \r
1265                                 bottom = styles[prefix + '-bottom' + suffix];\r
1266                                 if (right != bottom)\r
1267                                         return;\r
1268 \r
1269                                 left = styles[prefix + '-left' + suffix];\r
1270                                 if (bottom != left)\r
1271                                         return;\r
1272 \r
1273                                 // Compress\r
1274                                 styles[prefix + suffix] = left;\r
1275                                 delete styles[prefix + '-top' + suffix];\r
1276                                 delete styles[prefix + '-right' + suffix];\r
1277                                 delete styles[prefix + '-bottom' + suffix];\r
1278                                 delete styles[prefix + '-left' + suffix];\r
1279                         };\r
1280 \r
1281                         function canCompress(key) {\r
1282                                 var value = styles[key], i;\r
1283 \r
1284                                 if (!value || value.indexOf(' ') < 0)\r
1285                                         return;\r
1286 \r
1287                                 value = value.split(' ');\r
1288                                 i = value.length;\r
1289                                 while (i--) {\r
1290                                         if (value[i] !== value[0])\r
1291                                                 return false;\r
1292                                 }\r
1293 \r
1294                                 styles[key] = value[0];\r
1295 \r
1296                                 return true;\r
1297                         };\r
1298 \r
1299                         function compress2(target, a, b, c) {\r
1300                                 if (!canCompress(a))\r
1301                                         return;\r
1302 \r
1303                                 if (!canCompress(b))\r
1304                                         return;\r
1305 \r
1306                                 if (!canCompress(c))\r
1307                                         return;\r
1308 \r
1309                                 // Compress\r
1310                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];\r
1311                                 delete styles[a];\r
1312                                 delete styles[b];\r
1313                                 delete styles[c];\r
1314                         };\r
1315 \r
1316                         // Encodes the specified string by replacing all \" \' ; : with _<num>\r
1317                         function encode(str) {\r
1318                                 isEncoded = true;\r
1319 \r
1320                                 return encodingLookup[str];\r
1321                         };\r
1322 \r
1323                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc\r
1324                         // It will also decode the \" \' if keep_slashes is set to fale or omitted\r
1325                         function decode(str, keep_slashes) {\r
1326                                 if (isEncoded) {\r
1327                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {\r
1328                                                 return encodingLookup[str];\r
1329                                         });\r
1330                                 }\r
1331 \r
1332                                 if (!keep_slashes)\r
1333                                         str = str.replace(/\\([\'\";:])/g, "$1");\r
1334 \r
1335                                 return str;\r
1336                         }\r
1337 \r
1338                         if (css) {\r
1339                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing\r
1340                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {\r
1341                                         return str.replace(/[;:]/g, encode);\r
1342                                 });\r
1343 \r
1344                                 // Parse styles\r
1345                                 while (matches = styleRegExp.exec(css)) {\r
1346                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();\r
1347                                         value = matches[2].replace(trimRightRegExp, '');\r
1348 \r
1349                                         if (name && value.length > 0) {\r
1350                                                 // Opera will produce 700 instead of bold in their style values\r
1351                                                 if (name === 'font-weight' && value === '700')\r
1352                                                         value = 'bold';\r
1353                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED\r
1354                                                         value = value.toLowerCase();            \r
1355 \r
1356                                                 // Convert RGB colors to HEX\r
1357                                                 value = value.replace(rgbRegExp, toHex);\r
1358 \r
1359                                                 // Convert URLs and force them into url('value') format\r
1360                                                 value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {\r
1361                                                         str = str || str2;\r
1362 \r
1363                                                         if (str) {\r
1364                                                                 str = decode(str);\r
1365 \r
1366                                                                 // Force strings into single quote format\r
1367                                                                 return "'" + str.replace(/\'/g, "\\'") + "'";\r
1368                                                         }\r
1369 \r
1370                                                         url = decode(url || url2 || url3);\r
1371 \r
1372                                                         // Convert the URL to relative/absolute depending on config\r
1373                                                         if (urlConverter)\r
1374                                                                 url = urlConverter.call(urlConverterScope, url, 'style');\r
1375 \r
1376                                                         // Output new URL format\r
1377                                                         return "url('" + url.replace(/\'/g, "\\'") + "')";\r
1378                                                 });\r
1379 \r
1380                                                 styles[name] = isEncoded ? decode(value, true) : value;\r
1381                                         }\r
1382 \r
1383                                         styleRegExp.lastIndex = matches.index + matches[0].length;\r
1384                                 }\r
1385 \r
1386                                 // Compress the styles to reduce it's size for example IE will expand styles\r
1387                                 compress("border", "");\r
1388                                 compress("border", "-width");\r
1389                                 compress("border", "-color");\r
1390                                 compress("border", "-style");\r
1391                                 compress("padding", "");\r
1392                                 compress("margin", "");\r
1393                                 compress2('border', 'border-width', 'border-style', 'border-color');\r
1394 \r
1395                                 // Remove pointless border, IE produces these\r
1396                                 if (styles.border === 'medium none')\r
1397                                         delete styles.border;\r
1398                         }\r
1399 \r
1400                         return styles;\r
1401                 },\r
1402 \r
1403                 serialize : function(styles, element_name) {\r
1404                         var css = '', name, value;\r
1405 \r
1406                         function serializeStyles(name) {\r
1407                                 var styleList, i, l, value;\r
1408 \r
1409                                 styleList = schema.styles[name];\r
1410                                 if (styleList) {\r
1411                                         for (i = 0, l = styleList.length; i < l; i++) {\r
1412                                                 name = styleList[i];\r
1413                                                 value = styles[name];\r
1414 \r
1415                                                 if (value !== undef && value.length > 0)\r
1416                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
1417                                         }\r
1418                                 }\r
1419                         };\r
1420 \r
1421                         // Serialize styles according to schema\r
1422                         if (element_name && schema && schema.styles) {\r
1423                                 // Serialize global styles and element specific styles\r
1424                                 serializeStyles('*');\r
1425                                 serializeStyles(element_name);\r
1426                         } else {\r
1427                                 // Output the styles in the order they are inside the object\r
1428                                 for (name in styles) {\r
1429                                         value = styles[name];\r
1430 \r
1431                                         if (value !== undef && value.length > 0)\r
1432                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
1433                                 }\r
1434                         }\r
1435 \r
1436                         return css;\r
1437                 }\r
1438         };\r
1439 };\r
1440 \r
1441 (function(tinymce) {\r
1442         var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},\r
1443                 whiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;\r
1444 \r
1445         function split(str, delim) {\r
1446                 return str.split(delim || ',');\r
1447         };\r
1448 \r
1449         function unpack(lookup, data) {\r
1450                 var key, elements = {};\r
1451 \r
1452                 function replace(value) {\r
1453                         return value.replace(/[A-Z]+/g, function(key) {\r
1454                                 return replace(lookup[key]);\r
1455                         });\r
1456                 };\r
1457 \r
1458                 // Unpack lookup\r
1459                 for (key in lookup) {\r
1460                         if (lookup.hasOwnProperty(key))\r
1461                                 lookup[key] = replace(lookup[key]);\r
1462                 }\r
1463 \r
1464                 // Unpack and parse data into object map\r
1465                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {\r
1466                         attributes = split(attributes, '|');\r
1467 \r
1468                         elements[name] = {\r
1469                                 attributes : makeMap(attributes),\r
1470                                 attributesOrder : attributes,\r
1471                                 children : makeMap(children, '|', {'#comment' : {}})\r
1472                         }\r
1473                 });\r
1474 \r
1475                 return elements;\r
1476         };\r
1477 \r
1478         // Build a lookup table for block elements both lowercase and uppercase\r
1479         blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' + \r
1480                                                 'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' + \r
1481                                                 'noscript,menu,isindex,samp,header,footer,article,section,hgroup';\r
1482         blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));\r
1483 \r
1484         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size\r
1485         transitional = unpack({\r
1486                 Z : 'H|K|N|O|P',\r
1487                 Y : 'X|form|R|Q',\r
1488                 ZG : 'E|span|width|align|char|charoff|valign',\r
1489                 X : 'p|T|div|U|W|isindex|fieldset|table',\r
1490                 ZF : 'E|align|char|charoff|valign',\r
1491                 W : 'pre|hr|blockquote|address|center|noframes',\r
1492                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',\r
1493                 ZD : '[E][S]',\r
1494                 U : 'ul|ol|dl|menu|dir',\r
1495                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',\r
1496                 T : 'h1|h2|h3|h4|h5|h6',\r
1497                 ZB : 'X|S|Q',\r
1498                 S : 'R|P',\r
1499                 ZA : 'a|G|J|M|O|P',\r
1500                 R : 'a|H|K|N|O',\r
1501                 Q : 'noscript|P',\r
1502                 P : 'ins|del|script',\r
1503                 O : 'input|select|textarea|label|button',\r
1504                 N : 'M|L',\r
1505                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',\r
1506                 L : 'sub|sup',\r
1507                 K : 'J|I',\r
1508                 J : 'tt|i|b|u|s|strike',\r
1509                 I : 'big|small|font|basefont',\r
1510                 H : 'G|F',\r
1511                 G : 'br|span|bdo',\r
1512                 F : 'object|applet|img|map|iframe',\r
1513                 E : 'A|B|C',\r
1514                 D : 'accesskey|tabindex|onfocus|onblur',\r
1515                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',\r
1516                 B : 'lang|xml:lang|dir',\r
1517                 A : 'id|class|style|title'\r
1518         }, 'script[id|charset|type|language|src|defer|xml:space][]' + \r
1519                 'style[B|id|type|media|title|xml:space][]' + \r
1520                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + \r
1521                 'param[id|name|value|valuetype|type][]' + \r
1522                 'p[E|align][#|S]' + \r
1523                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + \r
1524                 'br[A|clear][]' + \r
1525                 'span[E][#|S]' + \r
1526                 'bdo[A|C|B][#|S]' + \r
1527                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + \r
1528                 'h1[E|align][#|S]' + \r
1529                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + \r
1530                 'map[B|C|A|name][X|form|Q|area]' + \r
1531                 'h2[E|align][#|S]' + \r
1532                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + \r
1533                 'h3[E|align][#|S]' + \r
1534                 'tt[E][#|S]' + \r
1535                 'i[E][#|S]' + \r
1536                 'b[E][#|S]' + \r
1537                 'u[E][#|S]' + \r
1538                 's[E][#|S]' + \r
1539                 'strike[E][#|S]' + \r
1540                 'big[E][#|S]' + \r
1541                 'small[E][#|S]' + \r
1542                 'font[A|B|size|color|face][#|S]' + \r
1543                 'basefont[id|size|color|face][]' + \r
1544                 'em[E][#|S]' + \r
1545                 'strong[E][#|S]' + \r
1546                 'dfn[E][#|S]' + \r
1547                 'code[E][#|S]' + \r
1548                 'q[E|cite][#|S]' + \r
1549                 'samp[E][#|S]' + \r
1550                 'kbd[E][#|S]' + \r
1551                 'var[E][#|S]' + \r
1552                 'cite[E][#|S]' + \r
1553                 'abbr[E][#|S]' + \r
1554                 'acronym[E][#|S]' + \r
1555                 'sub[E][#|S]' + \r
1556                 'sup[E][#|S]' + \r
1557                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + \r
1558                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + \r
1559                 'optgroup[E|disabled|label][option]' + \r
1560                 'option[E|selected|disabled|label|value][]' + \r
1561                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + \r
1562                 'label[E|for|accesskey|onfocus|onblur][#|S]' + \r
1563                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + \r
1564                 'h4[E|align][#|S]' + \r
1565                 'ins[E|cite|datetime][#|Y]' + \r
1566                 'h5[E|align][#|S]' + \r
1567                 'del[E|cite|datetime][#|Y]' + \r
1568                 'h6[E|align][#|S]' + \r
1569                 'div[E|align][#|Y]' + \r
1570                 'ul[E|type|compact][li]' + \r
1571                 'li[E|type|value][#|Y]' + \r
1572                 'ol[E|type|compact|start][li]' + \r
1573                 'dl[E|compact][dt|dd]' + \r
1574                 'dt[E][#|S]' + \r
1575                 'dd[E][#|Y]' + \r
1576                 'menu[E|compact][li]' + \r
1577                 'dir[E|compact][li]' + \r
1578                 'pre[E|width|xml:space][#|ZA]' + \r
1579                 'hr[E|align|noshade|size|width][]' + \r
1580                 'blockquote[E|cite][#|Y]' + \r
1581                 'address[E][#|S|p]' + \r
1582                 'center[E][#|Y]' + \r
1583                 'noframes[E][#|Y]' + \r
1584                 'isindex[A|B|prompt][]' + \r
1585                 'fieldset[E][#|legend|Y]' + \r
1586                 'legend[E|accesskey|align][#|S]' + \r
1587                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + \r
1588                 'caption[E|align][#|S]' + \r
1589                 'col[ZG][]' + \r
1590                 'colgroup[ZG][col]' + \r
1591                 'thead[ZF][tr]' + \r
1592                 'tr[ZF|bgcolor][th|td]' + \r
1593                 'th[E|ZE][#|Y]' + \r
1594                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + \r
1595                 'noscript[E][#|Y]' + \r
1596                 'td[E|ZE][#|Y]' + \r
1597                 'tfoot[ZF][tr]' + \r
1598                 'tbody[ZF][tr]' + \r
1599                 'area[E|D|shape|coords|href|nohref|alt|target][]' + \r
1600                 'base[id|href|target][]' + \r
1601                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'\r
1602         );\r
1603 \r
1604         boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,preload,autoplay,loop,controls');\r
1605         shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');\r
1606         nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,object'), shortEndedElementsMap);\r
1607         whiteSpaceElementsMap = makeMap('pre,script,style');\r
1608         selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');\r
1609 \r
1610         tinymce.html.Schema = function(settings) {\r
1611                 var self = this, elements = {}, children = {}, patternElements = [], validStyles;\r
1612 \r
1613                 settings = settings || {};\r
1614 \r
1615                 // Allow all elements and attributes if verify_html is set to false\r
1616                 if (settings.verify_html === false)\r
1617                         settings.valid_elements = '*[*]';\r
1618 \r
1619                 // Build styles list\r
1620                 if (settings.valid_styles) {\r
1621                         validStyles = {};\r
1622 \r
1623                         // Convert styles into a rule list\r
1624                         each(settings.valid_styles, function(value, key) {\r
1625                                 validStyles[key] = tinymce.explode(value);\r
1626                         });\r
1627                 }\r
1628 \r
1629                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.\r
1630                 function patternToRegExp(str) {\r
1631                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');\r
1632                 };\r
1633 \r
1634                 // Parses the specified valid_elements string and adds to the current rules\r
1635                 // This function is a bit hard to read since it's heavily optimized for speed\r
1636                 function addValidElements(valid_elements) {\r
1637                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,\r
1638                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,\r
1639                                 elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,\r
1640                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,\r
1641                                 hasPatternsRegExp = /[*?+]/;\r
1642 \r
1643                         if (valid_elements) {\r
1644                                 // Split valid elements into an array with rules\r
1645                                 valid_elements = split(valid_elements);\r
1646 \r
1647                                 if (elements['@']) {\r
1648                                         globalAttributes = elements['@'].attributes;\r
1649                                         globalAttributesOrder = elements['@'].attributesOrder;\r
1650                                 }\r
1651 \r
1652                                 // Loop all rules\r
1653                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {\r
1654                                         // Parse element rule\r
1655                                         matches = elementRuleRegExp.exec(valid_elements[ei]);\r
1656                                         if (matches) {\r
1657                                                 // Setup local names for matches\r
1658                                                 prefix = matches[1];\r
1659                                                 elementName = matches[2];\r
1660                                                 outputName = matches[3];\r
1661                                                 attrData = matches[4];\r
1662 \r
1663                                                 // Create new attributes and attributesOrder\r
1664                                                 attributes = {};\r
1665                                                 attributesOrder = [];\r
1666 \r
1667                                                 // Create the new element\r
1668                                                 element = {\r
1669                                                         attributes : attributes,\r
1670                                                         attributesOrder : attributesOrder\r
1671                                                 };\r
1672 \r
1673                                                 // Padd empty elements prefix\r
1674                                                 if (prefix === '#')\r
1675                                                         element.paddEmpty = true;\r
1676 \r
1677                                                 // Remove empty elements prefix\r
1678                                                 if (prefix === '-')\r
1679                                                         element.removeEmpty = true;\r
1680 \r
1681                                                 // Copy attributes from global rule into current rule\r
1682                                                 if (globalAttributes) {\r
1683                                                         for (key in globalAttributes)\r
1684                                                                 attributes[key] = globalAttributes[key];\r
1685 \r
1686                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);\r
1687                                                 }\r
1688 \r
1689                                                 // Attributes defined\r
1690                                                 if (attrData) {\r
1691                                                         attrData = split(attrData, '|');\r
1692                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {\r
1693                                                                 matches = attrRuleRegExp.exec(attrData[ai]);\r
1694                                                                 if (matches) {\r
1695                                                                         attr = {};\r
1696                                                                         attrType = matches[1];\r
1697                                                                         attrName = matches[2].replace(/::/g, ':');\r
1698                                                                         prefix = matches[3];\r
1699                                                                         value = matches[4];\r
1700 \r
1701                                                                         // Required\r
1702                                                                         if (attrType === '!') {\r
1703                                                                                 element.attributesRequired = element.attributesRequired || [];\r
1704                                                                                 element.attributesRequired.push(attrName);\r
1705                                                                                 attr.required = true;\r
1706                                                                         }\r
1707 \r
1708                                                                         // Denied from global\r
1709                                                                         if (attrType === '-') {\r
1710                                                                                 delete attributes[attrName];\r
1711                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);\r
1712                                                                                 continue;\r
1713                                                                         }\r
1714 \r
1715                                                                         // Default value\r
1716                                                                         if (prefix) {\r
1717                                                                                 // Default value\r
1718                                                                                 if (prefix === '=') {\r
1719                                                                                         element.attributesDefault = element.attributesDefault || [];\r
1720                                                                                         element.attributesDefault.push({name: attrName, value: value});\r
1721                                                                                         attr.defaultValue = value;\r
1722                                                                                 }\r
1723 \r
1724                                                                                 // Forced value\r
1725                                                                                 if (prefix === ':') {\r
1726                                                                                         element.attributesForced = element.attributesForced || [];\r
1727                                                                                         element.attributesForced.push({name: attrName, value: value});\r
1728                                                                                         attr.forcedValue = value;\r
1729                                                                                 }\r
1730 \r
1731                                                                                 // Required values\r
1732                                                                                 if (prefix === '<')\r
1733                                                                                         attr.validValues = makeMap(value, '?');\r
1734                                                                         }\r
1735 \r
1736                                                                         // Check for attribute patterns\r
1737                                                                         if (hasPatternsRegExp.test(attrName)) {\r
1738                                                                                 element.attributePatterns = element.attributePatterns || [];\r
1739                                                                                 attr.pattern = patternToRegExp(attrName);\r
1740                                                                                 element.attributePatterns.push(attr);\r
1741                                                                         } else {\r
1742                                                                                 // Add attribute to order list if it doesn't already exist\r
1743                                                                                 if (!attributes[attrName])\r
1744                                                                                         attributesOrder.push(attrName);\r
1745 \r
1746                                                                                 attributes[attrName] = attr;\r
1747                                                                         }\r
1748                                                                 }\r
1749                                                         }\r
1750                                                 }\r
1751 \r
1752                                                 // Global rule, store away these for later usage\r
1753                                                 if (!globalAttributes && elementName == '@') {\r
1754                                                         globalAttributes = attributes;\r
1755                                                         globalAttributesOrder = attributesOrder;\r
1756                                                 }\r
1757 \r
1758                                                 // Handle substitute elements such as b/strong\r
1759                                                 if (outputName) {\r
1760                                                         element.outputName = elementName;\r
1761                                                         elements[outputName] = element;\r
1762                                                 }\r
1763 \r
1764                                                 // Add pattern or exact element\r
1765                                                 if (hasPatternsRegExp.test(elementName)) {\r
1766                                                         element.pattern = patternToRegExp(elementName);\r
1767                                                         patternElements.push(element);\r
1768                                                 } else\r
1769                                                         elements[elementName] = element;\r
1770                                         }\r
1771                                 }\r
1772                         }\r
1773                 };\r
1774 \r
1775                 function setValidElements(valid_elements) {\r
1776                         elements = {};\r
1777                         patternElements = [];\r
1778 \r
1779                         addValidElements(valid_elements);\r
1780 \r
1781                         each(transitional, function(element, name) {\r
1782                                 children[name] = element.children;\r
1783                         });\r
1784                 };\r
1785 \r
1786                 // Adds custom non HTML elements to the schema\r
1787                 function addCustomElements(custom_elements) {\r
1788                         var customElementRegExp = /^(~)?(.+)$/;\r
1789 \r
1790                         if (custom_elements) {\r
1791                                 each(split(custom_elements), function(rule) {\r
1792                                         var matches = customElementRegExp.exec(rule),\r
1793                                                 inline = matches[1] === '~',\r
1794                                                 cloneName = inline ? 'span' : 'div',\r
1795                                                 name = matches[2];\r
1796 \r
1797                                         children[name] = children[cloneName];\r
1798                                         customElementsMap[name] = cloneName;\r
1799 \r
1800                                         // If it's not marked as inline then add it to valid block elements\r
1801                                         if (!inline)\r
1802                                                 blockElementsMap[name] = {};\r
1803 \r
1804                                         // Add custom elements at span/div positions\r
1805                                         each(children, function(element, child) {\r
1806                                                 if (element[cloneName])\r
1807                                                         element[name] = element[cloneName];\r
1808                                         });\r
1809                                 });\r
1810                         }\r
1811                 };\r
1812 \r
1813                 // Adds valid children to the schema object\r
1814                 function addValidChildren(valid_children) {\r
1815                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;\r
1816 \r
1817                         if (valid_children) {\r
1818                                 each(split(valid_children), function(rule) {\r
1819                                         var matches = childRuleRegExp.exec(rule), parent, prefix;\r
1820 \r
1821                                         if (matches) {\r
1822                                                 prefix = matches[1];\r
1823 \r
1824                                                 // Add/remove items from default\r
1825                                                 if (prefix)\r
1826                                                         parent = children[matches[2]];\r
1827                                                 else\r
1828                                                         parent = children[matches[2]] = {'#comment' : {}};\r
1829 \r
1830                                                 parent = children[matches[2]];\r
1831 \r
1832                                                 each(split(matches[3], '|'), function(child) {\r
1833                                                         if (prefix === '-')\r
1834                                                                 delete parent[child];\r
1835                                                         else\r
1836                                                                 parent[child] = {};\r
1837                                                 });\r
1838                                         }\r
1839                                 });\r
1840                         }\r
1841                 }\r
1842 \r
1843                 if (!settings.valid_elements) {\r
1844                         // No valid elements defined then clone the elements from the transitional spec\r
1845                         each(transitional, function(element, name) {\r
1846                                 elements[name] = {\r
1847                                         attributes : element.attributes,\r
1848                                         attributesOrder : element.attributesOrder\r
1849                                 };\r
1850 \r
1851                                 children[name] = element.children;\r
1852                         });\r
1853 \r
1854                         // Switch these\r
1855                         each(split('strong/b,em/i'), function(item) {\r
1856                                 item = split(item, '/');\r
1857                                 elements[item[1]].outputName = item[0];\r
1858                         });\r
1859 \r
1860                         // Add default alt attribute for images\r
1861                         elements.img.attributesDefault = [{name: 'alt', value: ''}];\r
1862 \r
1863                         // Remove these if they are empty by default\r
1864                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {\r
1865                                 elements[name].removeEmpty = true;\r
1866                         });\r
1867 \r
1868                         // Padd these by default\r
1869                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {\r
1870                                 elements[name].paddEmpty = true;\r
1871                         });\r
1872                 } else\r
1873                         setValidElements(settings.valid_elements);\r
1874 \r
1875                 addCustomElements(settings.custom_elements);\r
1876                 addValidChildren(settings.valid_children);\r
1877                 addValidElements(settings.extended_valid_elements);\r
1878 \r
1879                 // Todo: Remove this when we fix list handling to be valid\r
1880                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');\r
1881 \r
1882                 // Delete invalid elements\r
1883                 if (settings.invalid_elements) {\r
1884                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {\r
1885                                 if (elements[item])\r
1886                                         delete elements[item];\r
1887                         });\r
1888                 }\r
1889 \r
1890                 self.children = children;\r
1891 \r
1892                 self.styles = validStyles;\r
1893 \r
1894                 self.getBoolAttrs = function() {\r
1895                         return boolAttrMap;\r
1896                 };\r
1897 \r
1898                 self.getBlockElements = function() {\r
1899                         return blockElementsMap;\r
1900                 };\r
1901 \r
1902                 self.getShortEndedElements = function() {\r
1903                         return shortEndedElementsMap;\r
1904                 };\r
1905 \r
1906                 self.getSelfClosingElements = function() {\r
1907                         return selfClosingElementsMap;\r
1908                 };\r
1909 \r
1910                 self.getNonEmptyElements = function() {\r
1911                         return nonEmptyElementsMap;\r
1912                 };\r
1913 \r
1914                 self.getWhiteSpaceElements = function() {\r
1915                         return whiteSpaceElementsMap;\r
1916                 };\r
1917 \r
1918                 self.isValidChild = function(name, child) {\r
1919                         var parent = children[name];\r
1920 \r
1921                         return !!(parent && parent[child]);\r
1922                 };\r
1923 \r
1924                 self.getElementRule = function(name) {\r
1925                         var element = elements[name], i;\r
1926 \r
1927                         // Exact match found\r
1928                         if (element)\r
1929                                 return element;\r
1930 \r
1931                         // No exact match then try the patterns\r
1932                         i = patternElements.length;\r
1933                         while (i--) {\r
1934                                 element = patternElements[i];\r
1935 \r
1936                                 if (element.pattern.test(name))\r
1937                                         return element;\r
1938                         }\r
1939                 };\r
1940 \r
1941                 self.getCustomElements = function() {\r
1942                         return customElementsMap;\r
1943                 };\r
1944 \r
1945                 self.addValidElements = addValidElements;\r
1946 \r
1947                 self.setValidElements = setValidElements;\r
1948 \r
1949                 self.addCustomElements = addCustomElements;\r
1950 \r
1951                 self.addValidChildren = addValidChildren;\r
1952         };\r
1953 \r
1954         // Expose boolMap and blockElementMap as static properties for usage in DOMUtils\r
1955         tinymce.html.Schema.boolAttrMap = boolAttrMap;\r
1956         tinymce.html.Schema.blockElementsMap = blockElementsMap;\r
1957 })(tinymce);\r
1958 \r
1959 (function(tinymce) {\r
1960         tinymce.html.SaxParser = function(settings, schema) {\r
1961                 var self = this, noop = function() {};\r
1962 \r
1963                 settings = settings || {};\r
1964                 self.schema = schema = schema || new tinymce.html.Schema();\r
1965 \r
1966                 if (settings.fix_self_closing !== false)\r
1967                         settings.fix_self_closing = true;\r
1968 \r
1969                 // Add handler functions from settings and setup default handlers\r
1970                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {\r
1971                         if (name)\r
1972                                 self[name] = settings[name] || noop;\r
1973                 });\r
1974 \r
1975                 self.parse = function(html) {\r
1976                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name,\r
1977                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue,\r
1978                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,\r
1979                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing;\r
1980 \r
1981                         function processEndTag(name) {\r
1982                                 var pos, i;\r
1983 \r
1984                                 // Find position of parent of the same type\r
1985                                 pos = stack.length;\r
1986                                 while (pos--) {\r
1987                                         if (stack[pos].name === name)\r
1988                                                 break;                                          \r
1989                                 }\r
1990 \r
1991                                 // Found parent\r
1992                                 if (pos >= 0) {\r
1993                                         // Close all the open elements\r
1994                                         for (i = stack.length - 1; i >= pos; i--) {\r
1995                                                 name = stack[i];\r
1996 \r
1997                                                 if (name.valid)\r
1998                                                         self.end(name.name);\r
1999                                         }\r
2000 \r
2001                                         // Remove the open elements from the stack\r
2002                                         stack.length = pos;\r
2003                                 }\r
2004                         };\r
2005 \r
2006                         // Precompile RegExps and map objects\r
2007                         tokenRegExp = new RegExp('<(?:' +\r
2008                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment\r
2009                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA\r
2010                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE\r
2011                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI\r
2012                                 '(?:\\/([^>]+)>)|' + // End element\r
2013                                 '(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element\r
2014                         ')', 'g');\r
2015 \r
2016                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;\r
2017                         specialElements = {\r
2018                                 'script' : /<\/script[^>]*>/gi,\r
2019                                 'style' : /<\/style[^>]*>/gi,\r
2020                                 'noscript' : /<\/noscript[^>]*>/gi\r
2021                         };\r
2022 \r
2023                         // Setup lookup tables for empty elements and boolean attributes\r
2024                         shortEndedElements = schema.getShortEndedElements();\r
2025                         selfClosing = schema.getSelfClosingElements();\r
2026                         fillAttrsMap = schema.getBoolAttrs();\r
2027                         validate = settings.validate;\r
2028                         fixSelfClosing = settings.fix_self_closing;\r
2029 \r
2030                         while (matches = tokenRegExp.exec(html)) {\r
2031                                 // Text\r
2032                                 if (index < matches.index)\r
2033                                         self.text(decode(html.substr(index, matches.index - index)));\r
2034 \r
2035                                 if (value = matches[6]) { // End element\r
2036                                         processEndTag(value.toLowerCase());\r
2037                                 } else if (value = matches[7]) { // Start element\r
2038                                         value = value.toLowerCase();\r
2039                                         isShortEnded = value in shortEndedElements;\r
2040 \r
2041                                         // Is self closing tag for example an <li> after an open <li>\r
2042                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)\r
2043                                                 processEndTag(value);\r
2044 \r
2045                                         // Validate element\r
2046                                         if (!validate || (elementRule = schema.getElementRule(value))) {\r
2047                                                 isValidElement = true;\r
2048 \r
2049                                                 // Grab attributes map and patters when validation is enabled\r
2050                                                 if (validate) {\r
2051                                                         validAttributesMap = elementRule.attributes;\r
2052                                                         validAttributePatterns = elementRule.attributePatterns;\r
2053                                                 }\r
2054 \r
2055                                                 // Parse attributes\r
2056                                                 if (attribsValue = matches[8]) {\r
2057                                                         attrList = [];\r
2058                                                         attrList.map = {};\r
2059 \r
2060                                                         attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {\r
2061                                                                 var attrRule, i;\r
2062 \r
2063                                                                 name = name.toLowerCase();\r
2064                                                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute\r
2065 \r
2066                                                                 // Validate name and value\r
2067                                                                 if (validate && name.indexOf('data-') !== 0) {\r
2068                                                                         attrRule = validAttributesMap[name];\r
2069 \r
2070                                                                         // Find rule by pattern matching\r
2071                                                                         if (!attrRule && validAttributePatterns) {\r
2072                                                                                 i = validAttributePatterns.length;\r
2073                                                                                 while (i--) {\r
2074                                                                                         attrRule = validAttributePatterns[i];\r
2075                                                                                         if (attrRule.pattern.test(name))\r
2076                                                                                                 break;\r
2077                                                                                 }\r
2078 \r
2079                                                                                 // No rule matched\r
2080                                                                                 if (i === -1)\r
2081                                                                                         attrRule = null;\r
2082                                                                         }\r
2083 \r
2084                                                                         // No attribute rule found\r
2085                                                                         if (!attrRule)\r
2086                                                                                 return;\r
2087 \r
2088                                                                         // Validate value\r
2089                                                                         if (attrRule.validValues && !(value in attrRule.validValues))\r
2090                                                                                 return;\r
2091                                                                 }\r
2092 \r
2093                                                                 // Add attribute to list and map\r
2094                                                                 attrList.map[name] = value;\r
2095                                                                 attrList.push({\r
2096                                                                         name: name,\r
2097                                                                         value: value\r
2098                                                                 });\r
2099                                                         });\r
2100                                                 } else {\r
2101                                                         attrList = [];\r
2102                                                         attrList.map = {};\r
2103                                                 }\r
2104 \r
2105                                                 // Process attributes if validation is enabled\r
2106                                                 if (validate) {\r
2107                                                         attributesRequired = elementRule.attributesRequired;\r
2108                                                         attributesDefault = elementRule.attributesDefault;\r
2109                                                         attributesForced = elementRule.attributesForced;\r
2110 \r
2111                                                         // Handle forced attributes\r
2112                                                         if (attributesForced) {\r
2113                                                                 i = attributesForced.length;\r
2114                                                                 while (i--) {\r
2115                                                                         attr = attributesForced[i];\r
2116                                                                         name = attr.name;\r
2117                                                                         attrValue = attr.value;\r
2118 \r
2119                                                                         if (attrValue === '{$uid}')\r
2120                                                                                 attrValue = 'mce_' + idCount++;\r
2121 \r
2122                                                                         attrList.map[name] = attrValue;\r
2123                                                                         attrList.push({name: name, value: attrValue});\r
2124                                                                 }\r
2125                                                         }\r
2126 \r
2127                                                         // Handle default attributes\r
2128                                                         if (attributesDefault) {\r
2129                                                                 i = attributesDefault.length;\r
2130                                                                 while (i--) {\r
2131                                                                         attr = attributesDefault[i];\r
2132                                                                         name = attr.name;\r
2133 \r
2134                                                                         if (!(name in attrList.map)) {\r
2135                                                                                 attrValue = attr.value;\r
2136 \r
2137                                                                                 if (attrValue === '{$uid}')\r
2138                                                                                         attrValue = 'mce_' + idCount++;\r
2139 \r
2140                                                                                 attrList.map[name] = attrValue;\r
2141                                                                                 attrList.push({name: name, value: attrValue});\r
2142                                                                         }\r
2143                                                                 }\r
2144                                                         }\r
2145 \r
2146                                                         // Handle required attributes\r
2147                                                         if (attributesRequired) {\r
2148                                                                 i = attributesRequired.length;\r
2149                                                                 while (i--) {\r
2150                                                                         if (attributesRequired[i] in attrList.map)\r
2151                                                                                 break;\r
2152                                                                 }\r
2153 \r
2154                                                                 // None of the required attributes where found\r
2155                                                                 if (i === -1)\r
2156                                                                         isValidElement = false;\r
2157                                                         }\r
2158 \r
2159                                                         // Invalidate element if it's marked as bogus\r
2160                                                         if (attrList.map['data-mce-bogus'])\r
2161                                                                 isValidElement = false;\r
2162                                                 }\r
2163 \r
2164                                                 if (isValidElement)\r
2165                                                         self.start(value, attrList, isShortEnded);\r
2166                                         } else\r
2167                                                 isValidElement = false;\r
2168 \r
2169                                         // Treat script, noscript and style a bit different since they may include code that looks like elements\r
2170                                         if (endRegExp = specialElements[value]) {\r
2171                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;\r
2172 \r
2173                                                 if (matches = endRegExp.exec(html)) {\r
2174                                                         if (isValidElement)\r
2175                                                                 text = html.substr(index, matches.index - index);\r
2176 \r
2177                                                         index = matches.index + matches[0].length;\r
2178                                                 } else {\r
2179                                                         text = html.substr(index);\r
2180                                                         index = html.length;\r
2181                                                 }\r
2182 \r
2183                                                 if (isValidElement && text.length > 0)\r
2184                                                         self.text(text, true);\r
2185 \r
2186                                                 if (isValidElement)\r
2187                                                         self.end(value);\r
2188 \r
2189                                                 tokenRegExp.lastIndex = index;\r
2190                                                 continue;\r
2191                                         }\r
2192 \r
2193                                         // Push value on to stack\r
2194                                         if (!isShortEnded) {\r
2195                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)\r
2196                                                         stack.push({name: value, valid: isValidElement});\r
2197                                                 else if (isValidElement)\r
2198                                                         self.end(value);\r
2199                                         }\r
2200                                 } else if (value = matches[1]) { // Comment\r
2201                                         self.comment(value);\r
2202                                 } else if (value = matches[2]) { // CDATA\r
2203                                         self.cdata(value);\r
2204                                 } else if (value = matches[3]) { // DOCTYPE\r
2205                                         self.doctype(value);\r
2206                                 } else if (value = matches[4]) { // PI\r
2207                                         self.pi(value, matches[5]);\r
2208                                 }\r
2209 \r
2210                                 index = matches.index + matches[0].length;\r
2211                         }\r
2212 \r
2213                         // Text\r
2214                         if (index < html.length)\r
2215                                 self.text(decode(html.substr(index)));\r
2216 \r
2217                         // Close any open elements\r
2218                         for (i = stack.length - 1; i >= 0; i--) {\r
2219                                 value = stack[i];\r
2220 \r
2221                                 if (value.valid)\r
2222                                         self.end(value.name);\r
2223                         }\r
2224                 };\r
2225         }\r
2226 })(tinymce);\r
2227 \r
2228 (function(tinymce) {\r
2229         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {\r
2230                 '#text' : 3,\r
2231                 '#comment' : 8,\r
2232                 '#cdata' : 4,\r
2233                 '#pi' : 7,\r
2234                 '#doctype' : 10,\r
2235                 '#document-fragment' : 11\r
2236         };\r
2237 \r
2238         // Walks the tree left/right\r
2239         function walk(node, root_node, prev) {\r
2240                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';\r
2241 \r
2242                 // Walk into nodes if it has a start\r
2243                 if (node[startName])\r
2244                         return node[startName];\r
2245 \r
2246                 // Return the sibling if it has one\r
2247                 if (node !== root_node) {\r
2248                         sibling = node[siblingName];\r
2249 \r
2250                         if (sibling)\r
2251                                 return sibling;\r
2252 \r
2253                         // Walk up the parents to look for siblings\r
2254                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {\r
2255                                 sibling = parent[siblingName];\r
2256 \r
2257                                 if (sibling)\r
2258                                         return sibling;\r
2259                         }\r
2260                 }\r
2261         };\r
2262 \r
2263         function Node(name, type) {\r
2264                 this.name = name;\r
2265                 this.type = type;\r
2266 \r
2267                 if (type === 1) {\r
2268                         this.attributes = [];\r
2269                         this.attributes.map = {};\r
2270                 }\r
2271         }\r
2272 \r
2273         tinymce.extend(Node.prototype, {\r
2274                 replace : function(node) {\r
2275                         var self = this;\r
2276 \r
2277                         if (node.parent)\r
2278                                 node.remove();\r
2279 \r
2280                         self.insert(node, self);\r
2281                         self.remove();\r
2282 \r
2283                         return self;\r
2284                 },\r
2285 \r
2286                 attr : function(name, value) {\r
2287                         var self = this, attrs, i, undef;\r
2288 \r
2289                         if (typeof name !== "string") {\r
2290                                 for (i in name)\r
2291                                         self.attr(i, name[i]);\r
2292 \r
2293                                 return self;\r
2294                         }\r
2295 \r
2296                         if (attrs = self.attributes) {\r
2297                                 if (value !== undef) {\r
2298                                         // Remove attribute\r
2299                                         if (value === null) {\r
2300                                                 if (name in attrs.map) {\r
2301                                                         delete attrs.map[name];\r
2302 \r
2303                                                         i = attrs.length;\r
2304                                                         while (i--) {\r
2305                                                                 if (attrs[i].name === name) {\r
2306                                                                         attrs = attrs.splice(i, 1);\r
2307                                                                         return self;\r
2308                                                                 }\r
2309                                                         }\r
2310                                                 }\r
2311 \r
2312                                                 return self;\r
2313                                         }\r
2314 \r
2315                                         // Set attribute\r
2316                                         if (name in attrs.map) {\r
2317                                                 // Set attribute\r
2318                                                 i = attrs.length;\r
2319                                                 while (i--) {\r
2320                                                         if (attrs[i].name === name) {\r
2321                                                                 attrs[i].value = value;\r
2322                                                                 break;\r
2323                                                         }\r
2324                                                 }\r
2325                                         } else\r
2326                                                 attrs.push({name: name, value: value});\r
2327 \r
2328                                         attrs.map[name] = value;\r
2329 \r
2330                                         return self;\r
2331                                 } else {\r
2332                                         return attrs.map[name];\r
2333                                 }\r
2334                         }\r
2335                 },\r
2336 \r
2337                 clone : function() {\r
2338                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;\r
2339 \r
2340                         // Clone element attributes\r
2341                         if (selfAttrs = self.attributes) {\r
2342                                 cloneAttrs = [];\r
2343                                 cloneAttrs.map = {};\r
2344 \r
2345                                 for (i = 0, l = selfAttrs.length; i < l; i++) {\r
2346                                         selfAttr = selfAttrs[i];\r
2347 \r
2348                                         // Clone everything except id\r
2349                                         if (selfAttr.name !== 'id') {\r
2350                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};\r
2351                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;\r
2352                                         }\r
2353                                 }\r
2354 \r
2355                                 clone.attributes = cloneAttrs;\r
2356                         }\r
2357 \r
2358                         clone.value = self.value;\r
2359                         clone.shortEnded = self.shortEnded;\r
2360 \r
2361                         return clone;\r
2362                 },\r
2363 \r
2364                 wrap : function(wrapper) {\r
2365                         var self = this;\r
2366 \r
2367                         self.parent.insert(wrapper, self);\r
2368                         wrapper.append(self);\r
2369 \r
2370                         return self;\r
2371                 },\r
2372 \r
2373                 unwrap : function() {\r
2374                         var self = this, node, next;\r
2375 \r
2376                         for (node = self.firstChild; node; ) {\r
2377                                 next = node.next;\r
2378                                 self.insert(node, self, true);\r
2379                                 node = next;\r
2380                         }\r
2381 \r
2382                         self.remove();\r
2383                 },\r
2384 \r
2385                 remove : function() {\r
2386                         var self = this, parent = self.parent, next = self.next, prev = self.prev;\r
2387 \r
2388                         if (parent) {\r
2389                                 if (parent.firstChild === self) {\r
2390                                         parent.firstChild = next;\r
2391 \r
2392                                         if (next)\r
2393                                                 next.prev = null;\r
2394                                 } else {\r
2395                                         prev.next = next;\r
2396                                 }\r
2397 \r
2398                                 if (parent.lastChild === self) {\r
2399                                         parent.lastChild = prev;\r
2400 \r
2401                                         if (prev)\r
2402                                                 prev.next = null;\r
2403                                 } else {\r
2404                                         next.prev = prev;\r
2405                                 }\r
2406 \r
2407                                 self.parent = self.next = self.prev = null;\r
2408                         }\r
2409 \r
2410                         return self;\r
2411                 },\r
2412 \r
2413                 append : function(node) {\r
2414                         var self = this, last;\r
2415 \r
2416                         if (node.parent)\r
2417                                 node.remove();\r
2418 \r
2419                         last = self.lastChild;\r
2420                         if (last) {\r
2421                                 last.next = node;\r
2422                                 node.prev = last;\r
2423                                 self.lastChild = node;\r
2424                         } else\r
2425                                 self.lastChild = self.firstChild = node;\r
2426 \r
2427                         node.parent = self;\r
2428 \r
2429                         return node;\r
2430                 },\r
2431 \r
2432                 insert : function(node, ref_node, before) {\r
2433                         var parent;\r
2434 \r
2435                         if (node.parent)\r
2436                                 node.remove();\r
2437 \r
2438                         parent = ref_node.parent || this;\r
2439 \r
2440                         if (before) {\r
2441                                 if (ref_node === parent.firstChild)\r
2442                                         parent.firstChild = node;\r
2443                                 else\r
2444                                         ref_node.prev.next = node;\r
2445 \r
2446                                 node.prev = ref_node.prev;\r
2447                                 node.next = ref_node;\r
2448                                 ref_node.prev = node;\r
2449                         } else {\r
2450                                 if (ref_node === parent.lastChild)\r
2451                                         parent.lastChild = node;\r
2452                                 else\r
2453                                         ref_node.next.prev = node;\r
2454 \r
2455                                 node.next = ref_node.next;\r
2456                                 node.prev = ref_node;\r
2457                                 ref_node.next = node;\r
2458                         }\r
2459 \r
2460                         node.parent = parent;\r
2461 \r
2462                         return node;\r
2463                 },\r
2464 \r
2465                 getAll : function(name) {\r
2466                         var self = this, node, collection = [];\r
2467 \r
2468                         for (node = self.firstChild; node; node = walk(node, self)) {\r
2469                                 if (node.name === name)\r
2470                                         collection.push(node);\r
2471                         }\r
2472 \r
2473                         return collection;\r
2474                 },\r
2475 \r
2476                 empty : function() {\r
2477                         var self = this, nodes, i, node;\r
2478 \r
2479                         // Remove all children\r
2480                         if (self.firstChild) {\r
2481                                 nodes = [];\r
2482 \r
2483                                 // Collect the children\r
2484                                 for (node = self.firstChild; node; node = walk(node, self))\r
2485                                         nodes.push(node);\r
2486 \r
2487                                 // Remove the children\r
2488                                 i = nodes.length;\r
2489                                 while (i--) {\r
2490                                         node = nodes[i];\r
2491                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;\r
2492                                 }\r
2493                         }\r
2494 \r
2495                         self.firstChild = self.lastChild = null;\r
2496 \r
2497                         return self;\r
2498                 },\r
2499 \r
2500                 isEmpty : function(elements) {\r
2501                         var self = this, node = self.firstChild, i, name;\r
2502 \r
2503                         if (node) {\r
2504                                 do {\r
2505                                         if (node.type === 1) {\r
2506                                                 // Ignore bogus elements\r
2507                                                 if (node.attributes.map['data-mce-bogus'])\r
2508                                                         continue;\r
2509 \r
2510                                                 // Keep empty elements like <img />\r
2511                                                 if (elements[node.name])\r
2512                                                         return false;\r
2513 \r
2514                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>\r
2515                                                 i = node.attributes.length;\r
2516                                                 while (i--) {\r
2517                                                         name = node.attributes[i].name;\r
2518                                                         if (name === "name" || name.indexOf('data-') === 0)\r
2519                                                                 return false;\r
2520                                                 }\r
2521                                         }\r
2522 \r
2523                                         // Keep non whitespace text nodes\r
2524                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))\r
2525                                                 return false;\r
2526                                 } while (node = walk(node, self));\r
2527                         }\r
2528 \r
2529                         return true;\r
2530                 },\r
2531 \r
2532                 walk : function(prev) {\r
2533                         return walk(this, null, prev);\r
2534                 }\r
2535         });\r
2536 \r
2537         tinymce.extend(Node, {\r
2538                 create : function(name, attrs) {\r
2539                         var node, attrName;\r
2540 \r
2541                         // Create node\r
2542                         node = new Node(name, typeLookup[name] || 1);\r
2543 \r
2544                         // Add attributes if needed\r
2545                         if (attrs) {\r
2546                                 for (attrName in attrs)\r
2547                                         node.attr(attrName, attrs[attrName]);\r
2548                         }\r
2549 \r
2550                         return node;\r
2551                 }\r
2552         });\r
2553 \r
2554         tinymce.html.Node = Node;\r
2555 })(tinymce);\r
2556 \r
2557 (function(tinymce) {\r
2558         var Node = tinymce.html.Node;\r
2559 \r
2560         tinymce.html.DomParser = function(settings, schema) {\r
2561                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};\r
2562 \r
2563                 settings = settings || {};\r
2564                 settings.validate = "validate" in settings ? settings.validate : true;\r
2565                 settings.root_name = settings.root_name || 'body';\r
2566                 self.schema = schema = schema || new tinymce.html.Schema();\r
2567 \r
2568                 function fixInvalidChildren(nodes) {\r
2569                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,\r
2570                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;\r
2571 \r
2572                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');\r
2573                         nonEmptyElements = schema.getNonEmptyElements();\r
2574 \r
2575                         for (ni = 0; ni < nodes.length; ni++) {\r
2576                                 node = nodes[ni];\r
2577 \r
2578                                 // Already removed\r
2579                                 if (!node.parent)\r
2580                                         continue;\r
2581 \r
2582                                 // Get list of all parent nodes until we find a valid parent to stick the child into\r
2583                                 parents = [node];\r
2584                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)\r
2585                                         parents.push(parent);\r
2586 \r
2587                                 // Found a suitable parent\r
2588                                 if (parent && parents.length > 1) {\r
2589                                         // Reverse the array since it makes looping easier\r
2590                                         parents.reverse();\r
2591 \r
2592                                         // Clone the related parent and insert that after the moved node\r
2593                                         newParent = currentNode = self.filterNode(parents[0].clone());\r
2594 \r
2595                                         // Start cloning and moving children on the left side of the target node\r
2596                                         for (i = 0; i < parents.length - 1; i++) {\r
2597                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {\r
2598                                                         tempNode = self.filterNode(parents[i].clone());\r
2599                                                         currentNode.append(tempNode);\r
2600                                                 } else\r
2601                                                         tempNode = currentNode;\r
2602 \r
2603                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {\r
2604                                                         nextNode = childNode.next;\r
2605                                                         tempNode.append(childNode);\r
2606                                                         childNode = nextNode;\r
2607                                                 }\r
2608 \r
2609                                                 currentNode = tempNode;\r
2610                                         }\r
2611 \r
2612                                         if (!newParent.isEmpty(nonEmptyElements)) {\r
2613                                                 parent.insert(newParent, parents[0], true);\r
2614                                                 parent.insert(node, newParent);\r
2615                                         } else {\r
2616                                                 parent.insert(node, parents[0], true);\r
2617                                         }\r
2618 \r
2619                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>\r
2620                                         parent = parents[0];\r
2621                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {\r
2622                                                 parent.empty().remove();\r
2623                                         }\r
2624                                 } else if (node.parent) {\r
2625                                         // If it's an LI try to find a UL/OL for it or wrap it\r
2626                                         if (node.name === 'li') {\r
2627                                                 sibling = node.prev;\r
2628                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
2629                                                         sibling.append(node);\r
2630                                                         continue;\r
2631                                                 }\r
2632 \r
2633                                                 sibling = node.next;\r
2634                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
2635                                                         sibling.insert(node, sibling.firstChild, true);\r
2636                                                         continue;\r
2637                                                 }\r
2638 \r
2639                                                 node.wrap(self.filterNode(new Node('ul', 1)));\r
2640                                                 continue;\r
2641                                         }\r
2642 \r
2643                                         // Try wrapping the element in a DIV\r
2644                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {\r
2645                                                 node.wrap(self.filterNode(new Node('div', 1)));\r
2646                                         } else {\r
2647                                                 // We failed wrapping it, then remove or unwrap it\r
2648                                                 if (node.name === 'style' || node.name === 'script')\r
2649                                                         node.empty().remove();\r
2650                                                 else\r
2651                                                         node.unwrap();\r
2652                                         }\r
2653                                 }\r
2654                         }\r
2655                 };\r
2656 \r
2657                 self.filterNode = function(node) {\r
2658                         var i, name, list;\r
2659 \r
2660                         // Run element filters\r
2661                         if (name in nodeFilters) {\r
2662                                 list = matchedNodes[name];\r
2663 \r
2664                                 if (list)\r
2665                                         list.push(node);\r
2666                                 else\r
2667                                         matchedNodes[name] = [node];\r
2668                         }\r
2669 \r
2670                         // Run attribute filters\r
2671                         i = attributeFilters.length;\r
2672                         while (i--) {\r
2673                                 name = attributeFilters[i].name;\r
2674 \r
2675                                 if (name in node.attributes.map) {\r
2676                                         list = matchedAttributes[name];\r
2677 \r
2678                                         if (list)\r
2679                                                 list.push(node);\r
2680                                         else\r
2681                                                 matchedAttributes[name] = [node];\r
2682                                 }\r
2683                         }\r
2684 \r
2685                         return node;\r
2686                 };\r
2687 \r
2688                 self.addNodeFilter = function(name, callback) {\r
2689                         tinymce.each(tinymce.explode(name), function(name) {\r
2690                                 var list = nodeFilters[name];\r
2691 \r
2692                                 if (!list)\r
2693                                         nodeFilters[name] = list = [];\r
2694 \r
2695                                 list.push(callback);\r
2696                         });\r
2697                 };\r
2698 \r
2699                 self.addAttributeFilter = function(name, callback) {\r
2700                         tinymce.each(tinymce.explode(name), function(name) {\r
2701                                 var i;\r
2702 \r
2703                                 for (i = 0; i < attributeFilters.length; i++) {\r
2704                                         if (attributeFilters[i].name === name) {\r
2705                                                 attributeFilters[i].callbacks.push(callback);\r
2706                                                 return;\r
2707                                         }\r
2708                                 }\r
2709 \r
2710                                 attributeFilters.push({name: name, callbacks: [callback]});\r
2711                         });\r
2712                 };\r
2713 \r
2714                 self.parse = function(html, args) {\r
2715                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,\r
2716                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [],\r
2717                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;\r
2718 \r
2719                         args = args || {};\r
2720                         matchedNodes = {};\r
2721                         matchedAttributes = {};\r
2722                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());\r
2723                         nonEmptyElements = schema.getNonEmptyElements();\r
2724                         children = schema.children;\r
2725                         validate = settings.validate;\r
2726                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;\r
2727 \r
2728                         whiteSpaceElements = schema.getWhiteSpaceElements();\r
2729                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;\r
2730                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;\r
2731                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;\r
2732 \r
2733                         function addRootBlocks() {\r
2734                                 var node = rootNode.firstChild, next, rootBlockNode;\r
2735 \r
2736                                 while (node) {\r
2737                                         next = node.next;\r
2738 \r
2739                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {\r
2740                                                 if (!rootBlockNode) {\r
2741                                                         // Create a new root block element\r
2742                                                         rootBlockNode = createNode(rootBlockName, 1);\r
2743                                                         rootNode.insert(rootBlockNode, node);\r
2744                                                         rootBlockNode.append(node);\r
2745                                                 } else\r
2746                                                         rootBlockNode.append(node);\r
2747                                         } else {\r
2748                                                 rootBlockNode = null;\r
2749                                         }\r
2750 \r
2751                                         node = next;\r
2752                                 };\r
2753                         };\r
2754 \r
2755                         function createNode(name, type) {\r
2756                                 var node = new Node(name, type), list;\r
2757 \r
2758                                 if (name in nodeFilters) {\r
2759                                         list = matchedNodes[name];\r
2760 \r
2761                                         if (list)\r
2762                                                 list.push(node);\r
2763                                         else\r
2764                                                 matchedNodes[name] = [node];\r
2765                                 }\r
2766 \r
2767                                 return node;\r
2768                         };\r
2769 \r
2770                         function removeWhitespaceBefore(node) {\r
2771                                 var textNode, textVal, sibling;\r
2772 \r
2773                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {\r
2774                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');\r
2775 \r
2776                                         if (textVal.length > 0) {\r
2777                                                 textNode.value = textVal;\r
2778                                                 textNode = textNode.prev;\r
2779                                         } else {\r
2780                                                 sibling = textNode.prev;\r
2781                                                 textNode.remove();\r
2782                                                 textNode = sibling;\r
2783                                         }\r
2784                                 }\r
2785                         };\r
2786 \r
2787                         parser = new tinymce.html.SaxParser({\r
2788                                 validate : validate,\r
2789                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results\r
2790 \r
2791                                 cdata: function(text) {\r
2792                                         node.append(createNode('#cdata', 4)).value = text;\r
2793                                 },\r
2794 \r
2795                                 text: function(text, raw) {\r
2796                                         var textNode;\r
2797 \r
2798                                         // Trim all redundant whitespace on non white space elements\r
2799                                         if (!whiteSpaceElements[node.name]) {\r
2800                                                 text = text.replace(allWhiteSpaceRegExp, ' ');\r
2801 \r
2802                                                 if (node.lastChild && blockElements[node.lastChild.name])\r
2803                                                         text = text.replace(startWhiteSpaceRegExp, '');\r
2804                                         }\r
2805 \r
2806                                         // Do we need to create the node\r
2807                                         if (text.length !== 0) {\r
2808                                                 textNode = createNode('#text', 3);\r
2809                                                 textNode.raw = !!raw;\r
2810                                                 node.append(textNode).value = text;\r
2811                                         }\r
2812                                 },\r
2813 \r
2814                                 comment: function(text) {\r
2815                                         node.append(createNode('#comment', 8)).value = text;\r
2816                                 },\r
2817 \r
2818                                 pi: function(name, text) {\r
2819                                         node.append(createNode(name, 7)).value = text;\r
2820                                         removeWhitespaceBefore(node);\r
2821                                 },\r
2822 \r
2823                                 doctype: function(text) {\r
2824                                         var newNode;\r
2825                 \r
2826                                         newNode = node.append(createNode('#doctype', 10));\r
2827                                         newNode.value = text;\r
2828                                         removeWhitespaceBefore(node);\r
2829                                 },\r
2830 \r
2831                                 start: function(name, attrs, empty) {\r
2832                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;\r
2833 \r
2834                                         elementRule = validate ? schema.getElementRule(name) : {};\r
2835                                         if (elementRule) {\r
2836                                                 newNode = createNode(elementRule.outputName || name, 1);\r
2837                                                 newNode.attributes = attrs;\r
2838                                                 newNode.shortEnded = empty;\r
2839 \r
2840                                                 node.append(newNode);\r
2841 \r
2842                                                 // Check if node is valid child of the parent node is the child is\r
2843                                                 // unknown we don't collect it since it's probably a custom element\r
2844                                                 parent = children[node.name];\r
2845                                                 if (parent && children[newNode.name] && !parent[newNode.name])\r
2846                                                         invalidChildren.push(newNode);\r
2847 \r
2848                                                 attrFiltersLen = attributeFilters.length;\r
2849                                                 while (attrFiltersLen--) {\r
2850                                                         attrName = attributeFilters[attrFiltersLen].name;\r
2851 \r
2852                                                         if (attrName in attrs.map) {\r
2853                                                                 list = matchedAttributes[attrName];\r
2854 \r
2855                                                                 if (list)\r
2856                                                                         list.push(newNode);\r
2857                                                                 else\r
2858                                                                         matchedAttributes[attrName] = [newNode];\r
2859                                                         }\r
2860                                                 }\r
2861 \r
2862                                                 // Trim whitespace before block\r
2863                                                 if (blockElements[name])\r
2864                                                         removeWhitespaceBefore(newNode);\r
2865 \r
2866                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />\r
2867                                                 if (!empty)\r
2868                                                         node = newNode;\r
2869                                         }\r
2870                                 },\r
2871 \r
2872                                 end: function(name) {\r
2873                                         var textNode, elementRule, text, sibling, tempNode;\r
2874 \r
2875                                         elementRule = validate ? schema.getElementRule(name) : {};\r
2876                                         if (elementRule) {\r
2877                                                 if (blockElements[name]) {\r
2878                                                         if (!whiteSpaceElements[node.name]) {\r
2879                                                                 // Trim whitespace at beginning of block\r
2880                                                                 for (textNode = node.firstChild; textNode && textNode.type === 3; ) {\r
2881                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
2882 \r
2883                                                                         if (text.length > 0) {\r
2884                                                                                 textNode.value = text;\r
2885                                                                                 textNode = textNode.next;\r
2886                                                                         } else {\r
2887                                                                                 sibling = textNode.next;\r
2888                                                                                 textNode.remove();\r
2889                                                                                 textNode = sibling;\r
2890                                                                         }\r
2891                                                                 }\r
2892 \r
2893                                                                 // Trim whitespace at end of block\r
2894                                                                 for (textNode = node.lastChild; textNode && textNode.type === 3; ) {\r
2895                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');\r
2896 \r
2897                                                                         if (text.length > 0) {\r
2898                                                                                 textNode.value = text;\r
2899                                                                                 textNode = textNode.prev;\r
2900                                                                         } else {\r
2901                                                                                 sibling = textNode.prev;\r
2902                                                                                 textNode.remove();\r
2903                                                                                 textNode = sibling;\r
2904                                                                         }\r
2905                                                                 }\r
2906                                                         }\r
2907 \r
2908                                                         // Trim start white space\r
2909                                                         textNode = node.prev;\r
2910                                                         if (textNode && textNode.type === 3) {\r
2911                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
2912 \r
2913                                                                 if (text.length > 0)\r
2914                                                                         textNode.value = text;\r
2915                                                                 else\r
2916                                                                         textNode.remove();\r
2917                                                         }\r
2918                                                 }\r
2919 \r
2920                                                 // Handle empty nodes\r
2921                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {\r
2922                                                         if (node.isEmpty(nonEmptyElements)) {\r
2923                                                                 if (elementRule.paddEmpty)\r
2924                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';\r
2925                                                                 else {\r
2926                                                                         // Leave nodes that have a name like <a name="name">\r
2927                                                                         if (!node.attributes.map.name) {\r
2928                                                                                 tempNode = node.parent;\r
2929                                                                                 node.empty().remove();\r
2930                                                                                 node = tempNode;\r
2931                                                                                 return;\r
2932                                                                         }\r
2933                                                                 }\r
2934                                                         }\r
2935                                                 }\r
2936 \r
2937                                                 node = node.parent;\r
2938                                         }\r
2939                                 }\r
2940                         }, schema);\r
2941 \r
2942                         rootNode = node = new Node(args.context || settings.root_name, 11);\r
2943 \r
2944                         parser.parse(html);\r
2945 \r
2946                         // Fix invalid children or report invalid children in a contextual parsing\r
2947                         if (validate && invalidChildren.length) {\r
2948                                 if (!args.context)\r
2949                                         fixInvalidChildren(invalidChildren);\r
2950                                 else\r
2951                                         args.invalid = true;\r
2952                         }\r
2953 \r
2954                         // Wrap nodes in the root into block elements if the root is body\r
2955                         if (rootBlockName && rootNode.name == 'body')\r
2956                                 addRootBlocks();\r
2957 \r
2958                         // Run filters only when the contents is valid\r
2959                         if (!args.invalid) {\r
2960                                 // Run node filters\r
2961                                 for (name in matchedNodes) {\r
2962                                         list = nodeFilters[name];\r
2963                                         nodes = matchedNodes[name];\r
2964 \r
2965                                         // Remove already removed children\r
2966                                         fi = nodes.length;\r
2967                                         while (fi--) {\r
2968                                                 if (!nodes[fi].parent)\r
2969                                                         nodes.splice(fi, 1);\r
2970                                         }\r
2971 \r
2972                                         for (i = 0, l = list.length; i < l; i++)\r
2973                                                 list[i](nodes, name, args);\r
2974                                 }\r
2975 \r
2976                                 // Run attribute filters\r
2977                                 for (i = 0, l = attributeFilters.length; i < l; i++) {\r
2978                                         list = attributeFilters[i];\r
2979 \r
2980                                         if (list.name in matchedAttributes) {\r
2981                                                 nodes = matchedAttributes[list.name];\r
2982 \r
2983                                                 // Remove already removed children\r
2984                                                 fi = nodes.length;\r
2985                                                 while (fi--) {\r
2986                                                         if (!nodes[fi].parent)\r
2987                                                                 nodes.splice(fi, 1);\r
2988                                                 }\r
2989 \r
2990                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)\r
2991                                                         list.callbacks[fi](nodes, list.name, args);\r
2992                                         }\r
2993                                 }\r
2994                         }\r
2995 \r
2996                         return rootNode;\r
2997                 };\r
2998 \r
2999                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to\r
3000                 // make it possible to place the caret inside empty blocks. This logic tries to remove\r
3001                 // these elements and keep br elements that where intended to be there intact\r
3002                 if (settings.remove_trailing_brs) {\r
3003                         self.addNodeFilter('br', function(nodes, name) {\r
3004                                 var i, l = nodes.length, node, blockElements = schema.getBlockElements(),\r
3005                                         nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;\r
3006 \r
3007                                 // Remove brs from body element as well\r
3008                                 blockElements.body = 1;\r
3009 \r
3010                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>\r
3011                                 for (i = 0; i < l; i++) {\r
3012                                         node = nodes[i];\r
3013                                         parent = node.parent;\r
3014 \r
3015                                         if (blockElements[node.parent.name] && node === parent.lastChild) {\r
3016                                                 // Loop all nodes to the right of the current node and check for other BR elements\r
3017                                                 // excluding bookmarks since they are invisible\r
3018                                                 prev = node.prev;\r
3019                                                 while (prev) {\r
3020                                                         prevName = prev.name;\r
3021 \r
3022                                                         // Ignore bookmarks\r
3023                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {\r
3024                                                                 // Found a non BR element\r
3025                                                                 if (prevName !== "br")\r
3026                                                                         break;\r
3027         \r
3028                                                                 // Found another br it's a <br><br> structure then don't remove anything\r
3029                                                                 if (prevName === 'br') {\r
3030                                                                         node = null;\r
3031                                                                         break;\r
3032                                                                 }\r
3033                                                         }\r
3034 \r
3035                                                         prev = prev.prev;\r
3036                                                 }\r
3037 \r
3038                                                 if (node) {\r
3039                                                         node.remove();\r
3040 \r
3041                                                         // Is the parent to be considered empty after we removed the BR\r
3042                                                         if (parent.isEmpty(nonEmptyElements)) {\r
3043                                                                 elementRule = schema.getElementRule(parent.name);\r
3044 \r
3045                                                                 // Remove or padd the element depending on schema rule\r
3046                                                                 if (elementRule.removeEmpty)\r
3047                                                                         parent.remove();\r
3048                                                                 else if (elementRule.paddEmpty) \r
3049                                                                         parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';\r
3050                                                         }\r
3051                                                 }\r
3052                                         }\r
3053                                 }\r
3054                         });\r
3055                 }\r
3056         }\r
3057 })(tinymce);\r
3058 \r
3059 tinymce.html.Writer = function(settings) {\r
3060         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;\r
3061 \r
3062         settings = settings || {};\r
3063         indent = settings.indent;\r
3064         indentBefore = tinymce.makeMap(settings.indent_before || '');\r
3065         indentAfter = tinymce.makeMap(settings.indent_after || '');\r
3066         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);\r
3067         htmlOutput = settings.element_format == "html";\r
3068 \r
3069         return {\r
3070                 start: function(name, attrs, empty) {\r
3071                         var i, l, attr, value;\r
3072 \r
3073                         if (indent && indentBefore[name] && html.length > 0) {\r
3074                                 value = html[html.length - 1];\r
3075 \r
3076                                 if (value.length > 0 && value !== '\n')\r
3077                                         html.push('\n');\r
3078                         }\r
3079 \r
3080                         html.push('<', name);\r
3081 \r
3082                         if (attrs) {\r
3083                                 for (i = 0, l = attrs.length; i < l; i++) {\r
3084                                         attr = attrs[i];\r
3085                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');\r
3086                                 }\r
3087                         }\r
3088 \r
3089                         if (!empty || htmlOutput)\r
3090                                 html[html.length] = '>';\r
3091                         else\r
3092                                 html[html.length] = ' />';\r
3093 \r
3094                         if (empty && indent && indentAfter[name] && html.length > 0) {\r
3095                                 value = html[html.length - 1];\r
3096 \r
3097                                 if (value.length > 0 && value !== '\n')\r
3098                                         html.push('\n');\r
3099                         }\r
3100                 },\r
3101 \r
3102                 end: function(name) {\r
3103                         var value;\r
3104 \r
3105                         /*if (indent && indentBefore[name] && html.length > 0) {\r
3106                                 value = html[html.length - 1];\r
3107 \r
3108                                 if (value.length > 0 && value !== '\n')\r
3109                                         html.push('\n');\r
3110                         }*/\r
3111 \r
3112                         html.push('</', name, '>');\r
3113 \r
3114                         if (indent && indentAfter[name] && html.length > 0) {\r
3115                                 value = html[html.length - 1];\r
3116 \r
3117                                 if (value.length > 0 && value !== '\n')\r
3118                                         html.push('\n');\r
3119                         }\r
3120                 },\r
3121 \r
3122                 text: function(text, raw) {\r
3123                         if (text.length > 0)\r
3124                                 html[html.length] = raw ? text : encode(text);\r
3125                 },\r
3126 \r
3127                 cdata: function(text) {\r
3128                         html.push('<![CDATA[', text, ']]>');\r
3129                 },\r
3130 \r
3131                 comment: function(text) {\r
3132                         html.push('<!--', text, '-->');\r
3133                 },\r
3134 \r
3135                 pi: function(name, text) {\r
3136                         if (text)\r
3137                                 html.push('<?', name, ' ', text, '?>');\r
3138                         else\r
3139                                 html.push('<?', name, '?>');\r
3140 \r
3141                         if (indent)\r
3142                                 html.push('\n');\r
3143                 },\r
3144 \r
3145                 doctype: function(text) {\r
3146                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');\r
3147                 },\r
3148 \r
3149                 reset: function() {\r
3150                         html.length = 0;\r
3151                 },\r
3152 \r
3153                 getContent: function() {\r
3154                         return html.join('').replace(/\n$/, '');\r
3155                 }\r
3156         };\r
3157 };\r
3158 \r
3159 (function(tinymce) {\r
3160         tinymce.html.Serializer = function(settings, schema) {\r
3161                 var self = this, writer = new tinymce.html.Writer(settings);\r
3162 \r
3163                 settings = settings || {};\r
3164                 settings.validate = "validate" in settings ? settings.validate : true;\r
3165 \r
3166                 self.schema = schema = schema || new tinymce.html.Schema();\r
3167                 self.writer = writer;\r
3168 \r
3169                 self.serialize = function(node) {\r
3170                         var handlers, validate;\r
3171 \r
3172                         validate = settings.validate;\r
3173 \r
3174                         handlers = {\r
3175                                 // #text\r
3176                                 3: function(node, raw) {\r
3177                                         writer.text(node.value, node.raw);\r
3178                                 },\r
3179 \r
3180                                 // #comment\r
3181                                 8: function(node) {\r
3182                                         writer.comment(node.value);\r
3183                                 },\r
3184 \r
3185                                 // Processing instruction\r
3186                                 7: function(node) {\r
3187                                         writer.pi(node.name, node.value);\r
3188                                 },\r
3189 \r
3190                                 // Doctype\r
3191                                 10: function(node) {\r
3192                                         writer.doctype(node.value);\r
3193                                 },\r
3194 \r
3195                                 // CDATA\r
3196                                 4: function(node) {\r
3197                                         writer.cdata(node.value);\r
3198                                 },\r
3199 \r
3200                                 // Document fragment\r
3201                                 11: function(node) {\r
3202                                         if ((node = node.firstChild)) {\r
3203                                                 do {\r
3204                                                         walk(node);\r
3205                                                 } while (node = node.next);\r
3206                                         }\r
3207                                 }\r
3208                         };\r
3209 \r
3210                         writer.reset();\r
3211 \r
3212                         function walk(node) {\r
3213                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;\r
3214 \r
3215                                 if (!handler) {\r
3216                                         name = node.name;\r
3217                                         isEmpty = node.shortEnded;\r
3218                                         attrs = node.attributes;\r
3219 \r
3220                                         // Sort attributes\r
3221                                         if (validate && attrs && attrs.length > 1) {\r
3222                                                 sortedAttrs = [];\r
3223                                                 sortedAttrs.map = {};\r
3224 \r
3225                                                 elementRule = schema.getElementRule(node.name);\r
3226                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {\r
3227                                                         attrName = elementRule.attributesOrder[i];\r
3228 \r
3229                                                         if (attrName in attrs.map) {\r
3230                                                                 attrValue = attrs.map[attrName];\r
3231                                                                 sortedAttrs.map[attrName] = attrValue;\r
3232                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
3233                                                         }\r
3234                                                 }\r
3235 \r
3236                                                 for (i = 0, l = attrs.length; i < l; i++) {\r
3237                                                         attrName = attrs[i].name;\r
3238 \r
3239                                                         if (!(attrName in sortedAttrs.map)) {\r
3240                                                                 attrValue = attrs.map[attrName];\r
3241                                                                 sortedAttrs.map[attrName] = attrValue;\r
3242                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
3243                                                         }\r
3244                                                 }\r
3245 \r
3246                                                 attrs = sortedAttrs;\r
3247                                         }\r
3248 \r
3249                                         writer.start(node.name, attrs, isEmpty);\r
3250 \r
3251                                         if (!isEmpty) {\r
3252                                                 if ((node = node.firstChild)) {\r
3253                                                         do {\r
3254                                                                 walk(node);\r
3255                                                         } while (node = node.next);\r
3256                                                 }\r
3257 \r
3258                                                 writer.end(name);\r
3259                                         }\r
3260                                 } else\r
3261                                         handler(node);\r
3262                         }\r
3263 \r
3264                         // Serialize element and treat all non elements as fragments\r
3265                         if (node.type == 1 && !settings.inner)\r
3266                                 walk(node);\r
3267                         else\r
3268                                 handlers[11](node);\r
3269 \r
3270                         return writer.getContent();\r
3271                 };\r
3272         }\r
3273 })(tinymce);\r
3274 \r
3275 (function(tinymce) {\r
3276         // Shorten names\r
3277         var each = tinymce.each,\r
3278                 is = tinymce.is,\r
3279                 isWebKit = tinymce.isWebKit,\r
3280                 isIE = tinymce.isIE,\r
3281                 Entities = tinymce.html.Entities,\r
3282                 simpleSelectorRe = /^([a-z0-9],?)+$/i,\r
3283                 blockElementsMap = tinymce.html.Schema.blockElementsMap,\r
3284                 whiteSpaceRegExp = /^[ \t\r\n]*$/;\r
3285 \r
3286         tinymce.create('tinymce.dom.DOMUtils', {\r
3287                 doc : null,\r
3288                 root : null,\r
3289                 files : null,\r
3290                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,\r
3291                 props : {\r
3292                         "for" : "htmlFor",\r
3293                         "class" : "className",\r
3294                         className : "className",\r
3295                         checked : "checked",\r
3296                         disabled : "disabled",\r
3297                         maxlength : "maxLength",\r
3298                         readonly : "readOnly",\r
3299                         selected : "selected",\r
3300                         value : "value",\r
3301                         id : "id",\r
3302                         name : "name",\r
3303                         type : "type"\r
3304                 },\r
3305 \r
3306                 DOMUtils : function(d, s) {\r
3307                         var t = this, globalStyle, name;\r
3308 \r
3309                         t.doc = d;\r
3310                         t.win = window;\r
3311                         t.files = {};\r
3312                         t.cssFlicker = false;\r
3313                         t.counter = 0;\r
3314                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;\r
3315                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;\r
3316                         t.hasOuterHTML = "outerHTML" in d.createElement("a");\r
3317 \r
3318                         t.settings = s = tinymce.extend({\r
3319                                 keep_values : false,\r
3320                                 hex_colors : 1\r
3321                         }, s);\r
3322                         \r
3323                         t.schema = s.schema;\r
3324                         t.styles = new tinymce.html.Styles({\r
3325                                 url_converter : s.url_converter,\r
3326                                 url_converter_scope : s.url_converter_scope\r
3327                         }, s.schema);\r
3328 \r
3329                         // Fix IE6SP2 flicker and check it failed for pre SP2\r
3330                         if (tinymce.isIE6) {\r
3331                                 try {\r
3332                                         d.execCommand('BackgroundImageCache', false, true);\r
3333                                 } catch (e) {\r
3334                                         t.cssFlicker = true;\r
3335                                 }\r
3336                         }\r
3337 \r
3338                         if (isIE && s.schema) {\r
3339                                 // Add missing HTML 4/5 elements to IE\r
3340                                 ('abbr article aside audio canvas ' +\r
3341                                 'details figcaption figure footer ' +\r
3342                                 'header hgroup mark menu meter nav ' +\r
3343                                 'output progress section summary ' +\r
3344                                 'time video').replace(/\w+/g, function(name) {\r
3345                                         d.createElement(name);\r
3346                                 });\r
3347 \r
3348                                 // Create all custom elements\r
3349                                 for (name in s.schema.getCustomElements()) {\r
3350                                         d.createElement(name);\r
3351                                 }\r
3352                         }\r
3353 \r
3354                         tinymce.addUnload(t.destroy, t);\r
3355                 },\r
3356 \r
3357                 getRoot : function() {\r
3358                         var t = this, s = t.settings;\r
3359 \r
3360                         return (s && t.get(s.root_element)) || t.doc.body;\r
3361                 },\r
3362 \r
3363                 getViewPort : function(w) {\r
3364                         var d, b;\r
3365 \r
3366                         w = !w ? this.win : w;\r
3367                         d = w.document;\r
3368                         b = this.boxModel ? d.documentElement : d.body;\r
3369 \r
3370                         // Returns viewport size excluding scrollbars\r
3371                         return {\r
3372                                 x : w.pageXOffset || b.scrollLeft,\r
3373                                 y : w.pageYOffset || b.scrollTop,\r
3374                                 w : w.innerWidth || b.clientWidth,\r
3375                                 h : w.innerHeight || b.clientHeight\r
3376                         };\r
3377                 },\r
3378 \r
3379                 getRect : function(e) {\r
3380                         var p, t = this, sr;\r
3381 \r
3382                         e = t.get(e);\r
3383                         p = t.getPos(e);\r
3384                         sr = t.getSize(e);\r
3385 \r
3386                         return {\r
3387                                 x : p.x,\r
3388                                 y : p.y,\r
3389                                 w : sr.w,\r
3390                                 h : sr.h\r
3391                         };\r
3392                 },\r
3393 \r
3394                 getSize : function(e) {\r
3395                         var t = this, w, h;\r
3396 \r
3397                         e = t.get(e);\r
3398                         w = t.getStyle(e, 'width');\r
3399                         h = t.getStyle(e, 'height');\r
3400 \r
3401                         // Non pixel value, then force offset/clientWidth\r
3402                         if (w.indexOf('px') === -1)\r
3403                                 w = 0;\r
3404 \r
3405                         // Non pixel value, then force offset/clientWidth\r
3406                         if (h.indexOf('px') === -1)\r
3407                                 h = 0;\r
3408 \r
3409                         return {\r
3410                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,\r
3411                                 h : parseInt(h) || e.offsetHeight || e.clientHeight\r
3412                         };\r
3413                 },\r
3414 \r
3415                 getParent : function(n, f, r) {\r
3416                         return this.getParents(n, f, r, false);\r
3417                 },\r
3418 \r
3419                 getParents : function(n, f, r, c) {\r
3420                         var t = this, na, se = t.settings, o = [];\r
3421 \r
3422                         n = t.get(n);\r
3423                         c = c === undefined;\r
3424 \r
3425                         if (se.strict_root)\r
3426                                 r = r || t.getRoot();\r
3427 \r
3428                         // Wrap node name as func\r
3429                         if (is(f, 'string')) {\r
3430                                 na = f;\r
3431 \r
3432                                 if (f === '*') {\r
3433                                         f = function(n) {return n.nodeType == 1;};\r
3434                                 } else {\r
3435                                         f = function(n) {\r
3436                                                 return t.is(n, na);\r
3437                                         };\r
3438                                 }\r
3439                         }\r
3440 \r
3441                         while (n) {\r
3442                                 if (n == r || !n.nodeType || n.nodeType === 9)\r
3443                                         break;\r
3444 \r
3445                                 if (!f || f(n)) {\r
3446                                         if (c)\r
3447                                                 o.push(n);\r
3448                                         else\r
3449                                                 return n;\r
3450                                 }\r
3451 \r
3452                                 n = n.parentNode;\r
3453                         }\r
3454 \r
3455                         return c ? o : null;\r
3456                 },\r
3457 \r
3458                 get : function(e) {\r
3459                         var n;\r
3460 \r
3461                         if (e && this.doc && typeof(e) == 'string') {\r
3462                                 n = e;\r
3463                                 e = this.doc.getElementById(e);\r
3464 \r
3465                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick\r
3466                                 if (e && e.id !== n)\r
3467                                         return this.doc.getElementsByName(n)[1];\r
3468                         }\r
3469 \r
3470                         return e;\r
3471                 },\r
3472 \r
3473                 getNext : function(node, selector) {\r
3474                         return this._findSib(node, selector, 'nextSibling');\r
3475                 },\r
3476 \r
3477                 getPrev : function(node, selector) {\r
3478                         return this._findSib(node, selector, 'previousSibling');\r
3479                 },\r
3480 \r
3481 \r
3482                 select : function(pa, s) {\r
3483                         var t = this;\r
3484 \r
3485                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);\r
3486                 },\r
3487 \r
3488                 is : function(n, selector) {\r
3489                         var i;\r
3490 \r
3491                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance\r
3492                         if (n.length === undefined) {\r
3493                                 // Simple all selector\r
3494                                 if (selector === '*')\r
3495                                         return n.nodeType == 1;\r
3496 \r
3497                                 // Simple selector just elements\r
3498                                 if (simpleSelectorRe.test(selector)) {\r
3499                                         selector = selector.toLowerCase().split(/,/);\r
3500                                         n = n.nodeName.toLowerCase();\r
3501 \r
3502                                         for (i = selector.length - 1; i >= 0; i--) {\r
3503                                                 if (selector[i] == n)\r
3504                                                         return true;\r
3505                                         }\r
3506 \r
3507                                         return false;\r
3508                                 }\r
3509                         }\r
3510 \r
3511                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;\r
3512                 },\r
3513 \r
3514 \r
3515                 add : function(p, n, a, h, c) {\r
3516                         var t = this;\r
3517 \r
3518                         return this.run(p, function(p) {\r
3519                                 var e, k;\r
3520 \r
3521                                 e = is(n, 'string') ? t.doc.createElement(n) : n;\r
3522                                 t.setAttribs(e, a);\r
3523 \r
3524                                 if (h) {\r
3525                                         if (h.nodeType)\r
3526                                                 e.appendChild(h);\r
3527                                         else\r
3528                                                 t.setHTML(e, h);\r
3529                                 }\r
3530 \r
3531                                 return !c ? p.appendChild(e) : e;\r
3532                         });\r
3533                 },\r
3534 \r
3535                 create : function(n, a, h) {\r
3536                         return this.add(this.doc.createElement(n), n, a, h, 1);\r
3537                 },\r
3538 \r
3539                 createHTML : function(n, a, h) {\r
3540                         var o = '', t = this, k;\r
3541 \r
3542                         o += '<' + n;\r
3543 \r
3544                         for (k in a) {\r
3545                                 if (a.hasOwnProperty(k))\r
3546                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';\r
3547                         }\r
3548 \r
3549                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime\r
3550                         if (typeof(h) != "undefined")\r
3551                                 return o + '>' + h + '</' + n + '>';\r
3552 \r
3553                         return o + ' />';\r
3554                 },\r
3555 \r
3556                 remove : function(node, keep_children) {\r
3557                         return this.run(node, function(node) {\r
3558                                 var child, parent = node.parentNode;\r
3559 \r
3560                                 if (!parent)\r
3561                                         return null;\r
3562 \r
3563                                 if (keep_children) {\r
3564                                         while (child = node.firstChild) {\r
3565                                                 // IE 8 will crash if you don't remove completely empty text nodes\r
3566                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)\r
3567                                                         parent.insertBefore(child, node);\r
3568                                                 else\r
3569                                                         node.removeChild(child);\r
3570                                         }\r
3571                                 }\r
3572 \r
3573                                 return parent.removeChild(node);\r
3574                         });\r
3575                 },\r
3576 \r
3577                 setStyle : function(n, na, v) {\r
3578                         var t = this;\r
3579 \r
3580                         return t.run(n, function(e) {\r
3581                                 var s, i;\r
3582 \r
3583                                 s = e.style;\r
3584 \r
3585                                 // Camelcase it, if needed\r
3586                                 na = na.replace(/-(\D)/g, function(a, b){\r
3587                                         return b.toUpperCase();\r
3588                                 });\r
3589 \r
3590                                 // Default px suffix on these\r
3591                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))\r
3592                                         v += 'px';\r
3593 \r
3594                                 switch (na) {\r
3595                                         case 'opacity':\r
3596                                                 // IE specific opacity\r
3597                                                 if (isIE) {\r
3598                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";\r
3599 \r
3600                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)\r
3601                                                                 s.display = 'inline-block';\r
3602                                                 }\r
3603 \r
3604                                                 // Fix for older browsers\r
3605                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';\r
3606                                                 break;\r
3607 \r
3608                                         case 'float':\r
3609                                                 isIE ? s.styleFloat = v : s.cssFloat = v;\r
3610                                                 break;\r
3611                                         \r
3612                                         default:\r
3613                                                 s[na] = v || '';\r
3614                                 }\r
3615 \r
3616                                 // Force update of the style data\r
3617                                 if (t.settings.update_styles)\r
3618                                         t.setAttrib(e, 'data-mce-style');\r
3619                         });\r
3620                 },\r
3621 \r
3622                 getStyle : function(n, na, c) {\r
3623                         n = this.get(n);\r
3624 \r
3625                         if (!n)\r
3626                                 return;\r
3627 \r
3628                         // Gecko\r
3629                         if (this.doc.defaultView && c) {\r
3630                                 // Remove camelcase\r
3631                                 na = na.replace(/[A-Z]/g, function(a){\r
3632                                         return '-' + a;\r
3633                                 });\r
3634 \r
3635                                 try {\r
3636                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);\r
3637                                 } catch (ex) {\r
3638                                         // Old safari might fail\r
3639                                         return null;\r
3640                                 }\r
3641                         }\r
3642 \r
3643                         // Camelcase it, if needed\r
3644                         na = na.replace(/-(\D)/g, function(a, b){\r
3645                                 return b.toUpperCase();\r
3646                         });\r
3647 \r
3648                         if (na == 'float')\r
3649                                 na = isIE ? 'styleFloat' : 'cssFloat';\r
3650 \r
3651                         // IE & Opera\r
3652                         if (n.currentStyle && c)\r
3653                                 return n.currentStyle[na];\r
3654 \r
3655                         return n.style ? n.style[na] : undefined;\r
3656                 },\r
3657 \r
3658                 setStyles : function(e, o) {\r
3659                         var t = this, s = t.settings, ol;\r
3660 \r
3661                         ol = s.update_styles;\r
3662                         s.update_styles = 0;\r
3663 \r
3664                         each(o, function(v, n) {\r
3665                                 t.setStyle(e, n, v);\r
3666                         });\r
3667 \r
3668                         // Update style info\r
3669                         s.update_styles = ol;\r
3670                         if (s.update_styles)\r
3671                                 t.setAttrib(e, s.cssText);\r
3672                 },\r
3673 \r
3674                 removeAllAttribs: function(e) {\r
3675                         return this.run(e, function(e) {\r
3676                                 var i, attrs = e.attributes;\r
3677                                 for (i = attrs.length - 1; i >= 0; i--) {\r
3678                                         e.removeAttributeNode(attrs.item(i));\r
3679                                 }\r
3680                         });\r
3681                 },\r
3682 \r
3683                 setAttrib : function(e, n, v) {\r
3684                         var t = this;\r
3685 \r
3686                         // Whats the point\r
3687                         if (!e || !n)\r
3688                                 return;\r
3689 \r
3690                         // Strict XML mode\r
3691                         if (t.settings.strict)\r
3692                                 n = n.toLowerCase();\r
3693 \r
3694                         return this.run(e, function(e) {\r
3695                                 var s = t.settings;\r
3696 \r
3697                                 switch (n) {\r
3698                                         case "style":\r
3699                                                 if (!is(v, 'string')) {\r
3700                                                         each(v, function(v, n) {\r
3701                                                                 t.setStyle(e, n, v);\r
3702                                                         });\r
3703 \r
3704                                                         return;\r
3705                                                 }\r
3706 \r
3707                                                 // No mce_style for elements with these since they might get resized by the user\r
3708                                                 if (s.keep_values) {\r
3709                                                         if (v && !t._isRes(v))\r
3710                                                                 e.setAttribute('data-mce-style', v, 2);\r
3711                                                         else\r
3712                                                                 e.removeAttribute('data-mce-style', 2);\r
3713                                                 }\r
3714 \r
3715                                                 e.style.cssText = v;\r
3716                                                 break;\r
3717 \r
3718                                         case "class":\r
3719                                                 e.className = v || ''; // Fix IE null bug\r
3720                                                 break;\r
3721 \r
3722                                         case "src":\r
3723                                         case "href":\r
3724                                                 if (s.keep_values) {\r
3725                                                         if (s.url_converter)\r
3726                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);\r
3727 \r
3728                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);\r
3729                                                 }\r
3730 \r
3731                                                 break;\r
3732 \r
3733                                         case "shape":\r
3734                                                 e.setAttribute('data-mce-style', v);\r
3735                                                 break;\r
3736                                 }\r
3737 \r
3738                                 if (is(v) && v !== null && v.length !== 0)\r
3739                                         e.setAttribute(n, '' + v, 2);\r
3740                                 else\r
3741                                         e.removeAttribute(n, 2);\r
3742                         });\r
3743                 },\r
3744 \r
3745                 setAttribs : function(e, o) {\r
3746                         var t = this;\r
3747 \r
3748                         return this.run(e, function(e) {\r
3749                                 each(o, function(v, n) {\r
3750                                         t.setAttrib(e, n, v);\r
3751                                 });\r
3752                         });\r
3753                 },\r
3754 \r
3755                 getAttrib : function(e, n, dv) {\r
3756                         var v, t = this;\r
3757 \r
3758                         e = t.get(e);\r
3759 \r
3760                         if (!e || e.nodeType !== 1)\r
3761                                 return false;\r
3762 \r
3763                         if (!is(dv))\r
3764                                 dv = '';\r
3765 \r
3766                         // Try the mce variant for these\r
3767                         if (/^(src|href|style|coords|shape)$/.test(n)) {\r
3768                                 v = e.getAttribute("data-mce-" + n);\r
3769 \r
3770                                 if (v)\r
3771                                         return v;\r
3772                         }\r
3773 \r
3774                         if (isIE && t.props[n]) {\r
3775                                 v = e[t.props[n]];\r
3776                                 v = v && v.nodeValue ? v.nodeValue : v;\r
3777                         }\r
3778 \r
3779                         if (!v)\r
3780                                 v = e.getAttribute(n, 2);\r
3781 \r
3782                         // Check boolean attribs\r
3783                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {\r
3784                                 if (e[t.props[n]] === true && v === '')\r
3785                                         return n;\r
3786 \r
3787                                 return v ? n : '';\r
3788                         }\r
3789 \r
3790                         // Inner input elements will override attributes on form elements\r
3791                         if (e.nodeName === "FORM" && e.getAttributeNode(n))\r
3792                                 return e.getAttributeNode(n).nodeValue;\r
3793 \r
3794                         if (n === 'style') {\r
3795                                 v = v || e.style.cssText;\r
3796 \r
3797                                 if (v) {\r
3798                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);\r
3799 \r
3800                                         if (t.settings.keep_values && !t._isRes(v))\r
3801                                                 e.setAttribute('data-mce-style', v);\r
3802                                 }\r
3803                         }\r
3804 \r
3805                         // Remove Apple and WebKit stuff\r
3806                         if (isWebKit && n === "class" && v)\r
3807                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');\r
3808 \r
3809                         // Handle IE issues\r
3810                         if (isIE) {\r
3811                                 switch (n) {\r
3812                                         case 'rowspan':\r
3813                                         case 'colspan':\r
3814                                                 // IE returns 1 as default value\r
3815                                                 if (v === 1)\r
3816                                                         v = '';\r
3817 \r
3818                                                 break;\r
3819 \r
3820                                         case 'size':\r
3821                                                 // IE returns +0 as default value for size\r
3822                                                 if (v === '+0' || v === 20 || v === 0)\r
3823                                                         v = '';\r
3824 \r
3825                                                 break;\r
3826 \r
3827                                         case 'width':\r
3828                                         case 'height':\r
3829                                         case 'vspace':\r
3830                                         case 'checked':\r
3831                                         case 'disabled':\r
3832                                         case 'readonly':\r
3833                                                 if (v === 0)\r
3834                                                         v = '';\r
3835 \r
3836                                                 break;\r
3837 \r
3838                                         case 'hspace':\r
3839                                                 // IE returns -1 as default value\r
3840                                                 if (v === -1)\r
3841                                                         v = '';\r
3842 \r
3843                                                 break;\r
3844 \r
3845                                         case 'maxlength':\r
3846                                         case 'tabindex':\r
3847                                                 // IE returns default value\r
3848                                                 if (v === 32768 || v === 2147483647 || v === '32768')\r
3849                                                         v = '';\r
3850 \r
3851                                                 break;\r
3852 \r
3853                                         case 'multiple':\r
3854                                         case 'compact':\r
3855                                         case 'noshade':\r
3856                                         case 'nowrap':\r
3857                                                 if (v === 65535)\r
3858                                                         return n;\r
3859 \r
3860                                                 return dv;\r
3861 \r
3862                                         case 'shape':\r
3863                                                 v = v.toLowerCase();\r
3864                                                 break;\r
3865 \r
3866                                         default:\r
3867                                                 // IE has odd anonymous function for event attributes\r
3868                                                 if (n.indexOf('on') === 0 && v)\r
3869                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);\r
3870                                 }\r
3871                         }\r
3872 \r
3873                         return (v !== undefined && v !== null && v !== '') ? '' + v : dv;\r
3874                 },\r
3875 \r
3876                 getPos : function(n, ro) {\r
3877                         var t = this, x = 0, y = 0, e, d = t.doc, r;\r
3878 \r
3879                         n = t.get(n);\r
3880                         ro = ro || d.body;\r
3881 \r
3882                         if (n) {\r
3883                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes\r
3884                                 if (n.getBoundingClientRect) {\r
3885                                         n = n.getBoundingClientRect();\r
3886                                         e = t.boxModel ? d.documentElement : d.body;\r
3887 \r
3888                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit\r
3889                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position\r
3890                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;\r
3891                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;\r
3892 \r
3893                                         return {x : x, y : y};\r
3894                                 }\r
3895 \r
3896                                 r = n;\r
3897                                 while (r && r != ro && r.nodeType) {\r
3898                                         x += r.offsetLeft || 0;\r
3899                                         y += r.offsetTop || 0;\r
3900                                         r = r.offsetParent;\r
3901                                 }\r
3902 \r
3903                                 r = n.parentNode;\r
3904                                 while (r && r != ro && r.nodeType) {\r
3905                                         x -= r.scrollLeft || 0;\r
3906                                         y -= r.scrollTop || 0;\r
3907                                         r = r.parentNode;\r
3908                                 }\r
3909                         }\r
3910 \r
3911                         return {x : x, y : y};\r
3912                 },\r
3913 \r
3914                 parseStyle : function(st) {\r
3915                         return this.styles.parse(st);\r
3916                 },\r
3917 \r
3918                 serializeStyle : function(o, name) {\r
3919                         return this.styles.serialize(o, name);\r
3920                 },\r
3921 \r
3922                 loadCSS : function(u) {\r
3923                         var t = this, d = t.doc, head;\r
3924 \r
3925                         if (!u)\r
3926                                 u = '';\r
3927 \r
3928                         head = t.select('head')[0];\r
3929 \r
3930                         each(u.split(','), function(u) {\r
3931                                 var link;\r
3932 \r
3933                                 if (t.files[u])\r
3934                                         return;\r
3935 \r
3936                                 t.files[u] = true;\r
3937                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});\r
3938 \r
3939                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug\r
3940                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading\r
3941                                 // It's ugly but it seems to work fine.\r
3942                                 if (isIE && d.documentMode && d.recalc) {\r
3943                                         link.onload = function() {\r
3944                                                 if (d.recalc)\r
3945                                                         d.recalc();\r
3946 \r
3947                                                 link.onload = null;\r
3948                                         };\r
3949                                 }\r
3950 \r
3951                                 head.appendChild(link);\r
3952                         });\r
3953                 },\r
3954 \r
3955                 addClass : function(e, c) {\r
3956                         return this.run(e, function(e) {\r
3957                                 var o;\r
3958 \r
3959                                 if (!c)\r
3960                                         return 0;\r
3961 \r
3962                                 if (this.hasClass(e, c))\r
3963                                         return e.className;\r
3964 \r
3965                                 o = this.removeClass(e, c);\r
3966 \r
3967                                 return e.className = (o != '' ? (o + ' ') : '') + c;\r
3968                         });\r
3969                 },\r
3970 \r
3971                 removeClass : function(e, c) {\r
3972                         var t = this, re;\r
3973 \r
3974                         return t.run(e, function(e) {\r
3975                                 var v;\r
3976 \r
3977                                 if (t.hasClass(e, c)) {\r
3978                                         if (!re)\r
3979                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");\r
3980 \r
3981                                         v = e.className.replace(re, ' ');\r
3982                                         v = tinymce.trim(v != ' ' ? v : '');\r
3983 \r
3984                                         e.className = v;\r
3985 \r
3986                                         // Empty class attr\r
3987                                         if (!v) {\r
3988                                                 e.removeAttribute('class');\r
3989                                                 e.removeAttribute('className');\r
3990                                         }\r
3991 \r
3992                                         return v;\r
3993                                 }\r
3994 \r
3995                                 return e.className;\r
3996                         });\r
3997                 },\r
3998 \r
3999                 hasClass : function(n, c) {\r
4000                         n = this.get(n);\r
4001 \r
4002                         if (!n || !c)\r
4003                                 return false;\r
4004 \r
4005                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;\r
4006                 },\r
4007 \r
4008                 show : function(e) {\r
4009                         return this.setStyle(e, 'display', 'block');\r
4010                 },\r
4011 \r
4012                 hide : function(e) {\r
4013                         return this.setStyle(e, 'display', 'none');\r
4014                 },\r
4015 \r
4016                 isHidden : function(e) {\r
4017                         e = this.get(e);\r
4018 \r
4019                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';\r
4020                 },\r
4021 \r
4022                 uniqueId : function(p) {\r
4023                         return (!p ? 'mce_' : p) + (this.counter++);\r
4024                 },\r
4025 \r
4026                 setHTML : function(element, html) {\r
4027                         var self = this;\r
4028 \r
4029                         return self.run(element, function(element) {\r
4030                                 if (isIE) {\r
4031                                         // Remove all child nodes, IE keeps empty text nodes in DOM\r
4032                                         while (element.firstChild)\r
4033                                                 element.removeChild(element.firstChild);\r
4034 \r
4035                                         try {\r
4036                                                 // IE will remove comments from the beginning\r
4037                                                 // unless you padd the contents with something\r
4038                                                 element.innerHTML = '<br />' + html;\r
4039                                                 element.removeChild(element.firstChild);\r
4040                                         } catch (ex) {\r
4041                                                 // 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
4042                                                 // This seems to fix this problem\r
4043 \r
4044                                                 // Create new div with HTML contents and a BR infront to keep comments\r
4045                                                 element = self.create('div');\r
4046                                                 element.innerHTML = '<br />' + html;\r
4047 \r
4048                                                 // Add all children from div to target\r
4049                                                 each (element.childNodes, function(node, i) {\r
4050                                                         // Skip br element\r
4051                                                         if (i)\r
4052                                                                 element.appendChild(node);\r
4053                                                 });\r
4054                                         }\r
4055                                 } else\r
4056                                         element.innerHTML = html;\r
4057 \r
4058                                 return html;\r
4059                         });\r
4060                 },\r
4061 \r
4062                 getOuterHTML : function(elm) {\r
4063                         var doc, self = this;\r
4064 \r
4065                         elm = self.get(elm);\r
4066 \r
4067                         if (!elm)\r
4068                                 return null;\r
4069 \r
4070                         if (elm.nodeType === 1 && self.hasOuterHTML)\r
4071                                 return elm.outerHTML;\r
4072 \r
4073                         doc = (elm.ownerDocument || self.doc).createElement("body");\r
4074                         doc.appendChild(elm.cloneNode(true));\r
4075 \r
4076                         return doc.innerHTML;\r
4077                 },\r
4078 \r
4079                 setOuterHTML : function(e, h, d) {\r
4080                         var t = this;\r
4081 \r
4082                         function setHTML(e, h, d) {\r
4083                                 var n, tp;\r
4084 \r
4085                                 tp = d.createElement("body");\r
4086                                 tp.innerHTML = h;\r
4087 \r
4088                                 n = tp.lastChild;\r
4089                                 while (n) {\r
4090                                         t.insertAfter(n.cloneNode(true), e);\r
4091                                         n = n.previousSibling;\r
4092                                 }\r
4093 \r
4094                                 t.remove(e);\r
4095                         };\r
4096 \r
4097                         return this.run(e, function(e) {\r
4098                                 e = t.get(e);\r
4099 \r
4100                                 // Only set HTML on elements\r
4101                                 if (e.nodeType == 1) {\r
4102                                         d = d || e.ownerDocument || t.doc;\r
4103 \r
4104                                         if (isIE) {\r
4105                                                 try {\r
4106                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error\r
4107                                                         if (isIE && e.nodeType == 1)\r
4108                                                                 e.outerHTML = h;\r
4109                                                         else\r
4110                                                                 setHTML(e, h, d);\r
4111                                                 } catch (ex) {\r
4112                                                         // Fix for unknown runtime error\r
4113                                                         setHTML(e, h, d);\r
4114                                                 }\r
4115                                         } else\r
4116                                                 setHTML(e, h, d);\r
4117                                 }\r
4118                         });\r
4119                 },\r
4120 \r
4121                 decode : Entities.decode,\r
4122 \r
4123                 encode : Entities.encodeAllRaw,\r
4124 \r
4125                 insertAfter : function(node, reference_node) {\r
4126                         reference_node = this.get(reference_node);\r
4127 \r
4128                         return this.run(node, function(node) {\r
4129                                 var parent, nextSibling;\r
4130 \r
4131                                 parent = reference_node.parentNode;\r
4132                                 nextSibling = reference_node.nextSibling;\r
4133 \r
4134                                 if (nextSibling)\r
4135                                         parent.insertBefore(node, nextSibling);\r
4136                                 else\r
4137                                         parent.appendChild(node);\r
4138 \r
4139                                 return node;\r
4140                         });\r
4141                 },\r
4142 \r
4143                 isBlock : function(node) {\r
4144                         var type = node.nodeType;\r
4145 \r
4146                         // If it's a node then check the type and use the nodeName\r
4147                         if (type)\r
4148                                 return !!(type === 1 && blockElementsMap[node.nodeName]);\r
4149 \r
4150                         return !!blockElementsMap[node];\r
4151                 },\r
4152 \r
4153                 replace : function(n, o, k) {\r
4154                         var t = this;\r
4155 \r
4156                         if (is(o, 'array'))\r
4157                                 n = n.cloneNode(true);\r
4158 \r
4159                         return t.run(o, function(o) {\r
4160                                 if (k) {\r
4161                                         each(tinymce.grep(o.childNodes), function(c) {\r
4162                                                 n.appendChild(c);\r
4163                                         });\r
4164                                 }\r
4165 \r
4166                                 return o.parentNode.replaceChild(n, o);\r
4167                         });\r
4168                 },\r
4169 \r
4170                 rename : function(elm, name) {\r
4171                         var t = this, newElm;\r
4172 \r
4173                         if (elm.nodeName != name.toUpperCase()) {\r
4174                                 // Rename block element\r
4175                                 newElm = t.create(name);\r
4176 \r
4177                                 // Copy attribs to new block\r
4178                                 each(t.getAttribs(elm), function(attr_node) {\r
4179                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));\r
4180                                 });\r
4181 \r
4182                                 // Replace block\r
4183                                 t.replace(newElm, elm, 1);\r
4184                         }\r
4185 \r
4186                         return newElm || elm;\r
4187                 },\r
4188 \r
4189                 findCommonAncestor : function(a, b) {\r
4190                         var ps = a, pe;\r
4191 \r
4192                         while (ps) {\r
4193                                 pe = b;\r
4194 \r
4195                                 while (pe && ps != pe)\r
4196                                         pe = pe.parentNode;\r
4197 \r
4198                                 if (ps == pe)\r
4199                                         break;\r
4200 \r
4201                                 ps = ps.parentNode;\r
4202                         }\r
4203 \r
4204                         if (!ps && a.ownerDocument)\r
4205                                 return a.ownerDocument.documentElement;\r
4206 \r
4207                         return ps;\r
4208                 },\r
4209 \r
4210                 toHex : function(s) {\r
4211                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);\r
4212 \r
4213                         function hex(s) {\r
4214                                 s = parseInt(s).toString(16);\r
4215 \r
4216                                 return s.length > 1 ? s : '0' + s; // 0 -> 00\r
4217                         };\r
4218 \r
4219                         if (c) {\r
4220                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);\r
4221 \r
4222                                 return s;\r
4223                         }\r
4224 \r
4225                         return s;\r
4226                 },\r
4227 \r
4228                 getClasses : function() {\r
4229                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;\r
4230 \r
4231                         if (t.classes)\r
4232                                 return t.classes;\r
4233 \r
4234                         function addClasses(s) {\r
4235                                 // IE style imports\r
4236                                 each(s.imports, function(r) {\r
4237                                         addClasses(r);\r
4238                                 });\r
4239 \r
4240                                 each(s.cssRules || s.rules, function(r) {\r
4241                                         // Real type or fake it on IE\r
4242                                         switch (r.type || 1) {\r
4243                                                 // Rule\r
4244                                                 case 1:\r
4245                                                         if (r.selectorText) {\r
4246                                                                 each(r.selectorText.split(','), function(v) {\r
4247                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");\r
4248 \r
4249                                                                         // Is internal or it doesn't contain a class\r
4250                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))\r
4251                                                                                 return;\r
4252 \r
4253                                                                         // Remove everything but class name\r
4254                                                                         ov = v;\r
4255                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);\r
4256 \r
4257                                                                         // Filter classes\r
4258                                                                         if (f && !(v = f(v, ov)))\r
4259                                                                                 return;\r
4260 \r
4261                                                                         if (!lo[v]) {\r
4262                                                                                 cl.push({'class' : v});\r
4263                                                                                 lo[v] = 1;\r
4264                                                                         }\r
4265                                                                 });\r
4266                                                         }\r
4267                                                         break;\r
4268 \r
4269                                                 // Import\r
4270                                                 case 3:\r
4271                                                         addClasses(r.styleSheet);\r
4272                                                         break;\r
4273                                         }\r
4274                                 });\r
4275                         };\r
4276 \r
4277                         try {\r
4278                                 each(t.doc.styleSheets, addClasses);\r
4279                         } catch (ex) {\r
4280                                 // Ignore\r
4281                         }\r
4282 \r
4283                         if (cl.length > 0)\r
4284                                 t.classes = cl;\r
4285 \r
4286                         return cl;\r
4287                 },\r
4288 \r
4289                 run : function(e, f, s) {\r
4290                         var t = this, o;\r
4291 \r
4292                         if (t.doc && typeof(e) === 'string')\r
4293                                 e = t.get(e);\r
4294 \r
4295                         if (!e)\r
4296                                 return false;\r
4297 \r
4298                         s = s || this;\r
4299                         if (!e.nodeType && (e.length || e.length === 0)) {\r
4300                                 o = [];\r
4301 \r
4302                                 each(e, function(e, i) {\r
4303                                         if (e) {\r
4304                                                 if (typeof(e) == 'string')\r
4305                                                         e = t.doc.getElementById(e);\r
4306 \r
4307                                                 o.push(f.call(s, e, i));\r
4308                                         }\r
4309                                 });\r
4310 \r
4311                                 return o;\r
4312                         }\r
4313 \r
4314                         return f.call(s, e);\r
4315                 },\r
4316 \r
4317                 getAttribs : function(n) {\r
4318                         var o;\r
4319 \r
4320                         n = this.get(n);\r
4321 \r
4322                         if (!n)\r
4323                                 return [];\r
4324 \r
4325                         if (isIE) {\r
4326                                 o = [];\r
4327 \r
4328                                 // Object will throw exception in IE\r
4329                                 if (n.nodeName == 'OBJECT')\r
4330                                         return n.attributes;\r
4331 \r
4332                                 // IE doesn't keep the selected attribute if you clone option elements\r
4333                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))\r
4334                                         o.push({specified : 1, nodeName : 'selected'});\r
4335 \r
4336                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time\r
4337                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {\r
4338                                         o.push({specified : 1, nodeName : a});\r
4339                                 });\r
4340 \r
4341                                 return o;\r
4342                         }\r
4343 \r
4344                         return n.attributes;\r
4345                 },\r
4346 \r
4347                 isEmpty : function(node, elements) {\r
4348                         var self = this, i, attributes, type, walker, name;\r
4349 \r
4350                         node = node.firstChild;\r
4351                         if (node) {\r
4352                                 walker = new tinymce.dom.TreeWalker(node);\r
4353                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;\r
4354 \r
4355                                 do {\r
4356                                         type = node.nodeType;\r
4357 \r
4358                                         if (type === 1) {\r
4359                                                 // Ignore bogus elements\r
4360                                                 if (node.getAttribute('data-mce-bogus'))\r
4361                                                         continue;\r
4362 \r
4363                                                 // Keep empty elements like <img />\r
4364                                                 if (elements && elements[node.nodeName.toLowerCase()])\r
4365                                                         return false;\r
4366 \r
4367                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>\r
4368                                                 attributes = self.getAttribs(node);\r
4369                                                 i = node.attributes.length;\r
4370                                                 while (i--) {\r
4371                                                         name = node.attributes[i].nodeName;\r
4372                                                         if (name === "name" || name.indexOf('data-') === 0)\r
4373                                                                 return false;\r
4374                                                 }\r
4375                                         }\r
4376 \r
4377                                         // Keep non whitespace text nodes\r
4378                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))\r
4379                                                 return false;\r
4380                                 } while (node = walker.next());\r
4381                         }\r
4382 \r
4383                         return true;\r
4384                 },\r
4385 \r
4386                 destroy : function(s) {\r
4387                         var t = this;\r
4388 \r
4389                         if (t.events)\r
4390                                 t.events.destroy();\r
4391 \r
4392                         t.win = t.doc = t.root = t.events = null;\r
4393 \r
4394                         // Manual destroy then remove unload handler\r
4395                         if (!s)\r
4396                                 tinymce.removeUnload(t.destroy);\r
4397                 },\r
4398 \r
4399                 createRng : function() {\r
4400                         var d = this.doc;\r
4401 \r
4402                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);\r
4403                 },\r
4404 \r
4405                 nodeIndex : function(node, normalized) {\r
4406                         var idx = 0, lastNodeType, lastNode, nodeType;\r
4407 \r
4408                         if (node) {\r
4409                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {\r
4410                                         nodeType = node.nodeType;\r
4411 \r
4412                                         // Normalize text nodes\r
4413                                         if (normalized && nodeType == 3) {\r
4414                                                 if (nodeType == lastNodeType || !node.nodeValue.length)\r
4415                                                         continue;\r
4416                                         }\r
4417                                         idx++;\r
4418                                         lastNodeType = nodeType;\r
4419                                 }\r
4420                         }\r
4421 \r
4422                         return idx;\r
4423                 },\r
4424 \r
4425                 split : function(pe, e, re) {\r
4426                         var t = this, r = t.createRng(), bef, aft, pa;\r
4427 \r
4428                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense\r
4429                         // but we don't want that in our code since it serves no purpose for the end user\r
4430                         // For example if this is chopped:\r
4431                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>\r
4432                         // would produce:\r
4433                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>\r
4434                         // this function will then trim of empty edges and produce:\r
4435                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>\r
4436                         function trim(node) {\r
4437                                 var i, children = node.childNodes, type = node.nodeType;\r
4438 \r
4439                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')\r
4440                                         return;\r
4441 \r
4442                                 for (i = children.length - 1; i >= 0; i--)\r
4443                                         trim(children[i]);\r
4444 \r
4445                                 if (type != 9) {\r
4446                                         // Keep non whitespace text nodes\r
4447                                         if (type == 3 && node.nodeValue.length > 0) {\r
4448                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"\r
4449                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)\r
4450                                                         return;\r
4451                                         } else if (type == 1) {\r
4452                                                 // If the only child is a bookmark then move it up\r
4453                                                 children = node.childNodes;\r
4454                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')\r
4455                                                         node.parentNode.insertBefore(children[0], node);\r
4456 \r
4457                                                 // Keep non empty elements or img, hr etc\r
4458                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))\r
4459                                                         return;\r
4460                                         }\r
4461 \r
4462                                         t.remove(node);\r
4463                                 }\r
4464 \r
4465                                 return node;\r
4466                         };\r
4467 \r
4468                         if (pe && e) {\r
4469                                 // Get before chunk\r
4470                                 r.setStart(pe.parentNode, t.nodeIndex(pe));\r
4471                                 r.setEnd(e.parentNode, t.nodeIndex(e));\r
4472                                 bef = r.extractContents();\r
4473 \r
4474                                 // Get after chunk\r
4475                                 r = t.createRng();\r
4476                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);\r
4477                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);\r
4478                                 aft = r.extractContents();\r
4479 \r
4480                                 // Insert before chunk\r
4481                                 pa = pe.parentNode;\r
4482                                 pa.insertBefore(trim(bef), pe);\r
4483 \r
4484                                 // Insert middle chunk\r
4485                                 if (re)\r
4486                                         pa.replaceChild(re, e);\r
4487                                 else\r
4488                                         pa.insertBefore(e, pe);\r
4489 \r
4490                                 // Insert after chunk\r
4491                                 pa.insertBefore(trim(aft), pe);\r
4492                                 t.remove(pe);\r
4493 \r
4494                                 return re || e;\r
4495                         }\r
4496                 },\r
4497 \r
4498                 bind : function(target, name, func, scope) {\r
4499                         var t = this;\r
4500 \r
4501                         if (!t.events)\r
4502                                 t.events = new tinymce.dom.EventUtils();\r
4503 \r
4504                         return t.events.add(target, name, func, scope || this);\r
4505                 },\r
4506 \r
4507                 unbind : function(target, name, func) {\r
4508                         var t = this;\r
4509 \r
4510                         if (!t.events)\r
4511                                 t.events = new tinymce.dom.EventUtils();\r
4512 \r
4513                         return t.events.remove(target, name, func);\r
4514                 },\r
4515 \r
4516 \r
4517                 _findSib : function(node, selector, name) {\r
4518                         var t = this, f = selector;\r
4519 \r
4520                         if (node) {\r
4521                                 // If expression make a function of it using is\r
4522                                 if (is(f, 'string')) {\r
4523                                         f = function(node) {\r
4524                                                 return t.is(node, selector);\r
4525                                         };\r
4526                                 }\r
4527 \r
4528                                 // Loop all siblings\r
4529                                 for (node = node[name]; node; node = node[name]) {\r
4530                                         if (f(node))\r
4531                                                 return node;\r
4532                                 }\r
4533                         }\r
4534 \r
4535                         return null;\r
4536                 },\r
4537 \r
4538                 _isRes : function(c) {\r
4539                         // Is live resizble element\r
4540                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);\r
4541                 }\r
4542 \r
4543                 /*\r
4544                 walk : function(n, f, s) {\r
4545                         var d = this.doc, w;\r
4546 \r
4547                         if (d.createTreeWalker) {\r
4548                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);\r
4549 \r
4550                                 while ((n = w.nextNode()) != null)\r
4551                                         f.call(s || this, n);\r
4552                         } else\r
4553                                 tinymce.walk(n, f, 'childNodes', s);\r
4554                 }\r
4555                 */\r
4556 \r
4557                 /*\r
4558                 toRGB : function(s) {\r
4559                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);\r
4560 \r
4561                         if (c) {\r
4562                                 // #FFF -> #FFFFFF\r
4563                                 if (!is(c[3]))\r
4564                                         c[3] = c[2] = c[1];\r
4565 \r
4566                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";\r
4567                         }\r
4568 \r
4569                         return s;\r
4570                 }\r
4571                 */\r
4572         });\r
4573 \r
4574         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});\r
4575 })(tinymce);\r
4576 \r
4577 (function(ns) {\r
4578         // Range constructor\r
4579         function Range(dom) {\r
4580                 var t = this,\r
4581                         doc = dom.doc,\r
4582                         EXTRACT = 0,\r
4583                         CLONE = 1,\r
4584                         DELETE = 2,\r
4585                         TRUE = true,\r
4586                         FALSE = false,\r
4587                         START_OFFSET = 'startOffset',\r
4588                         START_CONTAINER = 'startContainer',\r
4589                         END_CONTAINER = 'endContainer',\r
4590                         END_OFFSET = 'endOffset',\r
4591                         extend = tinymce.extend,\r
4592                         nodeIndex = dom.nodeIndex;\r
4593 \r
4594                 extend(t, {\r
4595                         // Inital states\r
4596                         startContainer : doc,\r
4597                         startOffset : 0,\r
4598                         endContainer : doc,\r
4599                         endOffset : 0,\r
4600                         collapsed : TRUE,\r
4601                         commonAncestorContainer : doc,\r
4602 \r
4603                         // Range constants\r
4604                         START_TO_START : 0,\r
4605                         START_TO_END : 1,\r
4606                         END_TO_END : 2,\r
4607                         END_TO_START : 3,\r
4608 \r
4609                         // Public methods\r
4610                         setStart : setStart,\r
4611                         setEnd : setEnd,\r
4612                         setStartBefore : setStartBefore,\r
4613                         setStartAfter : setStartAfter,\r
4614                         setEndBefore : setEndBefore,\r
4615                         setEndAfter : setEndAfter,\r
4616                         collapse : collapse,\r
4617                         selectNode : selectNode,\r
4618                         selectNodeContents : selectNodeContents,\r
4619                         compareBoundaryPoints : compareBoundaryPoints,\r
4620                         deleteContents : deleteContents,\r
4621                         extractContents : extractContents,\r
4622                         cloneContents : cloneContents,\r
4623                         insertNode : insertNode,\r
4624                         surroundContents : surroundContents,\r
4625                         cloneRange : cloneRange\r
4626                 });\r
4627 \r
4628                 function setStart(n, o) {\r
4629                         _setEndPoint(TRUE, n, o);\r
4630                 };\r
4631 \r
4632                 function setEnd(n, o) {\r
4633                         _setEndPoint(FALSE, n, o);\r
4634                 };\r
4635 \r
4636                 function setStartBefore(n) {\r
4637                         setStart(n.parentNode, nodeIndex(n));\r
4638                 };\r
4639 \r
4640                 function setStartAfter(n) {\r
4641                         setStart(n.parentNode, nodeIndex(n) + 1);\r
4642                 };\r
4643 \r
4644                 function setEndBefore(n) {\r
4645                         setEnd(n.parentNode, nodeIndex(n));\r
4646                 };\r
4647 \r
4648                 function setEndAfter(n) {\r
4649                         setEnd(n.parentNode, nodeIndex(n) + 1);\r
4650                 };\r
4651 \r
4652                 function collapse(ts) {\r
4653                         if (ts) {\r
4654                                 t[END_CONTAINER] = t[START_CONTAINER];\r
4655                                 t[END_OFFSET] = t[START_OFFSET];\r
4656                         } else {\r
4657                                 t[START_CONTAINER] = t[END_CONTAINER];\r
4658                                 t[START_OFFSET] = t[END_OFFSET];\r
4659                         }\r
4660 \r
4661                         t.collapsed = TRUE;\r
4662                 };\r
4663 \r
4664                 function selectNode(n) {\r
4665                         setStartBefore(n);\r
4666                         setEndAfter(n);\r
4667                 };\r
4668 \r
4669                 function selectNodeContents(n) {\r
4670                         setStart(n, 0);\r
4671                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);\r
4672                 };\r
4673 \r
4674                 function compareBoundaryPoints(h, r) {\r
4675                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],\r
4676                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;\r
4677 \r
4678                         // Check START_TO_START\r
4679                         if (h === 0)\r
4680                                 return _compareBoundaryPoints(sc, so, rsc, rso);\r
4681         \r
4682                         // Check START_TO_END\r
4683                         if (h === 1)\r
4684                                 return _compareBoundaryPoints(ec, eo, rsc, rso);\r
4685         \r
4686                         // Check END_TO_END\r
4687                         if (h === 2)\r
4688                                 return _compareBoundaryPoints(ec, eo, rec, reo);\r
4689         \r
4690                         // Check END_TO_START\r
4691                         if (h === 3) \r
4692                                 return _compareBoundaryPoints(sc, so, rec, reo);\r
4693                 };\r
4694 \r
4695                 function deleteContents() {\r
4696                         _traverse(DELETE);\r
4697                 };\r
4698 \r
4699                 function extractContents() {\r
4700                         return _traverse(EXTRACT);\r
4701                 };\r
4702 \r
4703                 function cloneContents() {\r
4704                         return _traverse(CLONE);\r
4705                 };\r
4706 \r
4707                 function insertNode(n) {\r
4708                         var startContainer = this[START_CONTAINER],\r
4709                                 startOffset = this[START_OFFSET], nn, o;\r
4710 \r
4711                         // Node is TEXT_NODE or CDATA\r
4712                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {\r
4713                                 if (!startOffset) {\r
4714                                         // At the start of text\r
4715                                         startContainer.parentNode.insertBefore(n, startContainer);\r
4716                                 } else if (startOffset >= startContainer.nodeValue.length) {\r
4717                                         // At the end of text\r
4718                                         dom.insertAfter(n, startContainer);\r
4719                                 } else {\r
4720                                         // Middle, need to split\r
4721                                         nn = startContainer.splitText(startOffset);\r
4722                                         startContainer.parentNode.insertBefore(n, nn);\r
4723                                 }\r
4724                         } else {\r
4725                                 // Insert element node\r
4726                                 if (startContainer.childNodes.length > 0)\r
4727                                         o = startContainer.childNodes[startOffset];\r
4728 \r
4729                                 if (o)\r
4730                                         startContainer.insertBefore(n, o);\r
4731                                 else\r
4732                                         startContainer.appendChild(n);\r
4733                         }\r
4734                 };\r
4735 \r
4736                 function surroundContents(n) {\r
4737                         var f = t.extractContents();\r
4738 \r
4739                         t.insertNode(n);\r
4740                         n.appendChild(f);\r
4741                         t.selectNode(n);\r
4742                 };\r
4743 \r
4744                 function cloneRange() {\r
4745                         return extend(new Range(dom), {\r
4746                                 startContainer : t[START_CONTAINER],\r
4747                                 startOffset : t[START_OFFSET],\r
4748                                 endContainer : t[END_CONTAINER],\r
4749                                 endOffset : t[END_OFFSET],\r
4750                                 collapsed : t.collapsed,\r
4751                                 commonAncestorContainer : t.commonAncestorContainer\r
4752                         });\r
4753                 };\r
4754 \r
4755                 // Private methods\r
4756 \r
4757                 function _getSelectedNode(container, offset) {\r
4758                         var child;\r
4759 \r
4760                         if (container.nodeType == 3 /* TEXT_NODE */)\r
4761                                 return container;\r
4762 \r
4763                         if (offset < 0)\r
4764                                 return container;\r
4765 \r
4766                         child = container.firstChild;\r
4767                         while (child && offset > 0) {\r
4768                                 --offset;\r
4769                                 child = child.nextSibling;\r
4770                         }\r
4771 \r
4772                         if (child)\r
4773                                 return child;\r
4774 \r
4775                         return container;\r
4776                 };\r
4777 \r
4778                 function _isCollapsed() {\r
4779                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);\r
4780                 };\r
4781 \r
4782                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {\r
4783                         var c, offsetC, n, cmnRoot, childA, childB;\r
4784                         \r
4785                         // In the first case the boundary-points have the same container. A is before B\r
4786                         // if its offset is less than the offset of B, A is equal to B if its offset is\r
4787                         // equal to the offset of B, and A is after B if its offset is greater than the\r
4788                         // offset of B.\r
4789                         if (containerA == containerB) {\r
4790                                 if (offsetA == offsetB)\r
4791                                         return 0; // equal\r
4792 \r
4793                                 if (offsetA < offsetB)\r
4794                                         return -1; // before\r
4795 \r
4796                                 return 1; // after\r
4797                         }\r
4798 \r
4799                         // In the second case a child node C of the container of A is an ancestor\r
4800                         // container of B. In this case, A is before B if the offset of A is less than or\r
4801                         // equal to the index of the child node C and A is after B otherwise.\r
4802                         c = containerB;\r
4803                         while (c && c.parentNode != containerA)\r
4804                                 c = c.parentNode;\r
4805 \r
4806                         if (c) {\r
4807                                 offsetC = 0;\r
4808                                 n = containerA.firstChild;\r
4809 \r
4810                                 while (n != c && offsetC < offsetA) {\r
4811                                         offsetC++;\r
4812                                         n = n.nextSibling;\r
4813                                 }\r
4814 \r
4815                                 if (offsetA <= offsetC)\r
4816                                         return -1; // before\r
4817 \r
4818                                 return 1; // after\r
4819                         }\r
4820 \r
4821                         // In the third case a child node C of the container of B is an ancestor container\r
4822                         // of A. In this case, A is before B if the index of the child node C is less than\r
4823                         // the offset of B and A is after B otherwise.\r
4824                         c = containerA;\r
4825                         while (c && c.parentNode != containerB) {\r
4826                                 c = c.parentNode;\r
4827                         }\r
4828 \r
4829                         if (c) {\r
4830                                 offsetC = 0;\r
4831                                 n = containerB.firstChild;\r
4832 \r
4833                                 while (n != c && offsetC < offsetB) {\r
4834                                         offsetC++;\r
4835                                         n = n.nextSibling;\r
4836                                 }\r
4837 \r
4838                                 if (offsetC < offsetB)\r
4839                                         return -1; // before\r
4840 \r
4841                                 return 1; // after\r
4842                         }\r
4843 \r
4844                         // In the fourth case, none of three other cases hold: the containers of A and B\r
4845                         // are siblings or descendants of sibling nodes. In this case, A is before B if\r
4846                         // the container of A is before the container of B in a pre-order traversal of the\r
4847                         // Ranges' context tree and A is after B otherwise.\r
4848                         cmnRoot = dom.findCommonAncestor(containerA, containerB);\r
4849                         childA = containerA;\r
4850 \r
4851                         while (childA && childA.parentNode != cmnRoot)\r
4852                                 childA = childA.parentNode;\r
4853 \r
4854                         if (!childA)\r
4855                                 childA = cmnRoot;\r
4856 \r
4857                         childB = containerB;\r
4858                         while (childB && childB.parentNode != cmnRoot)\r
4859                                 childB = childB.parentNode;\r
4860 \r
4861                         if (!childB)\r
4862                                 childB = cmnRoot;\r
4863 \r
4864                         if (childA == childB)\r
4865                                 return 0; // equal\r
4866 \r
4867                         n = cmnRoot.firstChild;\r
4868                         while (n) {\r
4869                                 if (n == childA)\r
4870                                         return -1; // before\r
4871 \r
4872                                 if (n == childB)\r
4873                                         return 1; // after\r
4874 \r
4875                                 n = n.nextSibling;\r
4876                         }\r
4877                 };\r
4878 \r
4879                 function _setEndPoint(st, n, o) {\r
4880                         var ec, sc;\r
4881 \r
4882                         if (st) {\r
4883                                 t[START_CONTAINER] = n;\r
4884                                 t[START_OFFSET] = o;\r
4885                         } else {\r
4886                                 t[END_CONTAINER] = n;\r
4887                                 t[END_OFFSET] = o;\r
4888                         }\r
4889 \r
4890                         // If one boundary-point of a Range is set to have a root container\r
4891                         // other than the current one for the Range, the Range is collapsed to\r
4892                         // the new position. This enforces the restriction that both boundary-\r
4893                         // points of a Range must have the same root container.\r
4894                         ec = t[END_CONTAINER];\r
4895                         while (ec.parentNode)\r
4896                                 ec = ec.parentNode;\r
4897 \r
4898                         sc = t[START_CONTAINER];\r
4899                         while (sc.parentNode)\r
4900                                 sc = sc.parentNode;\r
4901 \r
4902                         if (sc == ec) {\r
4903                                 // The start position of a Range is guaranteed to never be after the\r
4904                                 // end position. To enforce this restriction, if the start is set to\r
4905                                 // be at a position after the end, the Range is collapsed to that\r
4906                                 // position.\r
4907                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)\r
4908                                         t.collapse(st);\r
4909                         } else\r
4910                                 t.collapse(st);\r
4911 \r
4912                         t.collapsed = _isCollapsed();\r
4913                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);\r
4914                 };\r
4915 \r
4916                 function _traverse(how) {\r
4917                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;\r
4918 \r
4919                         if (t[START_CONTAINER] == t[END_CONTAINER])\r
4920                                 return _traverseSameContainer(how);\r
4921 \r
4922                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
4923                                 if (p == t[START_CONTAINER])\r
4924                                         return _traverseCommonStartContainer(c, how);\r
4925 \r
4926                                 ++endContainerDepth;\r
4927                         }\r
4928 \r
4929                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
4930                                 if (p == t[END_CONTAINER])\r
4931                                         return _traverseCommonEndContainer(c, how);\r
4932 \r
4933                                 ++startContainerDepth;\r
4934                         }\r
4935 \r
4936                         depthDiff = startContainerDepth - endContainerDepth;\r
4937 \r
4938                         startNode = t[START_CONTAINER];\r
4939                         while (depthDiff > 0) {\r
4940                                 startNode = startNode.parentNode;\r
4941                                 depthDiff--;\r
4942                         }\r
4943 \r
4944                         endNode = t[END_CONTAINER];\r
4945                         while (depthDiff < 0) {\r
4946                                 endNode = endNode.parentNode;\r
4947                                 depthDiff++;\r
4948                         }\r
4949 \r
4950                         // ascend the ancestor hierarchy until we have a common parent.\r
4951                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {\r
4952                                 startNode = sp;\r
4953                                 endNode = ep;\r
4954                         }\r
4955 \r
4956                         return _traverseCommonAncestors(startNode, endNode, how);\r
4957                 };\r
4958 \r
4959                  function _traverseSameContainer(how) {\r
4960                         var frag, s, sub, n, cnt, sibling, xferNode;\r
4961 \r
4962                         if (how != DELETE)\r
4963                                 frag = doc.createDocumentFragment();\r
4964 \r
4965                         // If selection is empty, just return the fragment\r
4966                         if (t[START_OFFSET] == t[END_OFFSET])\r
4967                                 return frag;\r
4968 \r
4969                         // Text node needs special case handling\r
4970                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {\r
4971                                 // get the substring\r
4972                                 s = t[START_CONTAINER].nodeValue;\r
4973                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);\r
4974 \r
4975                                 // set the original text node to its new value\r
4976                                 if (how != CLONE) {\r
4977                                         t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);\r
4978 \r
4979                                         // Nothing is partially selected, so collapse to start point\r
4980                                         t.collapse(TRUE);\r
4981                                 }\r
4982 \r
4983                                 if (how == DELETE)\r
4984                                         return;\r
4985 \r
4986                                 frag.appendChild(doc.createTextNode(sub));\r
4987                                 return frag;\r
4988                         }\r
4989 \r
4990                         // Copy nodes between the start/end offsets.\r
4991                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);\r
4992                         cnt = t[END_OFFSET] - t[START_OFFSET];\r
4993 \r
4994                         while (cnt > 0) {\r
4995                                 sibling = n.nextSibling;\r
4996                                 xferNode = _traverseFullySelected(n, how);\r
4997 \r
4998                                 if (frag)\r
4999                                         frag.appendChild( xferNode );\r
5000 \r
5001                                 --cnt;\r
5002                                 n = sibling;\r
5003                         }\r
5004 \r
5005                         // Nothing is partially selected, so collapse to start point\r
5006                         if (how != CLONE)\r
5007                                 t.collapse(TRUE);\r
5008 \r
5009                         return frag;\r
5010                 };\r
5011 \r
5012                 function _traverseCommonStartContainer(endAncestor, how) {\r
5013                         var frag, n, endIdx, cnt, sibling, xferNode;\r
5014 \r
5015                         if (how != DELETE)\r
5016                                 frag = doc.createDocumentFragment();\r
5017 \r
5018                         n = _traverseRightBoundary(endAncestor, how);\r
5019 \r
5020                         if (frag)\r
5021                                 frag.appendChild(n);\r
5022 \r
5023                         endIdx = nodeIndex(endAncestor);\r
5024                         cnt = endIdx - t[START_OFFSET];\r
5025 \r
5026                         if (cnt <= 0) {\r
5027                                 // Collapse to just before the endAncestor, which\r
5028                                 // is partially selected.\r
5029                                 if (how != CLONE) {\r
5030                                         t.setEndBefore(endAncestor);\r
5031                                         t.collapse(FALSE);\r
5032                                 }\r
5033 \r
5034                                 return frag;\r
5035                         }\r
5036 \r
5037                         n = endAncestor.previousSibling;\r
5038                         while (cnt > 0) {\r
5039                                 sibling = n.previousSibling;\r
5040                                 xferNode = _traverseFullySelected(n, how);\r
5041 \r
5042                                 if (frag)\r
5043                                         frag.insertBefore(xferNode, frag.firstChild);\r
5044 \r
5045                                 --cnt;\r
5046                                 n = sibling;\r
5047                         }\r
5048 \r
5049                         // Collapse to just before the endAncestor, which\r
5050                         // is partially selected.\r
5051                         if (how != CLONE) {\r
5052                                 t.setEndBefore(endAncestor);\r
5053                                 t.collapse(FALSE);\r
5054                         }\r
5055 \r
5056                         return frag;\r
5057                 };\r
5058 \r
5059                 function _traverseCommonEndContainer(startAncestor, how) {\r
5060                         var frag, startIdx, n, cnt, sibling, xferNode;\r
5061 \r
5062                         if (how != DELETE)\r
5063                                 frag = doc.createDocumentFragment();\r
5064 \r
5065                         n = _traverseLeftBoundary(startAncestor, how);\r
5066                         if (frag)\r
5067                                 frag.appendChild(n);\r
5068 \r
5069                         startIdx = nodeIndex(startAncestor);\r
5070                         ++startIdx; // Because we already traversed it\r
5071 \r
5072                         cnt = t[END_OFFSET] - startIdx;\r
5073                         n = startAncestor.nextSibling;\r
5074                         while (cnt > 0) {\r
5075                                 sibling = n.nextSibling;\r
5076                                 xferNode = _traverseFullySelected(n, how);\r
5077 \r
5078                                 if (frag)\r
5079                                         frag.appendChild(xferNode);\r
5080 \r
5081                                 --cnt;\r
5082                                 n = sibling;\r
5083                         }\r
5084 \r
5085                         if (how != CLONE) {\r
5086                                 t.setStartAfter(startAncestor);\r
5087                                 t.collapse(TRUE);\r
5088                         }\r
5089 \r
5090                         return frag;\r
5091                 };\r
5092 \r
5093                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {\r
5094                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;\r
5095 \r
5096                         if (how != DELETE)\r
5097                                 frag = doc.createDocumentFragment();\r
5098 \r
5099                         n = _traverseLeftBoundary(startAncestor, how);\r
5100                         if (frag)\r
5101                                 frag.appendChild(n);\r
5102 \r
5103                         commonParent = startAncestor.parentNode;\r
5104                         startOffset = nodeIndex(startAncestor);\r
5105                         endOffset = nodeIndex(endAncestor);\r
5106                         ++startOffset;\r
5107 \r
5108                         cnt = endOffset - startOffset;\r
5109                         sibling = startAncestor.nextSibling;\r
5110 \r
5111                         while (cnt > 0) {\r
5112                                 nextSibling = sibling.nextSibling;\r
5113                                 n = _traverseFullySelected(sibling, how);\r
5114 \r
5115                                 if (frag)\r
5116                                         frag.appendChild(n);\r
5117 \r
5118                                 sibling = nextSibling;\r
5119                                 --cnt;\r
5120                         }\r
5121 \r
5122                         n = _traverseRightBoundary(endAncestor, how);\r
5123 \r
5124                         if (frag)\r
5125                                 frag.appendChild(n);\r
5126 \r
5127                         if (how != CLONE) {\r
5128                                 t.setStartAfter(startAncestor);\r
5129                                 t.collapse(TRUE);\r
5130                         }\r
5131 \r
5132                         return frag;\r
5133                 };\r
5134 \r
5135                 function _traverseRightBoundary(root, how) {\r
5136                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];\r
5137 \r
5138                         if (next == root)\r
5139                                 return _traverseNode(next, isFullySelected, FALSE, how);\r
5140 \r
5141                         parent = next.parentNode;\r
5142                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);\r
5143 \r
5144                         while (parent) {\r
5145                                 while (next) {\r
5146                                         prevSibling = next.previousSibling;\r
5147                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);\r
5148 \r
5149                                         if (how != DELETE)\r
5150                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);\r
5151 \r
5152                                         isFullySelected = TRUE;\r
5153                                         next = prevSibling;\r
5154                                 }\r
5155 \r
5156                                 if (parent == root)\r
5157                                         return clonedParent;\r
5158 \r
5159                                 next = parent.previousSibling;\r
5160                                 parent = parent.parentNode;\r
5161 \r
5162                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);\r
5163 \r
5164                                 if (how != DELETE)\r
5165                                         clonedGrandParent.appendChild(clonedParent);\r
5166 \r
5167                                 clonedParent = clonedGrandParent;\r
5168                         }\r
5169                 };\r
5170 \r
5171                 function _traverseLeftBoundary(root, how) {\r
5172                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;\r
5173 \r
5174                         if (next == root)\r
5175                                 return _traverseNode(next, isFullySelected, TRUE, how);\r
5176 \r
5177                         parent = next.parentNode;\r
5178                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);\r
5179 \r
5180                         while (parent) {\r
5181                                 while (next) {\r
5182                                         nextSibling = next.nextSibling;\r
5183                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);\r
5184 \r
5185                                         if (how != DELETE)\r
5186                                                 clonedParent.appendChild(clonedChild);\r
5187 \r
5188                                         isFullySelected = TRUE;\r
5189                                         next = nextSibling;\r
5190                                 }\r
5191 \r
5192                                 if (parent == root)\r
5193                                         return clonedParent;\r
5194 \r
5195                                 next = parent.nextSibling;\r
5196                                 parent = parent.parentNode;\r
5197 \r
5198                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);\r
5199 \r
5200                                 if (how != DELETE)\r
5201                                         clonedGrandParent.appendChild(clonedParent);\r
5202 \r
5203                                 clonedParent = clonedGrandParent;\r
5204                         }\r
5205                 };\r
5206 \r
5207                 function _traverseNode(n, isFullySelected, isLeft, how) {\r
5208                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;\r
5209 \r
5210                         if (isFullySelected)\r
5211                                 return _traverseFullySelected(n, how);\r
5212 \r
5213                         if (n.nodeType == 3 /* TEXT_NODE */) {\r
5214                                 txtValue = n.nodeValue;\r
5215 \r
5216                                 if (isLeft) {\r
5217                                         offset = t[START_OFFSET];\r
5218                                         newNodeValue = txtValue.substring(offset);\r
5219                                         oldNodeValue = txtValue.substring(0, offset);\r
5220                                 } else {\r
5221                                         offset = t[END_OFFSET];\r
5222                                         newNodeValue = txtValue.substring(0, offset);\r
5223                                         oldNodeValue = txtValue.substring(offset);\r
5224                                 }\r
5225 \r
5226                                 if (how != CLONE)\r
5227                                         n.nodeValue = oldNodeValue;\r
5228 \r
5229                                 if (how == DELETE)\r
5230                                         return;\r
5231 \r
5232                                 newNode = n.cloneNode(FALSE);\r
5233                                 newNode.nodeValue = newNodeValue;\r
5234 \r
5235                                 return newNode;\r
5236                         }\r
5237 \r
5238                         if (how == DELETE)\r
5239                                 return;\r
5240 \r
5241                         return n.cloneNode(FALSE);\r
5242                 };\r
5243 \r
5244                 function _traverseFullySelected(n, how) {\r
5245                         if (how != DELETE)\r
5246                                 return how == CLONE ? n.cloneNode(TRUE) : n;\r
5247 \r
5248                         n.parentNode.removeChild(n);\r
5249                 };\r
5250         };\r
5251 \r
5252         ns.Range = Range;\r
5253 })(tinymce.dom);\r
5254 \r
5255 (function() {\r
5256         function Selection(selection) {\r
5257                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;\r
5258 \r
5259                 function getPosition(rng, start) {\r
5260                         var checkRng, startIndex = 0, endIndex, inside,\r
5261                                 children, child, offset, index, position = -1, parent;\r
5262 \r
5263                         // Setup test range, collapse it and get the parent\r
5264                         checkRng = rng.duplicate();\r
5265                         checkRng.collapse(start);\r
5266                         parent = checkRng.parentElement();\r
5267 \r
5268                         // Check if the selection is within the right document\r
5269                         if (parent.ownerDocument !== selection.dom.doc)\r
5270                                 return;\r
5271 \r
5272                         // IE will report non editable elements as it's parent so look for an editable one\r
5273                         while (parent.contentEditable === "false") {\r
5274                                 parent = parent.parentNode;\r
5275                         }\r
5276 \r
5277                         // If parent doesn't have any children then return that we are inside the element\r
5278                         if (!parent.hasChildNodes()) {\r
5279                                 return {node : parent, inside : 1};\r
5280                         }\r
5281 \r
5282                         // Setup node list and endIndex\r
5283                         children = parent.children;\r
5284                         endIndex = children.length - 1;\r
5285 \r
5286                         // Perform a binary search for the position\r
5287                         while (startIndex <= endIndex) {\r
5288                                 index = Math.floor((startIndex + endIndex) / 2);\r
5289 \r
5290                                 // Move selection to node and compare the ranges\r
5291                                 child = children[index];\r
5292                                 checkRng.moveToElementText(child);\r
5293                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);\r
5294 \r
5295                                 // Before/after or an exact match\r
5296                                 if (position > 0) {\r
5297                                         endIndex = index - 1;\r
5298                                 } else if (position < 0) {\r
5299                                         startIndex = index + 1;\r
5300                                 } else {\r
5301                                         return {node : child};\r
5302                                 }\r
5303                         }\r
5304 \r
5305                         // Check if child position is before or we didn't find a position\r
5306                         if (position < 0) {\r
5307                                 // No element child was found use the parent element and the offset inside that\r
5308                                 if (!child) {\r
5309                                         checkRng.moveToElementText(parent);\r
5310                                         checkRng.collapse(true);\r
5311                                         child = parent;\r
5312                                         inside = true;\r
5313                                 } else\r
5314                                         checkRng.collapse(false);\r
5315 \r
5316                                 checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);\r
5317 \r
5318                                 // Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>\r
5319                                 if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {\r
5320                                         checkRng = rng.duplicate();\r
5321                                         checkRng.collapse(start);\r
5322 \r
5323                                         offset = -1;\r
5324                                         while (parent == checkRng.parentElement()) {\r
5325                                                 if (checkRng.move('character', -1) == 0)\r
5326                                                         break;\r
5327 \r
5328                                                 offset++;\r
5329                                         }\r
5330                                 }\r
5331 \r
5332                                 offset = offset || checkRng.text.replace('\r\n', ' ').length;\r
5333                         } else {\r
5334                                 // Child position is after the selection endpoint\r
5335                                 checkRng.collapse(true);\r
5336                                 checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);\r
5337 \r
5338                                 // Get the length of the text to find where the endpoint is relative to it's container\r
5339                                 offset = checkRng.text.replace('\r\n', ' ').length;\r
5340                         }\r
5341 \r
5342                         return {node : child, position : position, offset : offset, inside : inside};\r
5343                 };\r
5344 \r
5345                 // Returns a W3C DOM compatible range object by using the IE Range API\r
5346                 function getRange() {\r
5347                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;\r
5348 \r
5349                         // If selection is outside the current document just return an empty range\r
5350                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();\r
5351                         if (element.ownerDocument != dom.doc)\r
5352                                 return domRange;\r
5353 \r
5354                         collapsed = selection.isCollapsed();\r
5355 \r
5356                         // Handle control selection\r
5357                         if (ieRange.item) {\r
5358                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));\r
5359                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);\r
5360 \r
5361                                 return domRange;\r
5362                         }\r
5363 \r
5364                         function findEndPoint(start) {\r
5365                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;\r
5366 \r
5367                                 container = endPoint.node;\r
5368                                 offset = endPoint.offset;\r
5369 \r
5370                                 if (endPoint.inside && !container.hasChildNodes()) {\r
5371                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);\r
5372                                         return;\r
5373                                 }\r
5374 \r
5375                                 if (offset === undef) {\r
5376                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);\r
5377                                         return;\r
5378                                 }\r
5379 \r
5380                                 if (endPoint.position < 0) {\r
5381                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;\r
5382 \r
5383                                         if (!sibling) {\r
5384                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);\r
5385                                                 return;\r
5386                                         }\r
5387 \r
5388                                         if (!offset) {\r
5389                                                 if (sibling.nodeType == 3)\r
5390                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);\r
5391                                                 else\r
5392                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);\r
5393 \r
5394                                                 return;\r
5395                                         }\r
5396 \r
5397                                         // Find the text node and offset\r
5398                                         while (sibling) {\r
5399                                                 nodeValue = sibling.nodeValue;\r
5400                                                 textNodeOffset += nodeValue.length;\r
5401 \r
5402                                                 // We are at or passed the position we where looking for\r
5403                                                 if (textNodeOffset >= offset) {\r
5404                                                         container = sibling;\r
5405                                                         textNodeOffset -= offset;\r
5406                                                         textNodeOffset = nodeValue.length - textNodeOffset;\r
5407                                                         break;\r
5408                                                 }\r
5409 \r
5410                                                 sibling = sibling.nextSibling;\r
5411                                         }\r
5412                                 } else {\r
5413                                         // Find the text node and offset\r
5414                                         sibling = container.previousSibling;\r
5415 \r
5416                                         if (!sibling)\r
5417                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);\r
5418 \r
5419                                         // If there isn't any text to loop then use the first position\r
5420                                         if (!offset) {\r
5421                                                 if (container.nodeType == 3)\r
5422                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);\r
5423                                                 else\r
5424                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);\r
5425 \r
5426                                                 return;\r
5427                                         }\r
5428 \r
5429                                         while (sibling) {\r
5430                                                 textNodeOffset += sibling.nodeValue.length;\r
5431 \r
5432                                                 // We are at or passed the position we where looking for\r
5433                                                 if (textNodeOffset >= offset) {\r
5434                                                         container = sibling;\r
5435                                                         textNodeOffset -= offset;\r
5436                                                         break;\r
5437                                                 }\r
5438 \r
5439                                                 sibling = sibling.previousSibling;\r
5440                                         }\r
5441                                 }\r
5442 \r
5443                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);\r
5444                         };\r
5445 \r
5446                         try {\r
5447                                 // Find start point\r
5448                                 findEndPoint(true);\r
5449 \r
5450                                 // Find end point if needed\r
5451                                 if (!collapsed)\r
5452                                         findEndPoint();\r
5453                         } catch (ex) {\r
5454                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you\r
5455                                 // access the nodeValue or other properties of text nodes. This seems to happend when\r
5456                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.\r
5457                                 if (ex.number == -2147024809) {\r
5458                                         // Get the current selection\r
5459                                         bookmark = self.getBookmark(2);\r
5460 \r
5461                                         // Get start element\r
5462                                         tmpRange = ieRange.duplicate();\r
5463                                         tmpRange.collapse(true);\r
5464                                         element = tmpRange.parentElement();\r
5465 \r
5466                                         // Get end element\r
5467                                         if (!collapsed) {\r
5468                                                 tmpRange = ieRange.duplicate();\r
5469                                                 tmpRange.collapse(false);\r
5470                                                 element2 = tmpRange.parentElement();\r
5471                                                 element2.innerHTML = element2.innerHTML;\r
5472                                         }\r
5473 \r
5474                                         // Remove the broken elements\r
5475                                         element.innerHTML = element.innerHTML;\r
5476 \r
5477                                         // Restore the selection\r
5478                                         self.moveToBookmark(bookmark);\r
5479 \r
5480                                         // Since the range has moved we need to re-get it\r
5481                                         ieRange = selection.getRng();\r
5482 \r
5483                                         // Find start point\r
5484                                         findEndPoint(true);\r
5485 \r
5486                                         // Find end point if needed\r
5487                                         if (!collapsed)\r
5488                                                 findEndPoint();\r
5489                                 } else\r
5490                                         throw ex; // Throw other errors\r
5491                         }\r
5492 \r
5493                         return domRange;\r
5494                 };\r
5495 \r
5496                 this.getBookmark = function(type) {\r
5497                         var rng = selection.getRng(), start, end, bookmark = {};\r
5498 \r
5499                         function getIndexes(node) {\r
5500                                 var node, parent, root, children, i, indexes = [];\r
5501 \r
5502                                 parent = node.parentNode;\r
5503                                 root = dom.getRoot().parentNode;\r
5504 \r
5505                                 while (parent != root) {\r
5506                                         children = parent.children;\r
5507 \r
5508                                         i = children.length;\r
5509                                         while (i--) {\r
5510                                                 if (node === children[i]) {\r
5511                                                         indexes.push(i);\r
5512                                                         break;\r
5513                                                 }\r
5514                                         }\r
5515 \r
5516                                         node = parent;\r
5517                                         parent = parent.parentNode;\r
5518                                 }\r
5519 \r
5520                                 return indexes;\r
5521                         };\r
5522 \r
5523                         function getBookmarkEndPoint(start) {\r
5524                                 var position;\r
5525 \r
5526                                 position = getPosition(rng, start);\r
5527                                 if (position) {\r
5528                                         return {\r
5529                                                 position : position.position,\r
5530                                                 offset : position.offset,\r
5531                                                 indexes : getIndexes(position.node),\r
5532                                                 inside : position.inside\r
5533                                         };\r
5534                                 }\r
5535                         };\r
5536 \r
5537                         // Non ubstructive bookmark\r
5538                         if (type === 2) {\r
5539                                 // Handle text selection\r
5540                                 if (!rng.item) {\r
5541                                         bookmark.start = getBookmarkEndPoint(true);\r
5542 \r
5543                                         if (!selection.isCollapsed())\r
5544                                                 bookmark.end = getBookmarkEndPoint();\r
5545                                 } else\r
5546                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};\r
5547                         }\r
5548 \r
5549                         return bookmark;\r
5550                 };\r
5551 \r
5552                 this.moveToBookmark = function(bookmark) {\r
5553                         var rng, body = dom.doc.body;\r
5554 \r
5555                         function resolveIndexes(indexes) {\r
5556                                 var node, i, idx, children;\r
5557 \r
5558                                 node = dom.getRoot();\r
5559                                 for (i = indexes.length - 1; i >= 0; i--) {\r
5560                                         children = node.children;\r
5561                                         idx = indexes[i];\r
5562 \r
5563                                         if (idx <= children.length - 1) {\r
5564                                                 node = children[idx];\r
5565                                         }\r
5566                                 }\r
5567 \r
5568                                 return node;\r
5569                         };\r
5570                         \r
5571                         function setBookmarkEndPoint(start) {\r
5572                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;\r
5573 \r
5574                                 if (endPoint) {\r
5575                                         moveLeft = endPoint.position > 0;\r
5576 \r
5577                                         moveRng = body.createTextRange();\r
5578                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));\r
5579 \r
5580                                         offset = endPoint.offset;\r
5581                                         if (offset !== undef) {\r
5582                                                 moveRng.collapse(endPoint.inside || moveLeft);\r
5583                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);\r
5584                                         } else\r
5585                                                 moveRng.collapse(start);\r
5586 \r
5587                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);\r
5588 \r
5589                                         if (start)\r
5590                                                 rng.collapse(true);\r
5591                                 }\r
5592                         };\r
5593 \r
5594                         if (bookmark.start) {\r
5595                                 if (bookmark.start.ctrl) {\r
5596                                         rng = body.createControlRange();\r
5597                                         rng.addElement(resolveIndexes(bookmark.start.indexes));\r
5598                                         rng.select();\r
5599                                 } else {\r
5600                                         rng = body.createTextRange();\r
5601                                         setBookmarkEndPoint(true);\r
5602                                         setBookmarkEndPoint();\r
5603                                         rng.select();\r
5604                                 }\r
5605                         }\r
5606                 };\r
5607 \r
5608                 this.addRange = function(rng) {\r
5609                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;\r
5610 \r
5611                         function setEndPoint(start) {\r
5612                                 var container, offset, marker, tmpRng, nodes;\r
5613 \r
5614                                 marker = dom.create('a');\r
5615                                 container = start ? startContainer : endContainer;\r
5616                                 offset = start ? startOffset : endOffset;\r
5617                                 tmpRng = ieRng.duplicate();\r
5618 \r
5619                                 if (container == doc || container == doc.documentElement) {\r
5620                                         container = body;\r
5621                                         offset = 0;\r
5622                                 }\r
5623 \r
5624                                 if (container.nodeType == 3) {\r
5625                                         container.parentNode.insertBefore(marker, container);\r
5626                                         tmpRng.moveToElementText(marker);\r
5627                                         tmpRng.moveStart('character', offset);\r
5628                                         dom.remove(marker);\r
5629                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
5630                                 } else {\r
5631                                         nodes = container.childNodes;\r
5632 \r
5633                                         if (nodes.length) {\r
5634                                                 if (offset >= nodes.length) {\r
5635                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);\r
5636                                                 } else {\r
5637                                                         container.insertBefore(marker, nodes[offset]);\r
5638                                                 }\r
5639 \r
5640                                                 tmpRng.moveToElementText(marker);\r
5641                                         } else {\r
5642                                                 // Empty node selection for example <div>|</div>\r
5643                                                 marker = doc.createTextNode('\uFEFF');\r
5644                                                 container.appendChild(marker);\r
5645                                                 tmpRng.moveToElementText(marker.parentNode);\r
5646                                                 tmpRng.collapse(TRUE);\r
5647                                         }\r
5648 \r
5649                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
5650                                         dom.remove(marker);\r
5651                                 }\r
5652                         }\r
5653 \r
5654                         // Setup some shorter versions\r
5655                         startContainer = rng.startContainer;\r
5656                         startOffset = rng.startOffset;\r
5657                         endContainer = rng.endContainer;\r
5658                         endOffset = rng.endOffset;\r
5659                         ieRng = body.createTextRange();\r
5660 \r
5661                         // If single element selection then try making a control selection out of it\r
5662                         if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {\r
5663                                 if (startOffset == endOffset - 1) {\r
5664                                         try {\r
5665                                                 ctrlRng = body.createControlRange();\r
5666                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);\r
5667                                                 ctrlRng.select();\r
5668                                                 return;\r
5669                                         } catch (ex) {\r
5670                                                 // Ignore\r
5671                                         }\r
5672                                 }\r
5673                         }\r
5674 \r
5675                         // Set start/end point of selection\r
5676                         setEndPoint(true);\r
5677                         setEndPoint();\r
5678 \r
5679                         // Select the new range and scroll it into view\r
5680                         ieRng.select();\r
5681                 };\r
5682 \r
5683                 // Expose range method\r
5684                 this.getRangeAt = getRange;\r
5685         };\r
5686 \r
5687         // Expose the selection object\r
5688         tinymce.dom.TridentSelection = Selection;\r
5689 })();\r
5690 \r
5691 \r
5692 /*\r
5693  * Sizzle CSS Selector Engine - v1.0\r
5694  *  Copyright 2009, The Dojo Foundation\r
5695  *  Released under the MIT, BSD, and GPL Licenses.\r
5696  *  More information: http://sizzlejs.com/\r
5697  */\r
5698 (function(){\r
5699 \r
5700 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,\r
5701         done = 0,\r
5702         toString = Object.prototype.toString,\r
5703         hasDuplicate = false,\r
5704         baseHasDuplicate = true;\r
5705 \r
5706 // Here we check if the JavaScript engine is using some sort of\r
5707 // optimization where it does not always call our comparision\r
5708 // function. If that is the case, discard the hasDuplicate value.\r
5709 //   Thus far that includes Google Chrome.\r
5710 [0, 0].sort(function(){\r
5711         baseHasDuplicate = false;\r
5712         return 0;\r
5713 });\r
5714 \r
5715 var Sizzle = function(selector, context, results, seed) {\r
5716         results = results || [];\r
5717         context = context || document;\r
5718 \r
5719         var origContext = context;\r
5720 \r
5721         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {\r
5722                 return [];\r
5723         }\r
5724         \r
5725         if ( !selector || typeof selector !== "string" ) {\r
5726                 return results;\r
5727         }\r
5728 \r
5729         var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),\r
5730                 soFar = selector, ret, cur, pop, i;\r
5731         \r
5732         // Reset the position of the chunker regexp (start from head)\r
5733         do {\r
5734                 chunker.exec("");\r
5735                 m = chunker.exec(soFar);\r
5736 \r
5737                 if ( m ) {\r
5738                         soFar = m[3];\r
5739                 \r
5740                         parts.push( m[1] );\r
5741                 \r
5742                         if ( m[2] ) {\r
5743                                 extra = m[3];\r
5744                                 break;\r
5745                         }\r
5746                 }\r
5747         } while ( m );\r
5748 \r
5749         if ( parts.length > 1 && origPOS.exec( selector ) ) {\r
5750                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {\r
5751                         set = posProcess( parts[0] + parts[1], context );\r
5752                 } else {\r
5753                         set = Expr.relative[ parts[0] ] ?\r
5754                                 [ context ] :\r
5755                                 Sizzle( parts.shift(), context );\r
5756 \r
5757                         while ( parts.length ) {\r
5758                                 selector = parts.shift();\r
5759 \r
5760                                 if ( Expr.relative[ selector ] ) {\r
5761                                         selector += parts.shift();\r
5762                                 }\r
5763                                 \r
5764                                 set = posProcess( selector, set );\r
5765                         }\r
5766                 }\r
5767         } else {\r
5768                 // Take a shortcut and set the context if the root selector is an ID\r
5769                 // (but not if it'll be faster if the inner selector is an ID)\r
5770                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&\r
5771                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {\r
5772                         ret = Sizzle.find( parts.shift(), context, contextXML );\r
5773                         context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];\r
5774                 }\r
5775 \r
5776                 if ( context ) {\r
5777                         ret = seed ?\r
5778                                 { expr: parts.pop(), set: makeArray(seed) } :\r
5779                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );\r
5780                         set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;\r
5781 \r
5782                         if ( parts.length > 0 ) {\r
5783                                 checkSet = makeArray(set);\r
5784                         } else {\r
5785                                 prune = false;\r
5786                         }\r
5787 \r
5788                         while ( parts.length ) {\r
5789                                 cur = parts.pop();\r
5790                                 pop = cur;\r
5791 \r
5792                                 if ( !Expr.relative[ cur ] ) {\r
5793                                         cur = "";\r
5794                                 } else {\r
5795                                         pop = parts.pop();\r
5796                                 }\r
5797 \r
5798                                 if ( pop == null ) {\r
5799                                         pop = context;\r
5800                                 }\r
5801 \r
5802                                 Expr.relative[ cur ]( checkSet, pop, contextXML );\r
5803                         }\r
5804                 } else {\r
5805                         checkSet = parts = [];\r
5806                 }\r
5807         }\r
5808 \r
5809         if ( !checkSet ) {\r
5810                 checkSet = set;\r
5811         }\r
5812 \r
5813         if ( !checkSet ) {\r
5814                 Sizzle.error( cur || selector );\r
5815         }\r
5816 \r
5817         if ( toString.call(checkSet) === "[object Array]" ) {\r
5818                 if ( !prune ) {\r
5819                         results.push.apply( results, checkSet );\r
5820                 } else if ( context && context.nodeType === 1 ) {\r
5821                         for ( i = 0; checkSet[i] != null; i++ ) {\r
5822                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {\r
5823                                         results.push( set[i] );\r
5824                                 }\r
5825                         }\r
5826                 } else {\r
5827                         for ( i = 0; checkSet[i] != null; i++ ) {\r
5828                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {\r
5829                                         results.push( set[i] );\r
5830                                 }\r
5831                         }\r
5832                 }\r
5833         } else {\r
5834                 makeArray( checkSet, results );\r
5835         }\r
5836 \r
5837         if ( extra ) {\r
5838                 Sizzle( extra, origContext, results, seed );\r
5839                 Sizzle.uniqueSort( results );\r
5840         }\r
5841 \r
5842         return results;\r
5843 };\r
5844 \r
5845 Sizzle.uniqueSort = function(results){\r
5846         if ( sortOrder ) {\r
5847                 hasDuplicate = baseHasDuplicate;\r
5848                 results.sort(sortOrder);\r
5849 \r
5850                 if ( hasDuplicate ) {\r
5851                         for ( var i = 1; i < results.length; i++ ) {\r
5852                                 if ( results[i] === results[i-1] ) {\r
5853                                         results.splice(i--, 1);\r
5854                                 }\r
5855                         }\r
5856                 }\r
5857         }\r
5858 \r
5859         return results;\r
5860 };\r
5861 \r
5862 Sizzle.matches = function(expr, set){\r
5863         return Sizzle(expr, null, null, set);\r
5864 };\r
5865 \r
5866 Sizzle.find = function(expr, context, isXML){\r
5867         var set;\r
5868 \r
5869         if ( !expr ) {\r
5870                 return [];\r
5871         }\r
5872 \r
5873         for ( var i = 0, l = Expr.order.length; i < l; i++ ) {\r
5874                 var type = Expr.order[i], match;\r
5875                 \r
5876                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {\r
5877                         var left = match[1];\r
5878                         match.splice(1,1);\r
5879 \r
5880                         if ( left.substr( left.length - 1 ) !== "\\" ) {\r
5881                                 match[1] = (match[1] || "").replace(/\\/g, "");\r
5882                                 set = Expr.find[ type ]( match, context, isXML );\r
5883                                 if ( set != null ) {\r
5884                                         expr = expr.replace( Expr.match[ type ], "" );\r
5885                                         break;\r
5886                                 }\r
5887                         }\r
5888                 }\r
5889         }\r
5890 \r
5891         if ( !set ) {\r
5892                 set = context.getElementsByTagName("*");\r
5893         }\r
5894 \r
5895         return {set: set, expr: expr};\r
5896 };\r
5897 \r
5898 Sizzle.filter = function(expr, set, inplace, not){\r
5899         var old = expr, result = [], curLoop = set, match, anyFound,\r
5900                 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);\r
5901 \r
5902         while ( expr && set.length ) {\r
5903                 for ( var type in Expr.filter ) {\r
5904                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {\r
5905                                 var filter = Expr.filter[ type ], found, item, left = match[1];\r
5906                                 anyFound = false;\r
5907 \r
5908                                 match.splice(1,1);\r
5909 \r
5910                                 if ( left.substr( left.length - 1 ) === "\\" ) {\r
5911                                         continue;\r
5912                                 }\r
5913 \r
5914                                 if ( curLoop === result ) {\r
5915                                         result = [];\r
5916                                 }\r
5917 \r
5918                                 if ( Expr.preFilter[ type ] ) {\r
5919                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );\r
5920 \r
5921                                         if ( !match ) {\r
5922                                                 anyFound = found = true;\r
5923                                         } else if ( match === true ) {\r
5924                                                 continue;\r
5925                                         }\r
5926                                 }\r
5927 \r
5928                                 if ( match ) {\r
5929                                         for ( var i = 0; (item = curLoop[i]) != null; i++ ) {\r
5930                                                 if ( item ) {\r
5931                                                         found = filter( item, match, i, curLoop );\r
5932                                                         var pass = not ^ !!found;\r
5933 \r
5934                                                         if ( inplace && found != null ) {\r
5935                                                                 if ( pass ) {\r
5936                                                                         anyFound = true;\r
5937                                                                 } else {\r
5938                                                                         curLoop[i] = false;\r
5939                                                                 }\r
5940                                                         } else if ( pass ) {\r
5941                                                                 result.push( item );\r
5942                                                                 anyFound = true;\r
5943                                                         }\r
5944                                                 }\r
5945                                         }\r
5946                                 }\r
5947 \r
5948                                 if ( found !== undefined ) {\r
5949                                         if ( !inplace ) {\r
5950                                                 curLoop = result;\r
5951                                         }\r
5952 \r
5953                                         expr = expr.replace( Expr.match[ type ], "" );\r
5954 \r
5955                                         if ( !anyFound ) {\r
5956                                                 return [];\r
5957                                         }\r
5958 \r
5959                                         break;\r
5960                                 }\r
5961                         }\r
5962                 }\r
5963 \r
5964                 // Improper expression\r
5965                 if ( expr === old ) {\r
5966                         if ( anyFound == null ) {\r
5967                                 Sizzle.error( expr );\r
5968                         } else {\r
5969                                 break;\r
5970                         }\r
5971                 }\r
5972 \r
5973                 old = expr;\r
5974         }\r
5975 \r
5976         return curLoop;\r
5977 };\r
5978 \r
5979 Sizzle.error = function( msg ) {\r
5980         throw "Syntax error, unrecognized expression: " + msg;\r
5981 };\r
5982 \r
5983 var Expr = Sizzle.selectors = {\r
5984         order: [ "ID", "NAME", "TAG" ],\r
5985         match: {\r
5986                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
5987                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
5988                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,\r
5989                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,\r
5990                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,\r
5991                 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,\r
5992                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,\r
5993                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/\r
5994         },\r
5995         leftMatch: {},\r
5996         attrMap: {\r
5997                 "class": "className",\r
5998                 "for": "htmlFor"\r
5999         },\r
6000         attrHandle: {\r
6001                 href: function(elem){\r
6002                         return elem.getAttribute("href");\r
6003                 }\r
6004         },\r
6005         relative: {\r
6006                 "+": function(checkSet, part){\r
6007                         var isPartStr = typeof part === "string",\r
6008                                 isTag = isPartStr && !/\W/.test(part),\r
6009                                 isPartStrNotTag = isPartStr && !isTag;\r
6010 \r
6011                         if ( isTag ) {\r
6012                                 part = part.toLowerCase();\r
6013                         }\r
6014 \r
6015                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {\r
6016                                 if ( (elem = checkSet[i]) ) {\r
6017                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}\r
6018 \r
6019                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?\r
6020                                                 elem || false :\r
6021                                                 elem === part;\r
6022                                 }\r
6023                         }\r
6024 \r
6025                         if ( isPartStrNotTag ) {\r
6026                                 Sizzle.filter( part, checkSet, true );\r
6027                         }\r
6028                 },\r
6029                 ">": function(checkSet, part){\r
6030                         var isPartStr = typeof part === "string",\r
6031                                 elem, i = 0, l = checkSet.length;\r
6032 \r
6033                         if ( isPartStr && !/\W/.test(part) ) {\r
6034                                 part = part.toLowerCase();\r
6035 \r
6036                                 for ( ; i < l; i++ ) {\r
6037                                         elem = checkSet[i];\r
6038                                         if ( elem ) {\r
6039                                                 var parent = elem.parentNode;\r
6040                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;\r
6041                                         }\r
6042                                 }\r
6043                         } else {\r
6044                                 for ( ; i < l; i++ ) {\r
6045                                         elem = checkSet[i];\r
6046                                         if ( elem ) {\r
6047                                                 checkSet[i] = isPartStr ?\r
6048                                                         elem.parentNode :\r
6049                                                         elem.parentNode === part;\r
6050                                         }\r
6051                                 }\r
6052 \r
6053                                 if ( isPartStr ) {\r
6054                                         Sizzle.filter( part, checkSet, true );\r
6055                                 }\r
6056                         }\r
6057                 },\r
6058                 "": function(checkSet, part, isXML){\r
6059                         var doneName = done++, checkFn = dirCheck, nodeCheck;\r
6060 \r
6061                         if ( typeof part === "string" && !/\W/.test(part) ) {\r
6062                                 part = part.toLowerCase();\r
6063                                 nodeCheck = part;\r
6064                                 checkFn = dirNodeCheck;\r
6065                         }\r
6066 \r
6067                         checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);\r
6068                 },\r
6069                 "~": function(checkSet, part, isXML){\r
6070                         var doneName = done++, checkFn = dirCheck, nodeCheck;\r
6071 \r
6072                         if ( typeof part === "string" && !/\W/.test(part) ) {\r
6073                                 part = part.toLowerCase();\r
6074                                 nodeCheck = part;\r
6075                                 checkFn = dirNodeCheck;\r
6076                         }\r
6077 \r
6078                         checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);\r
6079                 }\r
6080         },\r
6081         find: {\r
6082                 ID: function(match, context, isXML){\r
6083                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
6084                                 var m = context.getElementById(match[1]);\r
6085                                 return m ? [m] : [];\r
6086                         }\r
6087                 },\r
6088                 NAME: function(match, context){\r
6089                         if ( typeof context.getElementsByName !== "undefined" ) {\r
6090                                 var ret = [], results = context.getElementsByName(match[1]);\r
6091 \r
6092                                 for ( var i = 0, l = results.length; i < l; i++ ) {\r
6093                                         if ( results[i].getAttribute("name") === match[1] ) {\r
6094                                                 ret.push( results[i] );\r
6095                                         }\r
6096                                 }\r
6097 \r
6098                                 return ret.length === 0 ? null : ret;\r
6099                         }\r
6100                 },\r
6101                 TAG: function(match, context){\r
6102                         return context.getElementsByTagName(match[1]);\r
6103                 }\r
6104         },\r
6105         preFilter: {\r
6106                 CLASS: function(match, curLoop, inplace, result, not, isXML){\r
6107                         match = " " + match[1].replace(/\\/g, "") + " ";\r
6108 \r
6109                         if ( isXML ) {\r
6110                                 return match;\r
6111                         }\r
6112 \r
6113                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {\r
6114                                 if ( elem ) {\r
6115                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {\r
6116                                                 if ( !inplace ) {\r
6117                                                         result.push( elem );\r
6118                                                 }\r
6119                                         } else if ( inplace ) {\r
6120                                                 curLoop[i] = false;\r
6121                                         }\r
6122                                 }\r
6123                         }\r
6124 \r
6125                         return false;\r
6126                 },\r
6127                 ID: function(match){\r
6128                         return match[1].replace(/\\/g, "");\r
6129                 },\r
6130                 TAG: function(match, curLoop){\r
6131                         return match[1].toLowerCase();\r
6132                 },\r
6133                 CHILD: function(match){\r
6134                         if ( match[1] === "nth" ) {\r
6135                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'\r
6136                                 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(\r
6137                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||\r
6138                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);\r
6139 \r
6140                                 // calculate the numbers (first)n+(last) including if they are negative\r
6141                                 match[2] = (test[1] + (test[2] || 1)) - 0;\r
6142                                 match[3] = test[3] - 0;\r
6143                         }\r
6144 \r
6145                         // TODO: Move to normal caching system\r
6146                         match[0] = done++;\r
6147 \r
6148                         return match;\r
6149                 },\r
6150                 ATTR: function(match, curLoop, inplace, result, not, isXML){\r
6151                         var name = match[1].replace(/\\/g, "");\r
6152                         \r
6153                         if ( !isXML && Expr.attrMap[name] ) {\r
6154                                 match[1] = Expr.attrMap[name];\r
6155                         }\r
6156 \r
6157                         if ( match[2] === "~=" ) {\r
6158                                 match[4] = " " + match[4] + " ";\r
6159                         }\r
6160 \r
6161                         return match;\r
6162                 },\r
6163                 PSEUDO: function(match, curLoop, inplace, result, not){\r
6164                         if ( match[1] === "not" ) {\r
6165                                 // If we're dealing with a complex expression, or a simple one\r
6166                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {\r
6167                                         match[3] = Sizzle(match[3], null, null, curLoop);\r
6168                                 } else {\r
6169                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);\r
6170                                         if ( !inplace ) {\r
6171                                                 result.push.apply( result, ret );\r
6172                                         }\r
6173                                         return false;\r
6174                                 }\r
6175                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {\r
6176                                 return true;\r
6177                         }\r
6178                         \r
6179                         return match;\r
6180                 },\r
6181                 POS: function(match){\r
6182                         match.unshift( true );\r
6183                         return match;\r
6184                 }\r
6185         },\r
6186         filters: {\r
6187                 enabled: function(elem){\r
6188                         return elem.disabled === false && elem.type !== "hidden";\r
6189                 },\r
6190                 disabled: function(elem){\r
6191                         return elem.disabled === true;\r
6192                 },\r
6193                 checked: function(elem){\r
6194                         return elem.checked === true;\r
6195                 },\r
6196                 selected: function(elem){\r
6197                         // Accessing this property makes selected-by-default\r
6198                         // options in Safari work properly\r
6199                         elem.parentNode.selectedIndex;\r
6200                         return elem.selected === true;\r
6201                 },\r
6202                 parent: function(elem){\r
6203                         return !!elem.firstChild;\r
6204                 },\r
6205                 empty: function(elem){\r
6206                         return !elem.firstChild;\r
6207                 },\r
6208                 has: function(elem, i, match){\r
6209                         return !!Sizzle( match[3], elem ).length;\r
6210                 },\r
6211                 header: function(elem){\r
6212                         return (/h\d/i).test( elem.nodeName );\r
6213                 },\r
6214                 text: function(elem){\r
6215                         return "text" === elem.type;\r
6216                 },\r
6217                 radio: function(elem){\r
6218                         return "radio" === elem.type;\r
6219                 },\r
6220                 checkbox: function(elem){\r
6221                         return "checkbox" === elem.type;\r
6222                 },\r
6223                 file: function(elem){\r
6224                         return "file" === elem.type;\r
6225                 },\r
6226                 password: function(elem){\r
6227                         return "password" === elem.type;\r
6228                 },\r
6229                 submit: function(elem){\r
6230                         return "submit" === elem.type;\r
6231                 },\r
6232                 image: function(elem){\r
6233                         return "image" === elem.type;\r
6234                 },\r
6235                 reset: function(elem){\r
6236                         return "reset" === elem.type;\r
6237                 },\r
6238                 button: function(elem){\r
6239                         return "button" === elem.type || elem.nodeName.toLowerCase() === "button";\r
6240                 },\r
6241                 input: function(elem){\r
6242                         return (/input|select|textarea|button/i).test(elem.nodeName);\r
6243                 }\r
6244         },\r
6245         setFilters: {\r
6246                 first: function(elem, i){\r
6247                         return i === 0;\r
6248                 },\r
6249                 last: function(elem, i, match, array){\r
6250                         return i === array.length - 1;\r
6251                 },\r
6252                 even: function(elem, i){\r
6253                         return i % 2 === 0;\r
6254                 },\r
6255                 odd: function(elem, i){\r
6256                         return i % 2 === 1;\r
6257                 },\r
6258                 lt: function(elem, i, match){\r
6259                         return i < match[3] - 0;\r
6260                 },\r
6261                 gt: function(elem, i, match){\r
6262                         return i > match[3] - 0;\r
6263                 },\r
6264                 nth: function(elem, i, match){\r
6265                         return match[3] - 0 === i;\r
6266                 },\r
6267                 eq: function(elem, i, match){\r
6268                         return match[3] - 0 === i;\r
6269                 }\r
6270         },\r
6271         filter: {\r
6272                 PSEUDO: function(elem, match, i, array){\r
6273                         var name = match[1], filter = Expr.filters[ name ];\r
6274 \r
6275                         if ( filter ) {\r
6276                                 return filter( elem, i, match, array );\r
6277                         } else if ( name === "contains" ) {\r
6278                                 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;\r
6279                         } else if ( name === "not" ) {\r
6280                                 var not = match[3];\r
6281 \r
6282                                 for ( var j = 0, l = not.length; j < l; j++ ) {\r
6283                                         if ( not[j] === elem ) {\r
6284                                                 return false;\r
6285                                         }\r
6286                                 }\r
6287 \r
6288                                 return true;\r
6289                         } else {\r
6290                                 Sizzle.error( "Syntax error, unrecognized expression: " + name );\r
6291                         }\r
6292                 },\r
6293                 CHILD: function(elem, match){\r
6294                         var type = match[1], node = elem;\r
6295                         switch (type) {\r
6296                                 case 'only':\r
6297                                 case 'first':\r
6298                                         while ( (node = node.previousSibling) )  {\r
6299                                                 if ( node.nodeType === 1 ) { \r
6300                                                         return false; \r
6301                                                 }\r
6302                                         }\r
6303                                         if ( type === "first" ) { \r
6304                                                 return true; \r
6305                                         }\r
6306                                         node = elem;\r
6307                                 case 'last':\r
6308                                         while ( (node = node.nextSibling) )      {\r
6309                                                 if ( node.nodeType === 1 ) { \r
6310                                                         return false; \r
6311                                                 }\r
6312                                         }\r
6313                                         return true;\r
6314                                 case 'nth':\r
6315                                         var first = match[2], last = match[3];\r
6316 \r
6317                                         if ( first === 1 && last === 0 ) {\r
6318                                                 return true;\r
6319                                         }\r
6320                                         \r
6321                                         var doneName = match[0],\r
6322                                                 parent = elem.parentNode;\r
6323         \r
6324                                         if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {\r
6325                                                 var count = 0;\r
6326                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
6327                                                         if ( node.nodeType === 1 ) {\r
6328                                                                 node.nodeIndex = ++count;\r
6329                                                         }\r
6330                                                 } \r
6331                                                 parent.sizcache = doneName;\r
6332                                         }\r
6333                                         \r
6334                                         var diff = elem.nodeIndex - last;\r
6335                                         if ( first === 0 ) {\r
6336                                                 return diff === 0;\r
6337                                         } else {\r
6338                                                 return ( diff % first === 0 && diff / first >= 0 );\r
6339                                         }\r
6340                         }\r
6341                 },\r
6342                 ID: function(elem, match){\r
6343                         return elem.nodeType === 1 && elem.getAttribute("id") === match;\r
6344                 },\r
6345                 TAG: function(elem, match){\r
6346                         return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;\r
6347                 },\r
6348                 CLASS: function(elem, match){\r
6349                         return (" " + (elem.className || elem.getAttribute("class")) + " ")\r
6350                                 .indexOf( match ) > -1;\r
6351                 },\r
6352                 ATTR: function(elem, match){\r
6353                         var name = match[1],\r
6354                                 result = Expr.attrHandle[ name ] ?\r
6355                                         Expr.attrHandle[ name ]( elem ) :\r
6356                                         elem[ name ] != null ?\r
6357                                                 elem[ name ] :\r
6358                                                 elem.getAttribute( name ),\r
6359                                 value = result + "",\r
6360                                 type = match[2],\r
6361                                 check = match[4];\r
6362 \r
6363                         return result == null ?\r
6364                                 type === "!=" :\r
6365                                 type === "=" ?\r
6366                                 value === check :\r
6367                                 type === "*=" ?\r
6368                                 value.indexOf(check) >= 0 :\r
6369                                 type === "~=" ?\r
6370                                 (" " + value + " ").indexOf(check) >= 0 :\r
6371                                 !check ?\r
6372                                 value && result !== false :\r
6373                                 type === "!=" ?\r
6374                                 value !== check :\r
6375                                 type === "^=" ?\r
6376                                 value.indexOf(check) === 0 :\r
6377                                 type === "$=" ?\r
6378                                 value.substr(value.length - check.length) === check :\r
6379                                 type === "|=" ?\r
6380                                 value === check || value.substr(0, check.length + 1) === check + "-" :\r
6381                                 false;\r
6382                 },\r
6383                 POS: function(elem, match, i, array){\r
6384                         var name = match[2], filter = Expr.setFilters[ name ];\r
6385 \r
6386                         if ( filter ) {\r
6387                                 return filter( elem, i, match, array );\r
6388                         }\r
6389                 }\r
6390         }\r
6391 };\r
6392 \r
6393 var origPOS = Expr.match.POS,\r
6394         fescape = function(all, num){\r
6395                 return "\\" + (num - 0 + 1);\r
6396         };\r
6397 \r
6398 for ( var type in Expr.match ) {\r
6399         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );\r
6400         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );\r
6401 }\r
6402 \r
6403 var makeArray = function(array, results) {\r
6404         array = Array.prototype.slice.call( array, 0 );\r
6405 \r
6406         if ( results ) {\r
6407                 results.push.apply( results, array );\r
6408                 return results;\r
6409         }\r
6410         \r
6411         return array;\r
6412 };\r
6413 \r
6414 // Perform a simple check to determine if the browser is capable of\r
6415 // converting a NodeList to an array using builtin methods.\r
6416 // Also verifies that the returned array holds DOM nodes\r
6417 // (which is not the case in the Blackberry browser)\r
6418 try {\r
6419         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;\r
6420 \r
6421 // Provide a fallback method if it does not work\r
6422 } catch(e){\r
6423         makeArray = function(array, results) {\r
6424                 var ret = results || [], i = 0;\r
6425 \r
6426                 if ( toString.call(array) === "[object Array]" ) {\r
6427                         Array.prototype.push.apply( ret, array );\r
6428                 } else {\r
6429                         if ( typeof array.length === "number" ) {\r
6430                                 for ( var l = array.length; i < l; i++ ) {\r
6431                                         ret.push( array[i] );\r
6432                                 }\r
6433                         } else {\r
6434                                 for ( ; array[i]; i++ ) {\r
6435                                         ret.push( array[i] );\r
6436                                 }\r
6437                         }\r
6438                 }\r
6439 \r
6440                 return ret;\r
6441         };\r
6442 }\r
6443 \r
6444 var sortOrder;\r
6445 \r
6446 if ( document.documentElement.compareDocumentPosition ) {\r
6447         sortOrder = function( a, b ) {\r
6448                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {\r
6449                         if ( a == b ) {\r
6450                                 hasDuplicate = true;\r
6451                         }\r
6452                         return a.compareDocumentPosition ? -1 : 1;\r
6453                 }\r
6454 \r
6455                 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;\r
6456                 if ( ret === 0 ) {\r
6457                         hasDuplicate = true;\r
6458                 }\r
6459                 return ret;\r
6460         };\r
6461 } else if ( "sourceIndex" in document.documentElement ) {\r
6462         sortOrder = function( a, b ) {\r
6463                 if ( !a.sourceIndex || !b.sourceIndex ) {\r
6464                         if ( a == b ) {\r
6465                                 hasDuplicate = true;\r
6466                         }\r
6467                         return a.sourceIndex ? -1 : 1;\r
6468                 }\r
6469 \r
6470                 var ret = a.sourceIndex - b.sourceIndex;\r
6471                 if ( ret === 0 ) {\r
6472                         hasDuplicate = true;\r
6473                 }\r
6474                 return ret;\r
6475         };\r
6476 } else if ( document.createRange ) {\r
6477         sortOrder = function( a, b ) {\r
6478                 if ( !a.ownerDocument || !b.ownerDocument ) {\r
6479                         if ( a == b ) {\r
6480                                 hasDuplicate = true;\r
6481                         }\r
6482                         return a.ownerDocument ? -1 : 1;\r
6483                 }\r
6484 \r
6485                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();\r
6486                 aRange.setStart(a, 0);\r
6487                 aRange.setEnd(a, 0);\r
6488                 bRange.setStart(b, 0);\r
6489                 bRange.setEnd(b, 0);\r
6490                 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);\r
6491                 if ( ret === 0 ) {\r
6492                         hasDuplicate = true;\r
6493                 }\r
6494                 return ret;\r
6495         };\r
6496 }\r
6497 \r
6498 // Utility function for retreiving the text value of an array of DOM nodes\r
6499 Sizzle.getText = function( elems ) {\r
6500         var ret = "", elem;\r
6501 \r
6502         for ( var i = 0; elems[i]; i++ ) {\r
6503                 elem = elems[i];\r
6504 \r
6505                 // Get the text from text nodes and CDATA nodes\r
6506                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {\r
6507                         ret += elem.nodeValue;\r
6508 \r
6509                 // Traverse everything else, except comment nodes\r
6510                 } else if ( elem.nodeType !== 8 ) {\r
6511                         ret += Sizzle.getText( elem.childNodes );\r
6512                 }\r
6513         }\r
6514 \r
6515         return ret;\r
6516 };\r
6517 \r
6518 // Check to see if the browser returns elements by name when\r
6519 // querying by getElementById (and provide a workaround)\r
6520 (function(){\r
6521         // We're going to inject a fake input element with a specified name\r
6522         var form = document.createElement("div"),\r
6523                 id = "script" + (new Date()).getTime();\r
6524         form.innerHTML = "<a name='" + id + "'/>";\r
6525 \r
6526         // Inject it into the root element, check its status, and remove it quickly\r
6527         var root = document.documentElement;\r
6528         root.insertBefore( form, root.firstChild );\r
6529 \r
6530         // The workaround has to do additional checks after a getElementById\r
6531         // Which slows things down for other browsers (hence the branching)\r
6532         if ( document.getElementById( id ) ) {\r
6533                 Expr.find.ID = function(match, context, isXML){\r
6534                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
6535                                 var m = context.getElementById(match[1]);\r
6536                                 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];\r
6537                         }\r
6538                 };\r
6539 \r
6540                 Expr.filter.ID = function(elem, match){\r
6541                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");\r
6542                         return elem.nodeType === 1 && node && node.nodeValue === match;\r
6543                 };\r
6544         }\r
6545 \r
6546         root.removeChild( form );\r
6547         root = form = null; // release memory in IE\r
6548 })();\r
6549 \r
6550 (function(){\r
6551         // Check to see if the browser returns only elements\r
6552         // when doing getElementsByTagName("*")\r
6553 \r
6554         // Create a fake element\r
6555         var div = document.createElement("div");\r
6556         div.appendChild( document.createComment("") );\r
6557 \r
6558         // Make sure no comments are found\r
6559         if ( div.getElementsByTagName("*").length > 0 ) {\r
6560                 Expr.find.TAG = function(match, context){\r
6561                         var results = context.getElementsByTagName(match[1]);\r
6562 \r
6563                         // Filter out possible comments\r
6564                         if ( match[1] === "*" ) {\r
6565                                 var tmp = [];\r
6566 \r
6567                                 for ( var i = 0; results[i]; i++ ) {\r
6568                                         if ( results[i].nodeType === 1 ) {\r
6569                                                 tmp.push( results[i] );\r
6570                                         }\r
6571                                 }\r
6572 \r
6573                                 results = tmp;\r
6574                         }\r
6575 \r
6576                         return results;\r
6577                 };\r
6578         }\r
6579 \r
6580         // Check to see if an attribute returns normalized href attributes\r
6581         div.innerHTML = "<a href='#'></a>";\r
6582         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&\r
6583                         div.firstChild.getAttribute("href") !== "#" ) {\r
6584                 Expr.attrHandle.href = function(elem){\r
6585                         return elem.getAttribute("href", 2);\r
6586                 };\r
6587         }\r
6588 \r
6589         div = null; // release memory in IE\r
6590 })();\r
6591 \r
6592 if ( document.querySelectorAll ) {\r
6593         (function(){\r
6594                 var oldSizzle = Sizzle, div = document.createElement("div");\r
6595                 div.innerHTML = "<p class='TEST'></p>";\r
6596 \r
6597                 // Safari can't handle uppercase or unicode characters when\r
6598                 // in quirks mode.\r
6599                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {\r
6600                         return;\r
6601                 }\r
6602         \r
6603                 Sizzle = function(query, context, extra, seed){\r
6604                         context = context || document;\r
6605 \r
6606                         // Only use querySelectorAll on non-XML documents\r
6607                         // (ID selectors don't work in non-HTML documents)\r
6608                         if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {\r
6609                                 try {\r
6610                                         return makeArray( context.querySelectorAll(query), extra );\r
6611                                 } catch(e){}\r
6612                         }\r
6613                 \r
6614                         return oldSizzle(query, context, extra, seed);\r
6615                 };\r
6616 \r
6617                 for ( var prop in oldSizzle ) {\r
6618                         Sizzle[ prop ] = oldSizzle[ prop ];\r
6619                 }\r
6620 \r
6621                 div = null; // release memory in IE\r
6622         })();\r
6623 }\r
6624 \r
6625 (function(){\r
6626         var div = document.createElement("div");\r
6627 \r
6628         div.innerHTML = "<div class='test e'></div><div class='test'></div>";\r
6629 \r
6630         // Opera can't find a second classname (in 9.6)\r
6631         // Also, make sure that getElementsByClassName actually exists\r
6632         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {\r
6633                 return;\r
6634         }\r
6635 \r
6636         // Safari caches class attributes, doesn't catch changes (in 3.2)\r
6637         div.lastChild.className = "e";\r
6638 \r
6639         if ( div.getElementsByClassName("e").length === 1 ) {\r
6640                 return;\r
6641         }\r
6642         \r
6643         Expr.order.splice(1, 0, "CLASS");\r
6644         Expr.find.CLASS = function(match, context, isXML) {\r
6645                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {\r
6646                         return context.getElementsByClassName(match[1]);\r
6647                 }\r
6648         };\r
6649 \r
6650         div = null; // release memory in IE\r
6651 })();\r
6652 \r
6653 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
6654         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
6655                 var elem = checkSet[i];\r
6656                 if ( elem ) {\r
6657                         elem = elem[dir];\r
6658                         var match = false;\r
6659 \r
6660                         while ( elem ) {\r
6661                                 if ( elem.sizcache === doneName ) {\r
6662                                         match = checkSet[elem.sizset];\r
6663                                         break;\r
6664                                 }\r
6665 \r
6666                                 if ( elem.nodeType === 1 && !isXML ){\r
6667                                         elem.sizcache = doneName;\r
6668                                         elem.sizset = i;\r
6669                                 }\r
6670 \r
6671                                 if ( elem.nodeName.toLowerCase() === cur ) {\r
6672                                         match = elem;\r
6673                                         break;\r
6674                                 }\r
6675 \r
6676                                 elem = elem[dir];\r
6677                         }\r
6678 \r
6679                         checkSet[i] = match;\r
6680                 }\r
6681         }\r
6682 }\r
6683 \r
6684 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
6685         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
6686                 var elem = checkSet[i];\r
6687                 if ( elem ) {\r
6688                         elem = elem[dir];\r
6689                         var match = false;\r
6690 \r
6691                         while ( elem ) {\r
6692                                 if ( elem.sizcache === doneName ) {\r
6693                                         match = checkSet[elem.sizset];\r
6694                                         break;\r
6695                                 }\r
6696 \r
6697                                 if ( elem.nodeType === 1 ) {\r
6698                                         if ( !isXML ) {\r
6699                                                 elem.sizcache = doneName;\r
6700                                                 elem.sizset = i;\r
6701                                         }\r
6702                                         if ( typeof cur !== "string" ) {\r
6703                                                 if ( elem === cur ) {\r
6704                                                         match = true;\r
6705                                                         break;\r
6706                                                 }\r
6707 \r
6708                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {\r
6709                                                 match = elem;\r
6710                                                 break;\r
6711                                         }\r
6712                                 }\r
6713 \r
6714                                 elem = elem[dir];\r
6715                         }\r
6716 \r
6717                         checkSet[i] = match;\r
6718                 }\r
6719         }\r
6720 }\r
6721 \r
6722 Sizzle.contains = document.compareDocumentPosition ? function(a, b){\r
6723         return !!(a.compareDocumentPosition(b) & 16);\r
6724 } : function(a, b){\r
6725         return a !== b && (a.contains ? a.contains(b) : true);\r
6726 };\r
6727 \r
6728 Sizzle.isXML = function(elem){\r
6729         // documentElement is verified for cases where it doesn't yet exist\r
6730         // (such as loading iframes in IE - #4833) \r
6731         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;\r
6732         return documentElement ? documentElement.nodeName !== "HTML" : false;\r
6733 };\r
6734 \r
6735 var posProcess = function(selector, context){\r
6736         var tmpSet = [], later = "", match,\r
6737                 root = context.nodeType ? [context] : context;\r
6738 \r
6739         // Position selectors must be done after the filter\r
6740         // And so must :not(positional) so we move all PSEUDOs to the end\r
6741         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {\r
6742                 later += match[0];\r
6743                 selector = selector.replace( Expr.match.PSEUDO, "" );\r
6744         }\r
6745 \r
6746         selector = Expr.relative[selector] ? selector + "*" : selector;\r
6747 \r
6748         for ( var i = 0, l = root.length; i < l; i++ ) {\r
6749                 Sizzle( selector, root[i], tmpSet );\r
6750         }\r
6751 \r
6752         return Sizzle.filter( later, tmpSet );\r
6753 };\r
6754 \r
6755 // EXPOSE\r
6756 \r
6757 window.tinymce.dom.Sizzle = Sizzle;\r
6758 \r
6759 })();\r
6760 \r
6761 \r
6762 (function(tinymce) {\r
6763         // Shorten names\r
6764         var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;\r
6765 \r
6766         tinymce.create('tinymce.dom.EventUtils', {\r
6767                 EventUtils : function() {\r
6768                         this.inits = [];\r
6769                         this.events = [];\r
6770                 },\r
6771 \r
6772                 add : function(o, n, f, s) {\r
6773                         var cb, t = this, el = t.events, r;\r
6774 \r
6775                         if (n instanceof Array) {\r
6776                                 r = [];\r
6777 \r
6778                                 each(n, function(n) {\r
6779                                         r.push(t.add(o, n, f, s));\r
6780                                 });\r
6781 \r
6782                                 return r;\r
6783                         }\r
6784 \r
6785                         // Handle array\r
6786                         if (o && o.hasOwnProperty && o instanceof Array) {\r
6787                                 r = [];\r
6788 \r
6789                                 each(o, function(o) {\r
6790                                         o = DOM.get(o);\r
6791                                         r.push(t.add(o, n, f, s));\r
6792                                 });\r
6793 \r
6794                                 return r;\r
6795                         }\r
6796 \r
6797                         o = DOM.get(o);\r
6798 \r
6799                         if (!o)\r
6800                                 return;\r
6801 \r
6802                         // Setup event callback\r
6803                         cb = function(e) {\r
6804                                 // Is all events disabled\r
6805                                 if (t.disabled)\r
6806                                         return;\r
6807 \r
6808                                 e = e || window.event;\r
6809 \r
6810                                 // Patch in target, preventDefault and stopPropagation in IE it's W3C valid\r
6811                                 if (e && isIE) {\r
6812                                         if (!e.target)\r
6813                                                 e.target = e.srcElement;\r
6814 \r
6815                                         // Patch in preventDefault, stopPropagation methods for W3C compatibility\r
6816                                         tinymce.extend(e, t._stoppers);\r
6817                                 }\r
6818 \r
6819                                 if (!s)\r
6820                                         return f(e);\r
6821 \r
6822                                 return f.call(s, e);\r
6823                         };\r
6824 \r
6825                         if (n == 'unload') {\r
6826                                 tinymce.unloads.unshift({func : cb});\r
6827                                 return cb;\r
6828                         }\r
6829 \r
6830                         if (n == 'init') {\r
6831                                 if (t.domLoaded)\r
6832                                         cb();\r
6833                                 else\r
6834                                         t.inits.push(cb);\r
6835 \r
6836                                 return cb;\r
6837                         }\r
6838 \r
6839                         // Store away listener reference\r
6840                         el.push({\r
6841                                 obj : o,\r
6842                                 name : n,\r
6843                                 func : f,\r
6844                                 cfunc : cb,\r
6845                                 scope : s\r
6846                         });\r
6847 \r
6848                         t._add(o, n, cb);\r
6849 \r
6850                         return f;\r
6851                 },\r
6852 \r
6853                 remove : function(o, n, f) {\r
6854                         var t = this, a = t.events, s = false, r;\r
6855 \r
6856                         // Handle array\r
6857                         if (o && o.hasOwnProperty && o instanceof Array) {\r
6858                                 r = [];\r
6859 \r
6860                                 each(o, function(o) {\r
6861                                         o = DOM.get(o);\r
6862                                         r.push(t.remove(o, n, f));\r
6863                                 });\r
6864 \r
6865                                 return r;\r
6866                         }\r
6867 \r
6868                         o = DOM.get(o);\r
6869 \r
6870                         each(a, function(e, i) {\r
6871                                 if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {\r
6872                                         a.splice(i, 1);\r
6873                                         t._remove(o, n, e.cfunc);\r
6874                                         s = true;\r
6875                                         return false;\r
6876                                 }\r
6877                         });\r
6878 \r
6879                         return s;\r
6880                 },\r
6881 \r
6882                 clear : function(o) {\r
6883                         var t = this, a = t.events, i, e;\r
6884 \r
6885                         if (o) {\r
6886                                 o = DOM.get(o);\r
6887 \r
6888                                 for (i = a.length - 1; i >= 0; i--) {\r
6889                                         e = a[i];\r
6890 \r
6891                                         if (e.obj === o) {\r
6892                                                 t._remove(e.obj, e.name, e.cfunc);\r
6893                                                 e.obj = e.cfunc = null;\r
6894                                                 a.splice(i, 1);\r
6895                                         }\r
6896                                 }\r
6897                         }\r
6898                 },\r
6899 \r
6900                 cancel : function(e) {\r
6901                         if (!e)\r
6902                                 return false;\r
6903 \r
6904                         this.stop(e);\r
6905 \r
6906                         return this.prevent(e);\r
6907                 },\r
6908 \r
6909                 stop : function(e) {\r
6910                         if (e.stopPropagation)\r
6911                                 e.stopPropagation();\r
6912                         else\r
6913                                 e.cancelBubble = true;\r
6914 \r
6915                         return false;\r
6916                 },\r
6917 \r
6918                 prevent : function(e) {\r
6919                         if (e.preventDefault)\r
6920                                 e.preventDefault();\r
6921                         else\r
6922                                 e.returnValue = false;\r
6923 \r
6924                         return false;\r
6925                 },\r
6926 \r
6927                 destroy : function() {\r
6928                         var t = this;\r
6929 \r
6930                         each(t.events, function(e, i) {\r
6931                                 t._remove(e.obj, e.name, e.cfunc);\r
6932                                 e.obj = e.cfunc = null;\r
6933                         });\r
6934 \r
6935                         t.events = [];\r
6936                         t = null;\r
6937                 },\r
6938 \r
6939                 _add : function(o, n, f) {\r
6940                         if (o.attachEvent)\r
6941                                 o.attachEvent('on' + n, f);\r
6942                         else if (o.addEventListener)\r
6943                                 o.addEventListener(n, f, false);\r
6944                         else\r
6945                                 o['on' + n] = f;\r
6946                 },\r
6947 \r
6948                 _remove : function(o, n, f) {\r
6949                         if (o) {\r
6950                                 try {\r
6951                                         if (o.detachEvent)\r
6952                                                 o.detachEvent('on' + n, f);\r
6953                                         else if (o.removeEventListener)\r
6954                                                 o.removeEventListener(n, f, false);\r
6955                                         else\r
6956                                                 o['on' + n] = null;\r
6957                                 } catch (ex) {\r
6958                                         // Might fail with permission denined on IE so we just ignore that\r
6959                                 }\r
6960                         }\r
6961                 },\r
6962 \r
6963                 _pageInit : function(win) {\r
6964                         var t = this;\r
6965 \r
6966                         // Keep it from running more than once\r
6967                         if (t.domLoaded)\r
6968                                 return;\r
6969 \r
6970                         t.domLoaded = true;\r
6971 \r
6972                         each(t.inits, function(c) {\r
6973                                 c();\r
6974                         });\r
6975 \r
6976                         t.inits = [];\r
6977                 },\r
6978 \r
6979                 _wait : function(win) {\r
6980                         var t = this, doc = win.document;\r
6981 \r
6982                         // No need since the document is already loaded\r
6983                         if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {\r
6984                                 t.domLoaded = 1;\r
6985                                 return;\r
6986                         }\r
6987 \r
6988                         // Use IE method\r
6989                         if (doc.attachEvent) {\r
6990                                 doc.attachEvent("onreadystatechange", function() {\r
6991                                         if (doc.readyState === "complete") {\r
6992                                                 doc.detachEvent("onreadystatechange", arguments.callee);\r
6993                                                 t._pageInit(win);\r
6994                                         }\r
6995                                 });\r
6996 \r
6997                                 if (doc.documentElement.doScroll && win == win.top) {\r
6998                                         (function() {\r
6999                                                 if (t.domLoaded)\r
7000                                                         return;\r
7001 \r
7002                                                 try {\r
7003                                                         // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.\r
7004                                                         // http://javascript.nwbox.com/IEContentLoaded/\r
7005                                                         doc.documentElement.doScroll("left");\r
7006                                                 } catch (ex) {\r
7007                                                         setTimeout(arguments.callee, 0);\r
7008                                                         return;\r
7009                                                 }\r
7010 \r
7011                                                 t._pageInit(win);\r
7012                                         })();\r
7013                                 }\r
7014                         } else if (doc.addEventListener) {\r
7015                                 t._add(win, 'DOMContentLoaded', function() {\r
7016                                         t._pageInit(win);\r
7017                                 });\r
7018                         }\r
7019 \r
7020                         t._add(win, 'load', function() {\r
7021                                 t._pageInit(win);\r
7022                         });\r
7023                 },\r
7024 \r
7025                 _stoppers : {\r
7026                         preventDefault : function() {\r
7027                                 this.returnValue = false;\r
7028                         },\r
7029 \r
7030                         stopPropagation : function() {\r
7031                                 this.cancelBubble = true;\r
7032                         }\r
7033                 }\r
7034         });\r
7035 \r
7036         Event = tinymce.dom.Event = new tinymce.dom.EventUtils();\r
7037 \r
7038         // Dispatch DOM content loaded event for IE and Safari\r
7039         Event._wait(window);\r
7040 \r
7041         tinymce.addUnload(function() {\r
7042                 Event.destroy();\r
7043         });\r
7044 })(tinymce);\r
7045 \r
7046 (function(tinymce) {\r
7047         tinymce.dom.Element = function(id, settings) {\r
7048                 var t = this, dom, el;\r
7049 \r
7050                 t.settings = settings = settings || {};\r
7051                 t.id = id;\r
7052                 t.dom = dom = settings.dom || tinymce.DOM;\r
7053 \r
7054                 // Only IE leaks DOM references, this is a lot faster\r
7055                 if (!tinymce.isIE)\r
7056                         el = dom.get(t.id);\r
7057 \r
7058                 tinymce.each(\r
7059                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + \r
7060                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + \r
7061                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + \r
7062                                 'isHidden,setHTML,get').split(/,/)\r
7063                         , function(k) {\r
7064                                 t[k] = function() {\r
7065                                         var a = [id], i;\r
7066 \r
7067                                         for (i = 0; i < arguments.length; i++)\r
7068                                                 a.push(arguments[i]);\r
7069 \r
7070                                         a = dom[k].apply(dom, a);\r
7071                                         t.update(k);\r
7072 \r
7073                                         return a;\r
7074                                 };\r
7075                 });\r
7076 \r
7077                 tinymce.extend(t, {\r
7078                         on : function(n, f, s) {\r
7079                                 return tinymce.dom.Event.add(t.id, n, f, s);\r
7080                         },\r
7081 \r
7082                         getXY : function() {\r
7083                                 return {\r
7084                                         x : parseInt(t.getStyle('left')),\r
7085                                         y : parseInt(t.getStyle('top'))\r
7086                                 };\r
7087                         },\r
7088 \r
7089                         getSize : function() {\r
7090                                 var n = dom.get(t.id);\r
7091 \r
7092                                 return {\r
7093                                         w : parseInt(t.getStyle('width') || n.clientWidth),\r
7094                                         h : parseInt(t.getStyle('height') || n.clientHeight)\r
7095                                 };\r
7096                         },\r
7097 \r
7098                         moveTo : function(x, y) {\r
7099                                 t.setStyles({left : x, top : y});\r
7100                         },\r
7101 \r
7102                         moveBy : function(x, y) {\r
7103                                 var p = t.getXY();\r
7104 \r
7105                                 t.moveTo(p.x + x, p.y + y);\r
7106                         },\r
7107 \r
7108                         resizeTo : function(w, h) {\r
7109                                 t.setStyles({width : w, height : h});\r
7110                         },\r
7111 \r
7112                         resizeBy : function(w, h) {\r
7113                                 var s = t.getSize();\r
7114 \r
7115                                 t.resizeTo(s.w + w, s.h + h);\r
7116                         },\r
7117 \r
7118                         update : function(k) {\r
7119                                 var b;\r
7120 \r
7121                                 if (tinymce.isIE6 && settings.blocker) {\r
7122                                         k = k || '';\r
7123 \r
7124                                         // Ignore getters\r
7125                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)\r
7126                                                 return;\r
7127 \r
7128                                         // Remove blocker on remove\r
7129                                         if (k == 'remove') {\r
7130                                                 dom.remove(t.blocker);\r
7131                                                 return;\r
7132                                         }\r
7133 \r
7134                                         if (!t.blocker) {\r
7135                                                 t.blocker = dom.uniqueId();\r
7136                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});\r
7137                                                 dom.setStyle(b, 'opacity', 0);\r
7138                                         } else\r
7139                                                 b = dom.get(t.blocker);\r
7140 \r
7141                                         dom.setStyles(b, {\r
7142                                                 left : t.getStyle('left', 1),\r
7143                                                 top : t.getStyle('top', 1),\r
7144                                                 width : t.getStyle('width', 1),\r
7145                                                 height : t.getStyle('height', 1),\r
7146                                                 display : t.getStyle('display', 1),\r
7147                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1\r
7148                                         });\r
7149                                 }\r
7150                         }\r
7151                 });\r
7152         };\r
7153 })(tinymce);\r
7154 \r
7155 (function(tinymce) {\r
7156         function trimNl(s) {\r
7157                 return s.replace(/[\n\r]+/g, '');\r
7158         };\r
7159 \r
7160         // Shorten names\r
7161         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;\r
7162 \r
7163         tinymce.create('tinymce.dom.Selection', {\r
7164                 Selection : function(dom, win, serializer) {\r
7165                         var t = this;\r
7166 \r
7167                         t.dom = dom;\r
7168                         t.win = win;\r
7169                         t.serializer = serializer;\r
7170 \r
7171                         // Add events\r
7172                         each([\r
7173                                 'onBeforeSetContent',\r
7174 \r
7175                                 'onBeforeGetContent',\r
7176 \r
7177                                 'onSetContent',\r
7178 \r
7179                                 'onGetContent'\r
7180                         ], function(e) {\r
7181                                 t[e] = new tinymce.util.Dispatcher(t);\r
7182                         });\r
7183 \r
7184                         // No W3C Range support\r
7185                         if (!t.win.getSelection)\r
7186                                 t.tridentSel = new tinymce.dom.TridentSelection(t);\r
7187 \r
7188                         if (tinymce.isIE && dom.boxModel)\r
7189                                 this._fixIESelection();\r
7190 \r
7191                         // Prevent leaks\r
7192                         tinymce.addUnload(t.destroy, t);\r
7193                 },\r
7194 \r
7195                 getContent : function(s) {\r
7196                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;\r
7197 \r
7198                         s = s || {};\r
7199                         wb = wa = '';\r
7200                         s.get = true;\r
7201                         s.format = s.format || 'html';\r
7202                         s.forced_root_block = '';\r
7203                         t.onBeforeGetContent.dispatch(t, s);\r
7204 \r
7205                         if (s.format == 'text')\r
7206                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));\r
7207 \r
7208                         if (r.cloneContents) {\r
7209                                 n = r.cloneContents();\r
7210 \r
7211                                 if (n)\r
7212                                         e.appendChild(n);\r
7213                         } else if (is(r.item) || is(r.htmlText))\r
7214                                 e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;\r
7215                         else\r
7216                                 e.innerHTML = r.toString();\r
7217 \r
7218                         // Keep whitespace before and after\r
7219                         if (/^\s/.test(e.innerHTML))\r
7220                                 wb = ' ';\r
7221 \r
7222                         if (/\s+$/.test(e.innerHTML))\r
7223                                 wa = ' ';\r
7224 \r
7225                         s.getInner = true;\r
7226 \r
7227                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;\r
7228                         t.onGetContent.dispatch(t, s);\r
7229 \r
7230                         return s.content;\r
7231                 },\r
7232 \r
7233                 setContent : function(content, args) {\r
7234                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;\r
7235 \r
7236                         args = args || {format : 'html'};\r
7237                         args.set = true;\r
7238                         content = args.content = content;\r
7239 \r
7240                         // Dispatch before set content event\r
7241                         if (!args.no_events)\r
7242                                 self.onBeforeSetContent.dispatch(self, args);\r
7243 \r
7244                         content = args.content;\r
7245 \r
7246                         if (rng.insertNode) {\r
7247                                 // Make caret marker since insertNode places the caret in the beginning of text after insert\r
7248                                 content += '<span id="__caret">_</span>';\r
7249 \r
7250                                 // Delete and insert new node\r
7251                                 if (rng.startContainer == doc && rng.endContainer == doc) {\r
7252                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents\r
7253                                         doc.body.innerHTML = content;\r
7254                                 } else {\r
7255                                         rng.deleteContents();\r
7256 \r
7257                                         if (doc.body.childNodes.length == 0) {\r
7258                                                 doc.body.innerHTML = content;\r
7259                                         } else {\r
7260                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges\r
7261                                                 if (rng.createContextualFragment) {\r
7262                                                         rng.insertNode(rng.createContextualFragment(content));\r
7263                                                 } else {\r
7264                                                         // Fake createContextualFragment call in IE 9\r
7265                                                         frag = doc.createDocumentFragment();\r
7266                                                         temp = doc.createElement('div');\r
7267 \r
7268                                                         frag.appendChild(temp);\r
7269                                                         temp.outerHTML = content;\r
7270 \r
7271                                                         rng.insertNode(frag);\r
7272                                                 }\r
7273                                         }\r
7274                                 }\r
7275 \r
7276                                 // Move to caret marker\r
7277                                 caretNode = self.dom.get('__caret');\r
7278 \r
7279                                 // Make sure we wrap it compleatly, Opera fails with a simple select call\r
7280                                 rng = doc.createRange();\r
7281                                 rng.setStartBefore(caretNode);\r
7282                                 rng.setEndBefore(caretNode);\r
7283                                 self.setRng(rng);\r
7284 \r
7285                                 // Remove the caret position\r
7286                                 self.dom.remove('__caret');\r
7287 \r
7288                                 try {\r
7289                                         self.setRng(rng);\r
7290                                 } catch (ex) {\r
7291                                         // Might fail on Opera for some odd reason\r
7292                                 }\r
7293                         } else {\r
7294                                 if (rng.item) {\r
7295                                         // Delete content and get caret text selection\r
7296                                         doc.execCommand('Delete', false, null);\r
7297                                         rng = self.getRng();\r
7298                                 }\r
7299 \r
7300                                 rng.pasteHTML(content);\r
7301                         }\r
7302 \r
7303                         // Dispatch set content event\r
7304                         if (!args.no_events)\r
7305                                 self.onSetContent.dispatch(self, args);\r
7306                 },\r
7307 \r
7308                 getStart : function() {\r
7309                         var rng = this.getRng(), startElement, parentElement, checkRng, node;\r
7310 \r
7311                         if (rng.duplicate || rng.item) {\r
7312                                 // Control selection, return first item\r
7313                                 if (rng.item)\r
7314                                         return rng.item(0);\r
7315 \r
7316                                 // Get start element\r
7317                                 checkRng = rng.duplicate();\r
7318                                 checkRng.collapse(1);\r
7319                                 startElement = checkRng.parentElement();\r
7320 \r
7321                                 // Check if range parent is inside the start element, then return the inner parent element\r
7322                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element\r
7323                                 parentElement = node = rng.parentElement();\r
7324                                 while (node = node.parentNode) {\r
7325                                         if (node == startElement) {\r
7326                                                 startElement = parentElement;\r
7327                                                 break;\r
7328                                         }\r
7329                                 }\r
7330 \r
7331                                 return startElement;\r
7332                         } else {\r
7333                                 startElement = rng.startContainer;\r
7334 \r
7335                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())\r
7336                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];\r
7337 \r
7338                                 if (startElement && startElement.nodeType == 3)\r
7339                                         return startElement.parentNode;\r
7340 \r
7341                                 return startElement;\r
7342                         }\r
7343                 },\r
7344 \r
7345                 getEnd : function() {\r
7346                         var t = this, r = t.getRng(), e, eo;\r
7347 \r
7348                         if (r.duplicate || r.item) {\r
7349                                 if (r.item)\r
7350                                         return r.item(0);\r
7351 \r
7352                                 r = r.duplicate();\r
7353                                 r.collapse(0);\r
7354                                 e = r.parentElement();\r
7355 \r
7356                                 if (e && e.nodeName == 'BODY')\r
7357                                         return e.lastChild || e;\r
7358 \r
7359                                 return e;\r
7360                         } else {\r
7361                                 e = r.endContainer;\r
7362                                 eo = r.endOffset;\r
7363 \r
7364                                 if (e.nodeType == 1 && e.hasChildNodes())\r
7365                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];\r
7366 \r
7367                                 if (e && e.nodeType == 3)\r
7368                                         return e.parentNode;\r
7369 \r
7370                                 return e;\r
7371                         }\r
7372                 },\r
7373 \r
7374                 getBookmark : function(type, normalized) {\r
7375                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;\r
7376 \r
7377                         function findIndex(name, element) {\r
7378                                 var index = 0;\r
7379 \r
7380                                 each(dom.select(name), function(node, i) {\r
7381                                         if (node == element)\r
7382                                                 index = i;\r
7383                                 });\r
7384 \r
7385                                 return index;\r
7386                         };\r
7387 \r
7388                         if (type == 2) {\r
7389                                 function getLocation() {\r
7390                                         var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};\r
7391 \r
7392                                         function getPoint(rng, start) {\r
7393                                                 var container = rng[start ? 'startContainer' : 'endContainer'],\r
7394                                                         offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;\r
7395 \r
7396                                                 if (container.nodeType == 3) {\r
7397                                                         if (normalized) {\r
7398                                                                 for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)\r
7399                                                                         offset += node.nodeValue.length;\r
7400                                                         }\r
7401 \r
7402                                                         point.push(offset);\r
7403                                                 } else {\r
7404                                                         childNodes = container.childNodes;\r
7405 \r
7406                                                         if (offset >= childNodes.length && childNodes.length) {\r
7407                                                                 after = 1;\r
7408                                                                 offset = Math.max(0, childNodes.length - 1);\r
7409                                                         }\r
7410 \r
7411                                                         point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);\r
7412                                                 }\r
7413 \r
7414                                                 for (; container && container != root; container = container.parentNode)\r
7415                                                         point.push(t.dom.nodeIndex(container, normalized));\r
7416 \r
7417                                                 return point;\r
7418                                         };\r
7419 \r
7420                                         bookmark.start = getPoint(rng, true);\r
7421 \r
7422                                         if (!t.isCollapsed())\r
7423                                                 bookmark.end = getPoint(rng);\r
7424 \r
7425                                         return bookmark;\r
7426                                 };\r
7427 \r
7428                                 if (t.tridentSel)\r
7429                                         return t.tridentSel.getBookmark(type);\r
7430 \r
7431                                 return getLocation();\r
7432                         }\r
7433 \r
7434                         // Handle simple range\r
7435                         if (type)\r
7436                                 return {rng : t.getRng()};\r
7437 \r
7438                         rng = t.getRng();\r
7439                         id = dom.uniqueId();\r
7440                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();\r
7441                         styles = 'overflow:hidden;line-height:0px';\r
7442 \r
7443                         // Explorer method\r
7444                         if (rng.duplicate || rng.item) {\r
7445                                 // Text selection\r
7446                                 if (!rng.item) {\r
7447                                         rng2 = rng.duplicate();\r
7448 \r
7449                                         try {\r
7450                                                 // Insert start marker\r
7451                                                 rng.collapse();\r
7452                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');\r
7453 \r
7454                                                 // Insert end marker\r
7455                                                 if (!collapsed) {\r
7456                                                         rng2.collapse(false);\r
7457 \r
7458                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>\r
7459                                                         rng.moveToElementText(rng2.parentElement());\r
7460                                                         if (rng.compareEndPoints('StartToEnd', rng2) == 0)\r
7461                                                                 rng2.move('character', -1);\r
7462 \r
7463                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');\r
7464                                                 }\r
7465                                         } catch (ex) {\r
7466                                                 // IE might throw unspecified error so lets ignore it\r
7467                                                 return null;\r
7468                                         }\r
7469                                 } else {\r
7470                                         // Control selection\r
7471                                         element = rng.item(0);\r
7472                                         name = element.nodeName;\r
7473 \r
7474                                         return {name : name, index : findIndex(name, element)};\r
7475                                 }\r
7476                         } else {\r
7477                                 element = t.getNode();\r
7478                                 name = element.nodeName;\r
7479                                 if (name == 'IMG')\r
7480                                         return {name : name, index : findIndex(name, element)};\r
7481 \r
7482                                 // W3C method\r
7483                                 rng2 = rng.cloneRange();\r
7484 \r
7485                                 // Insert end marker\r
7486                                 if (!collapsed) {\r
7487                                         rng2.collapse(false);\r
7488                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));\r
7489                                 }\r
7490 \r
7491                                 rng.collapse(true);\r
7492                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));\r
7493                         }\r
7494 \r
7495                         t.moveToBookmark({id : id, keep : 1});\r
7496 \r
7497                         return {id : id};\r
7498                 },\r
7499 \r
7500                 moveToBookmark : function(bookmark) {\r
7501                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;\r
7502 \r
7503                         if (bookmark) {\r
7504                                 if (bookmark.start) {\r
7505                                         rng = dom.createRng();\r
7506                                         root = dom.getRoot();\r
7507 \r
7508                                         function setEndPoint(start) {\r
7509                                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;\r
7510 \r
7511                                                 if (point) {\r
7512                                                         offset = point[0];\r
7513 \r
7514                                                         // Find container node\r
7515                                                         for (node = root, i = point.length - 1; i >= 1; i--) {\r
7516                                                                 children = node.childNodes;\r
7517 \r
7518                                                                 if (point[i] > children.length - 1)\r
7519                                                                         return;\r
7520 \r
7521                                                                 node = children[point[i]];\r
7522                                                         }\r
7523 \r
7524                                                         // Move text offset to best suitable location\r
7525                                                         if (node.nodeType === 3)\r
7526                                                                 offset = Math.min(point[0], node.nodeValue.length);\r
7527 \r
7528                                                         // Move element offset to best suitable location\r
7529                                                         if (node.nodeType === 1)\r
7530                                                                 offset = Math.min(point[0], node.childNodes.length);\r
7531 \r
7532                                                         // Set offset within container node\r
7533                                                         if (start)\r
7534                                                                 rng.setStart(node, offset);\r
7535                                                         else\r
7536                                                                 rng.setEnd(node, offset);\r
7537                                                 }\r
7538 \r
7539                                                 return true;\r
7540                                         };\r
7541 \r
7542                                         if (t.tridentSel)\r
7543                                                 return t.tridentSel.moveToBookmark(bookmark);\r
7544 \r
7545                                         if (setEndPoint(true) && setEndPoint()) {\r
7546                                                 t.setRng(rng);\r
7547                                         }\r
7548                                 } else if (bookmark.id) {\r
7549                                         function restoreEndPoint(suffix) {\r
7550                                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;\r
7551 \r
7552                                                 if (marker) {\r
7553                                                         node = marker.parentNode;\r
7554 \r
7555                                                         if (suffix == 'start') {\r
7556                                                                 if (!keep) {\r
7557                                                                         idx = dom.nodeIndex(marker);\r
7558                                                                 } else {\r
7559                                                                         node = marker.firstChild;\r
7560                                                                         idx = 1;\r
7561                                                                 }\r
7562 \r
7563                                                                 startContainer = endContainer = node;\r
7564                                                                 startOffset = endOffset = idx;\r
7565                                                         } else {\r
7566                                                                 if (!keep) {\r
7567                                                                         idx = dom.nodeIndex(marker);\r
7568                                                                 } else {\r
7569                                                                         node = marker.firstChild;\r
7570                                                                         idx = 1;\r
7571                                                                 }\r
7572 \r
7573                                                                 endContainer = node;\r
7574                                                                 endOffset = idx;\r
7575                                                         }\r
7576 \r
7577                                                         if (!keep) {\r
7578                                                                 prev = marker.previousSibling;\r
7579                                                                 next = marker.nextSibling;\r
7580 \r
7581                                                                 // Remove all marker text nodes\r
7582                                                                 each(tinymce.grep(marker.childNodes), function(node) {\r
7583                                                                         if (node.nodeType == 3)\r
7584                                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');\r
7585                                                                 });\r
7586 \r
7587                                                                 // Remove marker but keep children if for example contents where inserted into the marker\r
7588                                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature\r
7589                                                                 while (marker = dom.get(bookmark.id + '_' + suffix))\r
7590                                                                         dom.remove(marker, 1);\r
7591 \r
7592                                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node\r
7593                                                                 // 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
7594                                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {\r
7595                                                                         idx = prev.nodeValue.length;\r
7596                                                                         prev.appendData(next.nodeValue);\r
7597                                                                         dom.remove(next);\r
7598 \r
7599                                                                         if (suffix == 'start') {\r
7600                                                                                 startContainer = endContainer = prev;\r
7601                                                                                 startOffset = endOffset = idx;\r
7602                                                                         } else {\r
7603                                                                                 endContainer = prev;\r
7604                                                                                 endOffset = idx;\r
7605                                                                         }\r
7606                                                                 }\r
7607                                                         }\r
7608                                                 }\r
7609                                         };\r
7610 \r
7611                                         function addBogus(node) {\r
7612                                                 // Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly\r
7613                                                 if (dom.isBlock(node) && !node.innerHTML)\r
7614                                                         node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';\r
7615 \r
7616                                                 return node;\r
7617                                         };\r
7618 \r
7619                                         // Restore start/end points\r
7620                                         restoreEndPoint('start');\r
7621                                         restoreEndPoint('end');\r
7622 \r
7623                                         if (startContainer) {\r
7624                                                 rng = dom.createRng();\r
7625                                                 rng.setStart(addBogus(startContainer), startOffset);\r
7626                                                 rng.setEnd(addBogus(endContainer), endOffset);\r
7627                                                 t.setRng(rng);\r
7628                                         }\r
7629                                 } else if (bookmark.name) {\r
7630                                         t.select(dom.select(bookmark.name)[bookmark.index]);\r
7631                                 } else if (bookmark.rng)\r
7632                                         t.setRng(bookmark.rng);\r
7633                         }\r
7634                 },\r
7635 \r
7636                 select : function(node, content) {\r
7637                         var t = this, dom = t.dom, rng = dom.createRng(), idx;\r
7638 \r
7639                         if (node) {\r
7640                                 idx = dom.nodeIndex(node);\r
7641                                 rng.setStart(node.parentNode, idx);\r
7642                                 rng.setEnd(node.parentNode, idx + 1);\r
7643 \r
7644                                 // Find first/last text node or BR element\r
7645                                 if (content) {\r
7646                                         function setPoint(node, start) {\r
7647                                                 var walker = new tinymce.dom.TreeWalker(node, node);\r
7648 \r
7649                                                 do {\r
7650                                                         // Text node\r
7651                                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {\r
7652                                                                 if (start)\r
7653                                                                         rng.setStart(node, 0);\r
7654                                                                 else\r
7655                                                                         rng.setEnd(node, node.nodeValue.length);\r
7656 \r
7657                                                                 return;\r
7658                                                         }\r
7659 \r
7660                                                         // BR element\r
7661                                                         if (node.nodeName == 'BR') {\r
7662                                                                 if (start)\r
7663                                                                         rng.setStartBefore(node);\r
7664                                                                 else\r
7665                                                                         rng.setEndBefore(node);\r
7666 \r
7667                                                                 return;\r
7668                                                         }\r
7669                                                 } while (node = (start ? walker.next() : walker.prev()));\r
7670                                         };\r
7671 \r
7672                                         setPoint(node, 1);\r
7673                                         setPoint(node);\r
7674                                 }\r
7675 \r
7676                                 t.setRng(rng);\r
7677                         }\r
7678 \r
7679                         return node;\r
7680                 },\r
7681 \r
7682                 isCollapsed : function() {\r
7683                         var t = this, r = t.getRng(), s = t.getSel();\r
7684 \r
7685                         if (!r || r.item)\r
7686                                 return false;\r
7687 \r
7688                         if (r.compareEndPoints)\r
7689                                 return r.compareEndPoints('StartToEnd', r) === 0;\r
7690 \r
7691                         return !s || r.collapsed;\r
7692                 },\r
7693 \r
7694                 collapse : function(to_start) {\r
7695                         var self = this, rng = self.getRng(), node;\r
7696 \r
7697                         // Control range on IE\r
7698                         if (rng.item) {\r
7699                                 node = rng.item(0);\r
7700                                 rng = self.win.document.body.createTextRange();\r
7701                                 rng.moveToElementText(node);\r
7702                         }\r
7703 \r
7704                         rng.collapse(!!to_start);\r
7705                         self.setRng(rng);\r
7706                 },\r
7707 \r
7708                 getSel : function() {\r
7709                         var t = this, w = this.win;\r
7710 \r
7711                         return w.getSelection ? w.getSelection() : w.document.selection;\r
7712                 },\r
7713 \r
7714                 getRng : function(w3c) {\r
7715                         var t = this, s, r, elm, doc = t.win.document;\r
7716 \r
7717                         // Found tridentSel object then we need to use that one\r
7718                         if (w3c && t.tridentSel)\r
7719                                 return t.tridentSel.getRangeAt(0);\r
7720 \r
7721                         try {\r
7722                                 if (s = t.getSel())\r
7723                                         r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());\r
7724                         } catch (ex) {\r
7725                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe\r
7726                         }\r
7727 \r
7728                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet\r
7729                         if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {\r
7730                                 elm = doc.selection.createRange().item(0);\r
7731                                 r = doc.createRange();\r
7732                                 r.setStartBefore(elm);\r
7733                                 r.setEndAfter(elm);\r
7734                         }\r
7735 \r
7736                         // No range found then create an empty one\r
7737                         // This can occur when the editor is placed in a hidden container element on Gecko\r
7738                         // Or on IE when there was an exception\r
7739                         if (!r)\r
7740                                 r = doc.createRange ? doc.createRange() : doc.body.createTextRange();\r
7741 \r
7742                         if (t.selectedRange && t.explicitRange) {\r
7743                                 if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {\r
7744                                         // Safari, Opera and Chrome only ever select text which causes the range to change.\r
7745                                         // This lets us use the originally set range if the selection hasn't been changed by the user.\r
7746                                         r = t.explicitRange;\r
7747                                 } else {\r
7748                                         t.selectedRange = null;\r
7749                                         t.explicitRange = null;\r
7750                                 }\r
7751                         }\r
7752 \r
7753                         return r;\r
7754                 },\r
7755 \r
7756                 setRng : function(r) {\r
7757                         var s, t = this;\r
7758                         \r
7759                         if (!t.tridentSel) {\r
7760                                 s = t.getSel();\r
7761 \r
7762                                 if (s) {\r
7763                                         t.explicitRange = r;\r
7764 \r
7765                                         try {\r
7766                                                 s.removeAllRanges();\r
7767                                         } catch (ex) {\r
7768                                                 // IE9 might throw errors here don't know why\r
7769                                         }\r
7770 \r
7771                                         s.addRange(r);\r
7772                                         t.selectedRange = s.getRangeAt(0);\r
7773                                 }\r
7774                         } else {\r
7775                                 // Is W3C Range\r
7776                                 if (r.cloneRange) {\r
7777                                         t.tridentSel.addRange(r);\r
7778                                         return;\r
7779                                 }\r
7780 \r
7781                                 // Is IE specific range\r
7782                                 try {\r
7783                                         r.select();\r
7784                                 } catch (ex) {\r
7785                                         // Needed for some odd IE bug #1843306\r
7786                                 }\r
7787                         }\r
7788                 },\r
7789 \r
7790                 setNode : function(n) {\r
7791                         var t = this;\r
7792 \r
7793                         t.setContent(t.dom.getOuterHTML(n));\r
7794 \r
7795                         return n;\r
7796                 },\r
7797 \r
7798                 getNode : function() {\r
7799                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;\r
7800 \r
7801                         // Range maybe lost after the editor is made visible again\r
7802                         if (!rng)\r
7803                                 return t.dom.getRoot();\r
7804 \r
7805                         if (rng.setStart) {\r
7806                                 elm = rng.commonAncestorContainer;\r
7807 \r
7808                                 // Handle selection a image or other control like element such as anchors\r
7809                                 if (!rng.collapsed) {\r
7810                                         if (rng.startContainer == rng.endContainer) {\r
7811                                                 if (rng.endOffset - rng.startOffset < 2) {\r
7812                                                         if (rng.startContainer.hasChildNodes())\r
7813                                                                 elm = rng.startContainer.childNodes[rng.startOffset];\r
7814                                                 }\r
7815                                         }\r
7816 \r
7817                                         // If the anchor node is a element instead of a text node then return this element\r
7818                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1) \r
7819                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];\r
7820 \r
7821                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.\r
7822                                         // This happens when you double click an underlined word in FireFox.\r
7823                                         if (start.nodeType === 3 && end.nodeType === 3) {\r
7824                                                 function skipEmptyTextNodes(n, forwards) {\r
7825                                                         var orig = n;\r
7826                                                         while (n && n.nodeType === 3 && n.length === 0) {\r
7827                                                                 n = forwards ? n.nextSibling : n.previousSibling;\r
7828                                                         }\r
7829                                                         return n || orig;\r
7830                                                 }\r
7831                                                 if (start.length === rng.startOffset) {\r
7832                                                         start = skipEmptyTextNodes(start.nextSibling, true);\r
7833                                                 } else {\r
7834                                                         start = start.parentNode;\r
7835                                                 }\r
7836                                                 if (rng.endOffset === 0) {\r
7837                                                         end = skipEmptyTextNodes(end.previousSibling, false);\r
7838                                                 } else {\r
7839                                                         end = end.parentNode;\r
7840                                                 }\r
7841 \r
7842                                                 if (start && start === end)\r
7843                                                         return start;\r
7844                                         }\r
7845                                 }\r
7846 \r
7847                                 if (elm && elm.nodeType == 3)\r
7848                                         return elm.parentNode;\r
7849 \r
7850                                 return elm;\r
7851                         }\r
7852 \r
7853                         return rng.item ? rng.item(0) : rng.parentElement();\r
7854                 },\r
7855 \r
7856                 getSelectedBlocks : function(st, en) {\r
7857                         var t = this, dom = t.dom, sb, eb, n, bl = [];\r
7858 \r
7859                         sb = dom.getParent(st || t.getStart(), dom.isBlock);\r
7860                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);\r
7861 \r
7862                         if (sb)\r
7863                                 bl.push(sb);\r
7864 \r
7865                         if (sb && eb && sb != eb) {\r
7866                                 n = sb;\r
7867 \r
7868                                 while ((n = n.nextSibling) && n != eb) {\r
7869                                         if (dom.isBlock(n))\r
7870                                                 bl.push(n);\r
7871                                 }\r
7872                         }\r
7873 \r
7874                         if (eb && sb != eb)\r
7875                                 bl.push(eb);\r
7876 \r
7877                         return bl;\r
7878                 },\r
7879 \r
7880                 destroy : function(s) {\r
7881                         var t = this;\r
7882 \r
7883                         t.win = null;\r
7884 \r
7885                         // Manual destroy then remove unload handler\r
7886                         if (!s)\r
7887                                 tinymce.removeUnload(t.destroy);\r
7888                 },\r
7889 \r
7890                 // 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
7891                 _fixIESelection : function() {\r
7892                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;\r
7893 \r
7894                         // Make HTML element unselectable since we are going to handle selection by hand\r
7895                         doc.documentElement.unselectable = true;\r
7896 \r
7897                         // Return range from point or null if it failed\r
7898                         function rngFromPoint(x, y) {\r
7899                                 var rng = body.createTextRange();\r
7900 \r
7901                                 try {\r
7902                                         rng.moveToPoint(x, y);\r
7903                                 } catch (ex) {\r
7904                                         // IE sometimes throws and exception, so lets just ignore it\r
7905                                         rng = null;\r
7906                                 }\r
7907 \r
7908                                 return rng;\r
7909                         };\r
7910 \r
7911                         // Fires while the selection is changing\r
7912                         function selectionChange(e) {\r
7913                                 var pointRng;\r
7914 \r
7915                                 // Check if the button is down or not\r
7916                                 if (e.button) {\r
7917                                         // Create range from mouse position\r
7918                                         pointRng = rngFromPoint(e.x, e.y);\r
7919 \r
7920                                         if (pointRng) {\r
7921                                                 // Check if pointRange is before/after selection then change the endPoint\r
7922                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)\r
7923                                                         pointRng.setEndPoint('StartToStart', startRng);\r
7924                                                 else\r
7925                                                         pointRng.setEndPoint('EndToEnd', startRng);\r
7926 \r
7927                                                 pointRng.select();\r
7928                                         }\r
7929                                 } else\r
7930                                         endSelection();\r
7931                         }\r
7932 \r
7933                         // Removes listeners\r
7934                         function endSelection() {\r
7935                                 var rng = doc.selection.createRange();\r
7936 \r
7937                                 // If the range is collapsed then use the last start range\r
7938                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)\r
7939                                         startRng.select();\r
7940 \r
7941                                 dom.unbind(doc, 'mouseup', endSelection);\r
7942                                 dom.unbind(doc, 'mousemove', selectionChange);\r
7943                                 startRng = started = 0;\r
7944                         };\r
7945 \r
7946                         // Detect when user selects outside BODY\r
7947                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {\r
7948                                 if (e.target.nodeName === 'HTML') {\r
7949                                         if (started)\r
7950                                                 endSelection();\r
7951 \r
7952                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML\r
7953                                         htmlElm = doc.documentElement;\r
7954                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)\r
7955                                                 return;\r
7956 \r
7957                                         started = 1;\r
7958                                         // Setup start position\r
7959                                         startRng = rngFromPoint(e.x, e.y);\r
7960                                         if (startRng) {\r
7961                                                 // Listen for selection change events\r
7962                                                 dom.bind(doc, 'mouseup', endSelection);\r
7963                                                 dom.bind(doc, 'mousemove', selectionChange);\r
7964 \r
7965                                                 dom.win.focus();\r
7966                                                 startRng.select();\r
7967                                         }\r
7968                                 }\r
7969                         });\r
7970                 }\r
7971         });\r
7972 })(tinymce);\r
7973 \r
7974 (function(tinymce) {\r
7975         tinymce.dom.Serializer = function(settings, dom, schema) {\r
7976                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;\r
7977 \r
7978                 // Support the old apply_source_formatting option\r
7979                 if (!settings.apply_source_formatting)\r
7980                         settings.indent = false;\r
7981 \r
7982                 settings.remove_trailing_brs = true;\r
7983 \r
7984                 // Default DOM and Schema if they are undefined\r
7985                 dom = dom || tinymce.DOM;\r
7986                 schema = schema || new tinymce.html.Schema(settings);\r
7987                 settings.entity_encoding = settings.entity_encoding || 'named';\r
7988 \r
7989                 onPreProcess = new tinymce.util.Dispatcher(self);\r
7990 \r
7991                 onPostProcess = new tinymce.util.Dispatcher(self);\r
7992 \r
7993                 htmlParser = new tinymce.html.DomParser(settings, schema);\r
7994 \r
7995                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed\r
7996                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {\r
7997                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;\r
7998 \r
7999                         while (i--) {\r
8000                                 node = nodes[i];\r
8001 \r
8002                                 value = node.attributes.map[internalName];\r
8003                                 if (value !== undef) {\r
8004                                         // Set external name to internal value and remove internal\r
8005                                         node.attr(name, value.length > 0 ? value : null);\r
8006                                         node.attr(internalName, null);\r
8007                                 } else {\r
8008                                         // No internal attribute found then convert the value we have in the DOM\r
8009                                         value = node.attributes.map[name];\r
8010 \r
8011                                         if (name === "style")\r
8012                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);\r
8013                                         else if (urlConverter)\r
8014                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);\r
8015 \r
8016                                         node.attr(name, value.length > 0 ? value : null);\r
8017                                 }\r
8018                         }\r
8019                 });\r
8020 \r
8021                 // Remove internal classes mceItem<..>\r
8022                 htmlParser.addAttributeFilter('class', function(nodes, name) {\r
8023                         var i = nodes.length, node, value;\r
8024 \r
8025                         while (i--) {\r
8026                                 node = nodes[i];\r
8027                                 value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');\r
8028                                 node.attr('class', value.length > 0 ? value : null);\r
8029                         }\r
8030                 });\r
8031 \r
8032                 // Remove bookmark elements\r
8033                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {\r
8034                         var i = nodes.length, node;\r
8035 \r
8036                         while (i--) {\r
8037                                 node = nodes[i];\r
8038 \r
8039                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)\r
8040                                         node.remove();\r
8041                         }\r
8042                 });\r
8043 \r
8044                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles\r
8045                 htmlParser.addNodeFilter('script,style', function(nodes, name) {\r
8046                         var i = nodes.length, node, value;\r
8047 \r
8048                         function trim(value) {\r
8049                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')\r
8050                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')\r
8051                                                 .replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')\r
8052                                                 .replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');\r
8053                         };\r
8054 \r
8055                         while (i--) {\r
8056                                 node = nodes[i];\r
8057                                 value = node.firstChild ? node.firstChild.value : '';\r
8058 \r
8059                                 if (name === "script") {\r
8060                                         // Remove mce- prefix from script elements\r
8061                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));\r
8062 \r
8063                                         if (value.length > 0)\r
8064                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';\r
8065                                 } else {\r
8066                                         if (value.length > 0)\r
8067                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';\r
8068                                 }\r
8069                         }\r
8070                 });\r
8071 \r
8072                 // Convert comments to cdata and handle protected comments\r
8073                 htmlParser.addNodeFilter('#comment', function(nodes, name) {\r
8074                         var i = nodes.length, node;\r
8075 \r
8076                         while (i--) {\r
8077                                 node = nodes[i];\r
8078 \r
8079                                 if (node.value.indexOf('[CDATA[') === 0) {\r
8080                                         node.name = '#cdata';\r
8081                                         node.type = 4;\r
8082                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');\r
8083                                 } else if (node.value.indexOf('mce:protected ') === 0) {\r
8084                                         node.name = "#text";\r
8085                                         node.type = 3;\r
8086                                         node.raw = true;\r
8087                                         node.value = unescape(node.value).substr(14);\r
8088                                 }\r
8089                         }\r
8090                 });\r
8091 \r
8092                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {\r
8093                         var i = nodes.length, node;\r
8094 \r
8095                         while (i--) {\r
8096                                 node = nodes[i];\r
8097                                 if (node.type === 7)\r
8098                                         node.remove();\r
8099                                 else if (node.type === 1) {\r
8100                                         if (name === "input" && !("type" in node.attributes.map))\r
8101                                                 node.attr('type', 'text');\r
8102                                 }\r
8103                         }\r
8104                 });\r
8105 \r
8106                 // Fix list elements, TODO: Replace this later\r
8107                 if (settings.fix_list_elements) {\r
8108                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {\r
8109                                 var i = nodes.length, node, parentNode;\r
8110 \r
8111                                 while (i--) {\r
8112                                         node = nodes[i];\r
8113                                         parentNode = node.parent;\r
8114 \r
8115                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {\r
8116                                                 if (node.prev && node.prev.name === 'li') {\r
8117                                                         node.prev.append(node);\r
8118                                                 }\r
8119                                         }\r
8120                                 }\r
8121                         });\r
8122                 }\r
8123 \r
8124                 // Remove internal data attributes\r
8125                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {\r
8126                         var i = nodes.length;\r
8127 \r
8128                         while (i--) {\r
8129                                 nodes[i].attr(name, null);\r
8130                         }\r
8131                 });\r
8132 \r
8133                 // Return public methods\r
8134                 return {\r
8135                         schema : schema,\r
8136 \r
8137                         addNodeFilter : htmlParser.addNodeFilter,\r
8138 \r
8139                         addAttributeFilter : htmlParser.addAttributeFilter,\r
8140 \r
8141                         onPreProcess : onPreProcess,\r
8142 \r
8143                         onPostProcess : onPostProcess,\r
8144 \r
8145                         serialize : function(node, args) {\r
8146                                 var impl, doc, oldDoc, htmlSerializer, content;\r
8147 \r
8148                                 // Explorer won't clone contents of script and style and the\r
8149                                 // selected index of select elements are cleared on a clone operation.\r
8150                                 if (isIE && dom.select('script,style,select').length > 0) {\r
8151                                         content = node.innerHTML;\r
8152                                         node = node.cloneNode(false);\r
8153                                         dom.setHTML(node, content);\r
8154                                 } else\r
8155                                         node = node.cloneNode(true);\r
8156 \r
8157                                 // Nodes needs to be attached to something in WebKit/Opera\r
8158                                 // Older builds of Opera crashes if you attach the node to an document created dynamically\r
8159                                 // and since we can't feature detect a crash we need to sniff the acutal build number\r
8160                                 // This fix will make DOM ranges and make Sizzle happy!\r
8161                                 impl = node.ownerDocument.implementation;\r
8162                                 if (impl.createHTMLDocument) {\r
8163                                         // Create an empty HTML document\r
8164                                         doc = impl.createHTMLDocument("");\r
8165 \r
8166                                         // Add the element or it's children if it's a body element to the new document\r
8167                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {\r
8168                                                 doc.body.appendChild(doc.importNode(node, true));\r
8169                                         });\r
8170 \r
8171                                         // Grab first child or body element for serialization\r
8172                                         if (node.nodeName != 'BODY')\r
8173                                                 node = doc.body.firstChild;\r
8174                                         else\r
8175                                                 node = doc.body;\r
8176 \r
8177                                         // set the new document in DOMUtils so createElement etc works\r
8178                                         oldDoc = dom.doc;\r
8179                                         dom.doc = doc;\r
8180                                 }\r
8181 \r
8182                                 args = args || {};\r
8183                                 args.format = args.format || 'html';\r
8184 \r
8185                                 // Pre process\r
8186                                 if (!args.no_events) {\r
8187                                         args.node = node;\r
8188                                         onPreProcess.dispatch(self, args);\r
8189                                 }\r
8190 \r
8191                                 // Setup serializer\r
8192                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);\r
8193 \r
8194                                 // Parse and serialize HTML\r
8195                                 args.content = htmlSerializer.serialize(\r
8196                                         htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)\r
8197                                 );\r
8198 \r
8199                                 // Replace all BOM characters for now until we can find a better solution\r
8200                                 if (!args.cleanup)\r
8201                                         args.content = args.content.replace(/\uFEFF/g, '');\r
8202 \r
8203                                 // Post process\r
8204                                 if (!args.no_events)\r
8205                                         onPostProcess.dispatch(self, args);\r
8206 \r
8207                                 // Restore the old document if it was changed\r
8208                                 if (oldDoc)\r
8209                                         dom.doc = oldDoc;\r
8210 \r
8211                                 args.node = null;\r
8212 \r
8213                                 return args.content;\r
8214                         },\r
8215 \r
8216                         addRules : function(rules) {\r
8217                                 schema.addValidElements(rules);\r
8218                         },\r
8219 \r
8220                         setRules : function(rules) {\r
8221                                 schema.setValidElements(rules);\r
8222                         }\r
8223                 };\r
8224         };\r
8225 })(tinymce);\r
8226 (function(tinymce) {\r
8227         tinymce.dom.ScriptLoader = function(settings) {\r
8228                 var QUEUED = 0,\r
8229                         LOADING = 1,\r
8230                         LOADED = 2,\r
8231                         states = {},\r
8232                         queue = [],\r
8233                         scriptLoadedCallbacks = {},\r
8234                         queueLoadedCallbacks = [],\r
8235                         loading = 0,\r
8236                         undefined;\r
8237 \r
8238                 function loadScript(url, callback) {\r
8239                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;\r
8240 \r
8241                         // Execute callback when script is loaded\r
8242                         function done() {\r
8243                                 dom.remove(id);\r
8244 \r
8245                                 if (elm)\r
8246                                         elm.onreadystatechange = elm.onload = elm = null;\r
8247 \r
8248                                 callback();\r
8249                         };\r
8250                         \r
8251                         function error() {\r
8252                                 // Report the error so it's easier for people to spot loading errors\r
8253                                 if (typeof(console) !== "undefined" && console.log)\r
8254                                         console.log("Failed to load: " + url);\r
8255 \r
8256                                 // We can't mark it as done if there is a load error since\r
8257                                 // A) We don't want to produce 404 errors on the server and\r
8258                                 // B) the onerror event won't fire on all browsers.\r
8259                                 // done();\r
8260                         };\r
8261 \r
8262                         id = dom.uniqueId();\r
8263 \r
8264                         if (tinymce.isIE6) {\r
8265                                 uri = new tinymce.util.URI(url);\r
8266                                 loc = location;\r
8267 \r
8268                                 // If script is from same domain and we\r
8269                                 // use IE 6 then use XHR since it's more reliable\r
8270                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {\r
8271                                         tinymce.util.XHR.send({\r
8272                                                 url : tinymce._addVer(uri.getURI()),\r
8273                                                 success : function(content) {\r
8274                                                         // Create new temp script element\r
8275                                                         var script = dom.create('script', {\r
8276                                                                 type : 'text/javascript'\r
8277                                                         });\r
8278 \r
8279                                                         // Evaluate script in global scope\r
8280                                                         script.text = content;\r
8281                                                         document.getElementsByTagName('head')[0].appendChild(script);\r
8282                                                         dom.remove(script);\r
8283 \r
8284                                                         done();\r
8285                                                 },\r
8286                                                 \r
8287                                                 error : error\r
8288                                         });\r
8289 \r
8290                                         return;\r
8291                                 }\r
8292                         }\r
8293 \r
8294                         // Create new script element\r
8295                         elm = dom.create('script', {\r
8296                                 id : id,\r
8297                                 type : 'text/javascript',\r
8298                                 src : tinymce._addVer(url)\r
8299                         });\r
8300 \r
8301                         // Add onload listener for non IE browsers since IE9\r
8302                         // fires onload event before the script is parsed and executed\r
8303                         if (!tinymce.isIE)\r
8304                                 elm.onload = done;\r
8305 \r
8306                         // Add onerror event will get fired on some browsers but not all of them\r
8307                         elm.onerror = error;\r
8308 \r
8309                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly\r
8310                         if (!tinymce.isOpera) {\r
8311                                 elm.onreadystatechange = function() {\r
8312                                         var state = elm.readyState;\r
8313 \r
8314                                         // Loaded state is passed on IE 6 however there\r
8315                                         // are known issues with this method but we can't use\r
8316                                         // XHR in a cross domain loading\r
8317                                         if (state == 'complete' || state == 'loaded')\r
8318                                                 done();\r
8319                                 };\r
8320                         }\r
8321 \r
8322                         // Most browsers support this feature so we report errors\r
8323                         // for those at least to help users track their missing plugins etc\r
8324                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option\r
8325                         /*elm.onerror = function() {\r
8326                                 alert('Failed to load: ' + url);\r
8327                         };*/\r
8328 \r
8329                         // Add script to document\r
8330                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);\r
8331                 };\r
8332 \r
8333                 this.isDone = function(url) {\r
8334                         return states[url] == LOADED;\r
8335                 };\r
8336 \r
8337                 this.markDone = function(url) {\r
8338                         states[url] = LOADED;\r
8339                 };\r
8340 \r
8341                 this.add = this.load = function(url, callback, scope) {\r
8342                         var item, state = states[url];\r
8343 \r
8344                         // Add url to load queue\r
8345                         if (state == undefined) {\r
8346                                 queue.push(url);\r
8347                                 states[url] = QUEUED;\r
8348                         }\r
8349 \r
8350                         if (callback) {\r
8351                                 // Store away callback for later execution\r
8352                                 if (!scriptLoadedCallbacks[url])\r
8353                                         scriptLoadedCallbacks[url] = [];\r
8354 \r
8355                                 scriptLoadedCallbacks[url].push({\r
8356                                         func : callback,\r
8357                                         scope : scope || this\r
8358                                 });\r
8359                         }\r
8360                 };\r
8361 \r
8362                 this.loadQueue = function(callback, scope) {\r
8363                         this.loadScripts(queue, callback, scope);\r
8364                 };\r
8365 \r
8366                 this.loadScripts = function(scripts, callback, scope) {\r
8367                         var loadScripts;\r
8368 \r
8369                         function execScriptLoadedCallbacks(url) {\r
8370                                 // Execute URL callback functions\r
8371                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {\r
8372                                         callback.func.call(callback.scope);\r
8373                                 });\r
8374 \r
8375                                 scriptLoadedCallbacks[url] = undefined;\r
8376                         };\r
8377 \r
8378                         queueLoadedCallbacks.push({\r
8379                                 func : callback,\r
8380                                 scope : scope || this\r
8381                         });\r
8382 \r
8383                         loadScripts = function() {\r
8384                                 var loadingScripts = tinymce.grep(scripts);\r
8385 \r
8386                                 // Current scripts has been handled\r
8387                                 scripts.length = 0;\r
8388 \r
8389                                 // Load scripts that needs to be loaded\r
8390                                 tinymce.each(loadingScripts, function(url) {\r
8391                                         // Script is already loaded then execute script callbacks directly\r
8392                                         if (states[url] == LOADED) {\r
8393                                                 execScriptLoadedCallbacks(url);\r
8394                                                 return;\r
8395                                         }\r
8396 \r
8397                                         // Is script not loading then start loading it\r
8398                                         if (states[url] != LOADING) {\r
8399                                                 states[url] = LOADING;\r
8400                                                 loading++;\r
8401 \r
8402                                                 loadScript(url, function() {\r
8403                                                         states[url] = LOADED;\r
8404                                                         loading--;\r
8405 \r
8406                                                         execScriptLoadedCallbacks(url);\r
8407 \r
8408                                                         // Load more scripts if they where added by the recently loaded script\r
8409                                                         loadScripts();\r
8410                                                 });\r
8411                                         }\r
8412                                 });\r
8413 \r
8414                                 // No scripts are currently loading then execute all pending queue loaded callbacks\r
8415                                 if (!loading) {\r
8416                                         tinymce.each(queueLoadedCallbacks, function(callback) {\r
8417                                                 callback.func.call(callback.scope);\r
8418                                         });\r
8419 \r
8420                                         queueLoadedCallbacks.length = 0;\r
8421                                 }\r
8422                         };\r
8423 \r
8424                         loadScripts();\r
8425                 };\r
8426         };\r
8427 \r
8428         // Global script loader\r
8429         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();\r
8430 })(tinymce);\r
8431 \r
8432 tinymce.dom.TreeWalker = function(start_node, root_node) {\r
8433         var node = start_node;\r
8434 \r
8435         function findSibling(node, start_name, sibling_name, shallow) {\r
8436                 var sibling, parent;\r
8437 \r
8438                 if (node) {\r
8439                         // Walk into nodes if it has a start\r
8440                         if (!shallow && node[start_name])\r
8441                                 return node[start_name];\r
8442 \r
8443                         // Return the sibling if it has one\r
8444                         if (node != root_node) {\r
8445                                 sibling = node[sibling_name];\r
8446                                 if (sibling)\r
8447                                         return sibling;\r
8448 \r
8449                                 // Walk up the parents to look for siblings\r
8450                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {\r
8451                                         sibling = parent[sibling_name];\r
8452                                         if (sibling)\r
8453                                                 return sibling;\r
8454                                 }\r
8455                         }\r
8456                 }\r
8457         };\r
8458 \r
8459         this.current = function() {\r
8460                 return node;\r
8461         };\r
8462 \r
8463         this.next = function(shallow) {\r
8464                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));\r
8465         };\r
8466 \r
8467         this.prev = function(shallow) {\r
8468                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));\r
8469         };\r
8470 };\r
8471 \r
8472 (function(tinymce) {\r
8473         tinymce.dom.RangeUtils = function(dom) {\r
8474                 var INVISIBLE_CHAR = '\uFEFF';\r
8475 \r
8476                 this.walk = function(rng, callback) {\r
8477                         var startContainer = rng.startContainer,\r
8478                                 startOffset = rng.startOffset,\r
8479                                 endContainer = rng.endContainer,\r
8480                                 endOffset = rng.endOffset,\r
8481                                 ancestor, startPoint,\r
8482                                 endPoint, node, parent, siblings, nodes;\r
8483 \r
8484                         // Handle table cell selection the table plugin enables\r
8485                         // you to fake select table cells and perform formatting actions on them\r
8486                         nodes = dom.select('td.mceSelected,th.mceSelected');\r
8487                         if (nodes.length > 0) {\r
8488                                 tinymce.each(nodes, function(node) {\r
8489                                         callback([node]);\r
8490                                 });\r
8491 \r
8492                                 return;\r
8493                         }\r
8494 \r
8495                         function collectSiblings(node, name, end_node) {\r
8496                                 var siblings = [];\r
8497 \r
8498                                 for (; node && node != end_node; node = node[name])\r
8499                                         siblings.push(node);\r
8500 \r
8501                                 return siblings;\r
8502                         };\r
8503 \r
8504                         function findEndPoint(node, root) {\r
8505                                 do {\r
8506                                         if (node.parentNode == root)\r
8507                                                 return node;\r
8508 \r
8509                                         node = node.parentNode;\r
8510                                 } while(node);\r
8511                         };\r
8512 \r
8513                         function walkBoundary(start_node, end_node, next) {\r
8514                                 var siblingName = next ? 'nextSibling' : 'previousSibling';\r
8515 \r
8516                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {\r
8517                                         parent = node.parentNode;\r
8518                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);\r
8519 \r
8520                                         if (siblings.length) {\r
8521                                                 if (!next)\r
8522                                                         siblings.reverse();\r
8523 \r
8524                                                 callback(siblings);\r
8525                                         }\r
8526                                 }\r
8527                         };\r
8528 \r
8529                         // If index based start position then resolve it\r
8530                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())\r
8531                                 startContainer = startContainer.childNodes[startOffset];\r
8532 \r
8533                         // If index based end position then resolve it\r
8534                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())\r
8535                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];\r
8536 \r
8537                         // Find common ancestor and end points\r
8538                         ancestor = dom.findCommonAncestor(startContainer, endContainer);\r
8539 \r
8540                         // Same container\r
8541                         if (startContainer == endContainer)\r
8542                                 return callback([startContainer]);\r
8543 \r
8544                         // Process left side\r
8545                         for (node = startContainer; node; node = node.parentNode) {\r
8546                                 if (node == endContainer)\r
8547                                         return walkBoundary(startContainer, ancestor, true);\r
8548 \r
8549                                 if (node == ancestor)\r
8550                                         break;\r
8551                         }\r
8552 \r
8553                         // Process right side\r
8554                         for (node = endContainer; node; node = node.parentNode) {\r
8555                                 if (node == startContainer)\r
8556                                         return walkBoundary(endContainer, ancestor);\r
8557 \r
8558                                 if (node == ancestor)\r
8559                                         break;\r
8560                         }\r
8561 \r
8562                         // Find start/end point\r
8563                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;\r
8564                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;\r
8565 \r
8566                         // Walk left leaf\r
8567                         walkBoundary(startContainer, startPoint, true);\r
8568 \r
8569                         // Walk the middle from start to end point\r
8570                         siblings = collectSiblings(\r
8571                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,\r
8572                                 'nextSibling',\r
8573                                 endPoint == endContainer ? endPoint.nextSibling : endPoint\r
8574                         );\r
8575 \r
8576                         if (siblings.length)\r
8577                                 callback(siblings);\r
8578 \r
8579                         // Walk right leaf\r
8580                         walkBoundary(endContainer, endPoint);\r
8581                 };\r
8582 \r
8583                 /*              this.split = function(rng) {\r
8584                         var startContainer = rng.startContainer,\r
8585                                 startOffset = rng.startOffset,\r
8586                                 endContainer = rng.endContainer,\r
8587                                 endOffset = rng.endOffset;\r
8588 \r
8589                         function splitText(node, offset) {\r
8590                                 if (offset == node.nodeValue.length)\r
8591                                         node.appendData(INVISIBLE_CHAR);\r
8592 \r
8593                                 node = node.splitText(offset);\r
8594 \r
8595                                 if (node.nodeValue === INVISIBLE_CHAR)\r
8596                                         node.nodeValue = '';\r
8597 \r
8598                                 return node;\r
8599                         };\r
8600 \r
8601                         // Handle single text node\r
8602                         if (startContainer == endContainer) {\r
8603                                 if (startContainer.nodeType == 3) {\r
8604                                         if (startOffset != 0)\r
8605                                                 startContainer = endContainer = splitText(startContainer, startOffset);\r
8606 \r
8607                                         if (endOffset - startOffset != startContainer.nodeValue.length)\r
8608                                                 splitText(startContainer, endOffset - startOffset);\r
8609                                 }\r
8610                         } else {\r
8611                                 // Split startContainer text node if needed\r
8612                                 if (startContainer.nodeType == 3 && startOffset != 0) {\r
8613                                         startContainer = splitText(startContainer, startOffset);\r
8614                                         startOffset = 0;\r
8615                                 }\r
8616 \r
8617                                 // Split endContainer text node if needed\r
8618                                 if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {\r
8619                                         endContainer = splitText(endContainer, endOffset).previousSibling;\r
8620                                         endOffset = endContainer.nodeValue.length;\r
8621                                 }\r
8622                         }\r
8623 \r
8624                         return {\r
8625                                 startContainer : startContainer,\r
8626                                 startOffset : startOffset,\r
8627                                 endContainer : endContainer,\r
8628                                 endOffset : endOffset\r
8629                         };\r
8630                 };\r
8631 */\r
8632         };\r
8633 \r
8634         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {\r
8635                 if (rng1 && rng2) {\r
8636                         // Compare native IE ranges\r
8637                         if (rng1.item || rng1.duplicate) {\r
8638                                 // Both are control ranges and the selected element matches\r
8639                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))\r
8640                                         return true;\r
8641 \r
8642                                 // Both are text ranges and the range matches\r
8643                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))\r
8644                                         return true;\r
8645                         } else {\r
8646                                 // Compare w3c ranges\r
8647                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;\r
8648                         }\r
8649                 }\r
8650 \r
8651                 return false;\r
8652         };\r
8653 })(tinymce);\r
8654 \r
8655 (function(tinymce) {\r
8656         var Event = tinymce.dom.Event, each = tinymce.each;\r
8657 \r
8658         tinymce.create('tinymce.ui.KeyboardNavigation', {\r
8659                 KeyboardNavigation: function(settings, dom) {\r
8660                         var t = this, root = settings.root, items = settings.items,\r
8661                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,\r
8662                                         excludeFromTabOrder = settings.excludeFromTabOrder,\r
8663                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;\r
8664 \r
8665                         dom = dom || tinymce.DOM;\r
8666 \r
8667                         itemFocussed = function(evt) {\r
8668                                 focussedId = evt.target.id;\r
8669                         };\r
8670                         \r
8671                         itemBlurred = function(evt) {\r
8672                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');\r
8673                         };\r
8674                         \r
8675                         rootFocussed = function(evt) {\r
8676                                 var item = dom.get(focussedId);\r
8677                                 dom.setAttrib(item, 'tabindex', '0');\r
8678                                 item.focus();\r
8679                         };\r
8680                         \r
8681                         t.focus = function() {\r
8682                                 dom.get(focussedId).focus();\r
8683                         };\r
8684 \r
8685                         t.destroy = function() {\r
8686                                 each(items, function(item) {\r
8687                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);\r
8688                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);\r
8689                                 });\r
8690 \r
8691                                 dom.unbind(dom.get(root), 'focus', rootFocussed);\r
8692                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);\r
8693 \r
8694                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;\r
8695                                 t.destroy = function() {};\r
8696                         };\r
8697                         \r
8698                         t.moveFocus = function(dir, evt) {\r
8699                                 var idx = -1, controls = t.controls, newFocus;\r
8700 \r
8701                                 if (!focussedId)\r
8702                                         return;\r
8703 \r
8704                                 each(items, function(item, index) {\r
8705                                         if (item.id === focussedId) {\r
8706                                                 idx = index;\r
8707                                                 return false;\r
8708                                         }\r
8709                                 });\r
8710 \r
8711                                 idx += dir;\r
8712                                 if (idx < 0) {\r
8713                                         idx = items.length - 1;\r
8714                                 } else if (idx >= items.length) {\r
8715                                         idx = 0;\r
8716                                 }\r
8717                                 \r
8718                                 newFocus = items[idx];\r
8719                                 dom.setAttrib(focussedId, 'tabindex', '-1');\r
8720                                 dom.setAttrib(newFocus.id, 'tabindex', '0');\r
8721                                 dom.get(newFocus.id).focus();\r
8722 \r
8723                                 if (settings.actOnFocus) {\r
8724                                         settings.onAction(newFocus.id);\r
8725                                 }\r
8726 \r
8727                                 if (evt)\r
8728                                         Event.cancel(evt);\r
8729                         };\r
8730                         \r
8731                         rootKeydown = function(evt) {\r
8732                                 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
8733                                 \r
8734                                 switch (evt.keyCode) {\r
8735                                         case DOM_VK_LEFT:\r
8736                                                 if (enableLeftRight) t.moveFocus(-1);\r
8737                                                 break;\r
8738         \r
8739                                         case DOM_VK_RIGHT:\r
8740                                                 if (enableLeftRight) t.moveFocus(1);\r
8741                                                 break;\r
8742         \r
8743                                         case DOM_VK_UP:\r
8744                                                 if (enableUpDown) t.moveFocus(-1);\r
8745                                                 break;\r
8746 \r
8747                                         case DOM_VK_DOWN:\r
8748                                                 if (enableUpDown) t.moveFocus(1);\r
8749                                                 break;\r
8750 \r
8751                                         case DOM_VK_ESCAPE:\r
8752                                                 if (settings.onCancel) {\r
8753                                                         settings.onCancel();\r
8754                                                         Event.cancel(evt);\r
8755                                                 }\r
8756                                                 break;\r
8757 \r
8758                                         case DOM_VK_ENTER:\r
8759                                         case DOM_VK_RETURN:\r
8760                                         case DOM_VK_SPACE:\r
8761                                                 if (settings.onAction) {\r
8762                                                         settings.onAction(focussedId);\r
8763                                                         Event.cancel(evt);\r
8764                                                 }\r
8765                                                 break;\r
8766                                 }\r
8767                         };\r
8768 \r
8769                         // Set up state and listeners for each item.\r
8770                         each(items, function(item, idx) {\r
8771                                 var tabindex;\r
8772 \r
8773                                 if (!item.id) {\r
8774                                         item.id = dom.uniqueId('_mce_item_');\r
8775                                 }\r
8776 \r
8777                                 if (excludeFromTabOrder) {\r
8778                                         dom.bind(item.id, 'blur', itemBlurred);\r
8779                                         tabindex = '-1';\r
8780                                 } else {\r
8781                                         tabindex = (idx === 0 ? '0' : '-1');\r
8782                                 }\r
8783 \r
8784                                 dom.setAttrib(item.id, 'tabindex', tabindex);\r
8785                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);\r
8786                         });\r
8787                         \r
8788                         // Setup initial state for root element.\r
8789                         if (items[0]){\r
8790                                 focussedId = items[0].id;\r
8791                         }\r
8792 \r
8793                         dom.setAttrib(root, 'tabindex', '-1');\r
8794                         \r
8795                         // Setup listeners for root element.\r
8796                         dom.bind(dom.get(root), 'focus', rootFocussed);\r
8797                         dom.bind(dom.get(root), 'keydown', rootKeydown);\r
8798                 }\r
8799         });\r
8800 })(tinymce);\r
8801 (function(tinymce) {\r
8802         // Shorten class names\r
8803         var DOM = tinymce.DOM, is = tinymce.is;\r
8804 \r
8805         tinymce.create('tinymce.ui.Control', {\r
8806                 Control : function(id, s, editor) {\r
8807                         this.id = id;\r
8808                         this.settings = s = s || {};\r
8809                         this.rendered = false;\r
8810                         this.onRender = new tinymce.util.Dispatcher(this);\r
8811                         this.classPrefix = '';\r
8812                         this.scope = s.scope || this;\r
8813                         this.disabled = 0;\r
8814                         this.active = 0;\r
8815                         this.editor = editor;\r
8816                 },\r
8817                 \r
8818                 setAriaProperty : function(property, value) {\r
8819                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);\r
8820                         if (element) {\r
8821                                 DOM.setAttrib(element, 'aria-' + property, !!value);\r
8822                         }\r
8823                 },\r
8824                 \r
8825                 focus : function() {\r
8826                         DOM.get(this.id).focus();\r
8827                 },\r
8828 \r
8829                 setDisabled : function(s) {\r
8830                         if (s != this.disabled) {\r
8831                                 this.setAriaProperty('disabled', s);\r
8832 \r
8833                                 this.setState('Disabled', s);\r
8834                                 this.setState('Enabled', !s);\r
8835                                 this.disabled = s;\r
8836                         }\r
8837                 },\r
8838 \r
8839                 isDisabled : function() {\r
8840                         return this.disabled;\r
8841                 },\r
8842 \r
8843                 setActive : function(s) {\r
8844                         if (s != this.active) {\r
8845                                 this.setState('Active', s);\r
8846                                 this.active = s;\r
8847                                 this.setAriaProperty('pressed', s);\r
8848                         }\r
8849                 },\r
8850 \r
8851                 isActive : function() {\r
8852                         return this.active;\r
8853                 },\r
8854 \r
8855                 setState : function(c, s) {\r
8856                         var n = DOM.get(this.id);\r
8857 \r
8858                         c = this.classPrefix + c;\r
8859 \r
8860                         if (s)\r
8861                                 DOM.addClass(n, c);\r
8862                         else\r
8863                                 DOM.removeClass(n, c);\r
8864                 },\r
8865 \r
8866                 isRendered : function() {\r
8867                         return this.rendered;\r
8868                 },\r
8869 \r
8870                 renderHTML : function() {\r
8871                 },\r
8872 \r
8873                 renderTo : function(n) {\r
8874                         DOM.setHTML(n, this.renderHTML());\r
8875                 },\r
8876 \r
8877                 postRender : function() {\r
8878                         var t = this, b;\r
8879 \r
8880                         // Set pending states\r
8881                         if (is(t.disabled)) {\r
8882                                 b = t.disabled;\r
8883                                 t.disabled = -1;\r
8884                                 t.setDisabled(b);\r
8885                         }\r
8886 \r
8887                         if (is(t.active)) {\r
8888                                 b = t.active;\r
8889                                 t.active = -1;\r
8890                                 t.setActive(b);\r
8891                         }\r
8892                 },\r
8893 \r
8894                 remove : function() {\r
8895                         DOM.remove(this.id);\r
8896                         this.destroy();\r
8897                 },\r
8898 \r
8899                 destroy : function() {\r
8900                         tinymce.dom.Event.clear(this.id);\r
8901                 }\r
8902         });\r
8903 })(tinymce);\r
8904 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {\r
8905         Container : function(id, s, editor) {\r
8906                 this.parent(id, s, editor);\r
8907 \r
8908                 this.controls = [];\r
8909 \r
8910                 this.lookup = {};\r
8911         },\r
8912 \r
8913         add : function(c) {\r
8914                 this.lookup[c.id] = c;\r
8915                 this.controls.push(c);\r
8916 \r
8917                 return c;\r
8918         },\r
8919 \r
8920         get : function(n) {\r
8921                 return this.lookup[n];\r
8922         }\r
8923 });\r
8924 \r
8925 \r
8926 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {\r
8927         Separator : function(id, s) {\r
8928                 this.parent(id, s);\r
8929                 this.classPrefix = 'mceSeparator';\r
8930                 this.setDisabled(true);\r
8931         },\r
8932 \r
8933         renderHTML : function() {\r
8934                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});\r
8935         }\r
8936 });\r
8937 \r
8938 (function(tinymce) {\r
8939         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
8940 \r
8941         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {\r
8942                 MenuItem : function(id, s) {\r
8943                         this.parent(id, s);\r
8944                         this.classPrefix = 'mceMenuItem';\r
8945                 },\r
8946 \r
8947                 setSelected : function(s) {\r
8948                         this.setState('Selected', s);\r
8949                         this.setAriaProperty('checked', !!s);\r
8950                         this.selected = s;\r
8951                 },\r
8952 \r
8953                 isSelected : function() {\r
8954                         return this.selected;\r
8955                 },\r
8956 \r
8957                 postRender : function() {\r
8958                         var t = this;\r
8959                         \r
8960                         t.parent();\r
8961 \r
8962                         // Set pending state\r
8963                         if (is(t.selected))\r
8964                                 t.setSelected(t.selected);\r
8965                 }\r
8966         });\r
8967 })(tinymce);\r
8968 \r
8969 (function(tinymce) {\r
8970         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
8971 \r
8972         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {\r
8973                 Menu : function(id, s) {\r
8974                         var t = this;\r
8975 \r
8976                         t.parent(id, s);\r
8977                         t.items = {};\r
8978                         t.collapsed = false;\r
8979                         t.menuCount = 0;\r
8980                         t.onAddItem = new tinymce.util.Dispatcher(this);\r
8981                 },\r
8982 \r
8983                 expand : function(d) {\r
8984                         var t = this;\r
8985 \r
8986                         if (d) {\r
8987                                 walk(t, function(o) {\r
8988                                         if (o.expand)\r
8989                                                 o.expand();\r
8990                                 }, 'items', t);\r
8991                         }\r
8992 \r
8993                         t.collapsed = false;\r
8994                 },\r
8995 \r
8996                 collapse : function(d) {\r
8997                         var t = this;\r
8998 \r
8999                         if (d) {\r
9000                                 walk(t, function(o) {\r
9001                                         if (o.collapse)\r
9002                                                 o.collapse();\r
9003                                 }, 'items', t);\r
9004                         }\r
9005 \r
9006                         t.collapsed = true;\r
9007                 },\r
9008 \r
9009                 isCollapsed : function() {\r
9010                         return this.collapsed;\r
9011                 },\r
9012 \r
9013                 add : function(o) {\r
9014                         if (!o.settings)\r
9015                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);\r
9016 \r
9017                         this.onAddItem.dispatch(this, o);\r
9018 \r
9019                         return this.items[o.id] = o;\r
9020                 },\r
9021 \r
9022                 addSeparator : function() {\r
9023                         return this.add({separator : true});\r
9024                 },\r
9025 \r
9026                 addMenu : function(o) {\r
9027                         if (!o.collapse)\r
9028                                 o = this.createMenu(o);\r
9029 \r
9030                         this.menuCount++;\r
9031 \r
9032                         return this.add(o);\r
9033                 },\r
9034 \r
9035                 hasMenus : function() {\r
9036                         return this.menuCount !== 0;\r
9037                 },\r
9038 \r
9039                 remove : function(o) {\r
9040                         delete this.items[o.id];\r
9041                 },\r
9042 \r
9043                 removeAll : function() {\r
9044                         var t = this;\r
9045 \r
9046                         walk(t, function(o) {\r
9047                                 if (o.removeAll)\r
9048                                         o.removeAll();\r
9049                                 else\r
9050                                         o.remove();\r
9051 \r
9052                                 o.destroy();\r
9053                         }, 'items', t);\r
9054 \r
9055                         t.items = {};\r
9056                 },\r
9057 \r
9058                 createMenu : function(o) {\r
9059                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);\r
9060 \r
9061                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);\r
9062 \r
9063                         return m;\r
9064                 }\r
9065         });\r
9066 })(tinymce);\r
9067 (function(tinymce) {\r
9068         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;\r
9069 \r
9070         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {\r
9071                 DropMenu : function(id, s) {\r
9072                         s = s || {};\r
9073                         s.container = s.container || DOM.doc.body;\r
9074                         s.offset_x = s.offset_x || 0;\r
9075                         s.offset_y = s.offset_y || 0;\r
9076                         s.vp_offset_x = s.vp_offset_x || 0;\r
9077                         s.vp_offset_y = s.vp_offset_y || 0;\r
9078 \r
9079                         if (is(s.icons) && !s.icons)\r
9080                                 s['class'] += ' mceNoIcons';\r
9081 \r
9082                         this.parent(id, s);\r
9083                         this.onShowMenu = new tinymce.util.Dispatcher(this);\r
9084                         this.onHideMenu = new tinymce.util.Dispatcher(this);\r
9085                         this.classPrefix = 'mceMenu';\r
9086                 },\r
9087 \r
9088                 createMenu : function(s) {\r
9089                         var t = this, cs = t.settings, m;\r
9090 \r
9091                         s.container = s.container || cs.container;\r
9092                         s.parent = t;\r
9093                         s.constrain = s.constrain || cs.constrain;\r
9094                         s['class'] = s['class'] || cs['class'];\r
9095                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;\r
9096                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;\r
9097                         s.keyboard_focus = cs.keyboard_focus;\r
9098                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);\r
9099 \r
9100                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);\r
9101 \r
9102                         return m;\r
9103                 },\r
9104                 \r
9105                 focus : function() {\r
9106                         var t = this;\r
9107                         if (t.keyboardNav) {\r
9108                                 t.keyboardNav.focus();\r
9109                         }\r
9110                 },\r
9111 \r
9112                 update : function() {\r
9113                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;\r
9114 \r
9115                         tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;\r
9116                         th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;\r
9117 \r
9118                         if (!DOM.boxModel)\r
9119                                 t.element.setStyles({width : tw + 2, height : th + 2});\r
9120                         else\r
9121                                 t.element.setStyles({width : tw, height : th});\r
9122 \r
9123                         if (s.max_width)\r
9124                                 DOM.setStyle(co, 'width', tw);\r
9125 \r
9126                         if (s.max_height) {\r
9127                                 DOM.setStyle(co, 'height', th);\r
9128 \r
9129                                 if (tb.clientHeight < s.max_height)\r
9130                                         DOM.setStyle(co, 'overflow', 'hidden');\r
9131                         }\r
9132                 },\r
9133 \r
9134                 showMenu : function(x, y, px) {\r
9135                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;\r
9136 \r
9137                         t.collapse(1);\r
9138 \r
9139                         if (t.isMenuVisible)\r
9140                                 return;\r
9141 \r
9142                         if (!t.rendered) {\r
9143                                 co = DOM.add(t.settings.container, t.renderNode());\r
9144 \r
9145                                 each(t.items, function(o) {\r
9146                                         o.postRender();\r
9147                                 });\r
9148 \r
9149                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
9150                         } else\r
9151                                 co = DOM.get('menu_' + t.id);\r
9152 \r
9153                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug\r
9154                         if (!tinymce.isOpera)\r
9155                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});\r
9156 \r
9157                         DOM.show(co);\r
9158                         t.update();\r
9159 \r
9160                         x += s.offset_x || 0;\r
9161                         y += s.offset_y || 0;\r
9162                         vp.w -= 4;\r
9163                         vp.h -= 4;\r
9164 \r
9165                         // Move inside viewport if not submenu\r
9166                         if (s.constrain) {\r
9167                                 w = co.clientWidth - ot;\r
9168                                 h = co.clientHeight - ot;\r
9169                                 mx = vp.x + vp.w;\r
9170                                 my = vp.y + vp.h;\r
9171 \r
9172                                 if ((x + s.vp_offset_x + w) > mx)\r
9173                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);\r
9174 \r
9175                                 if ((y + s.vp_offset_y + h) > my)\r
9176                                         y = Math.max(0, (my - s.vp_offset_y) - h);\r
9177                         }\r
9178 \r
9179                         DOM.setStyles(co, {left : x , top : y});\r
9180                         t.element.update();\r
9181 \r
9182                         t.isMenuVisible = 1;\r
9183                         t.mouseClickFunc = Event.add(co, 'click', function(e) {\r
9184                                 var m;\r
9185 \r
9186                                 e = e.target;\r
9187 \r
9188                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {\r
9189                                         m = t.items[e.id];\r
9190 \r
9191                                         if (m.isDisabled())\r
9192                                                 return;\r
9193 \r
9194                                         dm = t;\r
9195 \r
9196                                         while (dm) {\r
9197                                                 if (dm.hideMenu)\r
9198                                                         dm.hideMenu();\r
9199 \r
9200                                                 dm = dm.settings.parent;\r
9201                                         }\r
9202 \r
9203                                         if (m.settings.onclick)\r
9204                                                 m.settings.onclick(e);\r
9205 \r
9206                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
9207                                 }\r
9208                         });\r
9209 \r
9210                         if (t.hasMenus()) {\r
9211                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {\r
9212                                         var m, r, mi;\r
9213 \r
9214                                         e = e.target;\r
9215                                         if (e && (e = DOM.getParent(e, 'tr'))) {\r
9216                                                 m = t.items[e.id];\r
9217 \r
9218                                                 if (t.lastMenu)\r
9219                                                         t.lastMenu.collapse(1);\r
9220 \r
9221                                                 if (m.isDisabled())\r
9222                                                         return;\r
9223 \r
9224                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {\r
9225                                                         //p = DOM.getPos(s.container);\r
9226                                                         r = DOM.getRect(e);\r
9227                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);\r
9228                                                         t.lastMenu = m;\r
9229                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');\r
9230                                                 }\r
9231                                         }\r
9232                                 });\r
9233                         }\r
9234                         \r
9235                         Event.add(co, 'keydown', t._keyHandler, t);\r
9236 \r
9237                         t.onShowMenu.dispatch(t);\r
9238 \r
9239                         if (s.keyboard_focus) { \r
9240                                 t._setupKeyboardNav(); \r
9241                         }\r
9242                 },\r
9243 \r
9244                 hideMenu : function(c) {\r
9245                         var t = this, co = DOM.get('menu_' + t.id), e;\r
9246 \r
9247                         if (!t.isMenuVisible)\r
9248                                 return;\r
9249 \r
9250                         if (t.keyboardNav) t.keyboardNav.destroy();\r
9251                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
9252                         Event.remove(co, 'click', t.mouseClickFunc);\r
9253                         Event.remove(co, 'keydown', t._keyHandler);\r
9254                         DOM.hide(co);\r
9255                         t.isMenuVisible = 0;\r
9256 \r
9257                         if (!c)\r
9258                                 t.collapse(1);\r
9259 \r
9260                         if (t.element)\r
9261                                 t.element.hide();\r
9262 \r
9263                         if (e = DOM.get(t.id))\r
9264                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');\r
9265 \r
9266                         t.onHideMenu.dispatch(t);\r
9267                 },\r
9268 \r
9269                 add : function(o) {\r
9270                         var t = this, co;\r
9271 \r
9272                         o = t.parent(o);\r
9273 \r
9274                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))\r
9275                                 t._add(DOM.select('tbody', co)[0], o);\r
9276 \r
9277                         return o;\r
9278                 },\r
9279 \r
9280                 collapse : function(d) {\r
9281                         this.parent(d);\r
9282                         this.hideMenu(1);\r
9283                 },\r
9284 \r
9285                 remove : function(o) {\r
9286                         DOM.remove(o.id);\r
9287                         this.destroy();\r
9288 \r
9289                         return this.parent(o);\r
9290                 },\r
9291 \r
9292                 destroy : function() {\r
9293                         var t = this, co = DOM.get('menu_' + t.id);\r
9294 \r
9295                         if (t.keyboardNav) t.keyboardNav.destroy();\r
9296                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
9297                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);\r
9298                         Event.remove(co, 'click', t.mouseClickFunc);\r
9299                         Event.remove(co, 'keydown', t._keyHandler);\r
9300 \r
9301                         if (t.element)\r
9302                                 t.element.remove();\r
9303 \r
9304                         DOM.remove(co);\r
9305                 },\r
9306 \r
9307                 renderNode : function() {\r
9308                         var t = this, s = t.settings, n, tb, co, w;\r
9309 \r
9310                         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
9311                         if (t.settings.parent) {\r
9312                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);\r
9313                         }\r
9314                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});\r
9315                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
9316 \r
9317                         if (s.menu_line)\r
9318                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});\r
9319 \r
9320 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});\r
9321                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});\r
9322                         tb = DOM.add(n, 'tbody');\r
9323 \r
9324                         each(t.items, function(o) {\r
9325                                 t._add(tb, o);\r
9326                         });\r
9327 \r
9328                         t.rendered = true;\r
9329 \r
9330                         return w;\r
9331                 },\r
9332 \r
9333                 // Internal functions\r
9334                 _setupKeyboardNav : function(){\r
9335                         var contextMenu, menuItems, t=this; \r
9336                         contextMenu = DOM.select('#menu_' + t.id)[0];\r
9337                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);\r
9338                         menuItems.splice(0,0,contextMenu);\r
9339                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({\r
9340                                 root: 'menu_' + t.id,\r
9341                                 items: menuItems,\r
9342                                 onCancel: function() {\r
9343                                         t.hideMenu();\r
9344                                 },\r
9345                                 enableUpDown: true\r
9346                         });\r
9347                         contextMenu.focus();\r
9348                 },\r
9349 \r
9350                 _keyHandler : function(evt) {\r
9351                         var t = this, e;\r
9352                         switch (evt.keyCode) {\r
9353                                 case 37: // Left\r
9354                                         if (t.settings.parent) {\r
9355                                                 t.hideMenu();\r
9356                                                 t.settings.parent.focus();\r
9357                                                 Event.cancel(evt);\r
9358                                         }\r
9359                                         break;\r
9360                                 case 39: // Right\r
9361                                         if (t.mouseOverFunc)\r
9362                                                 t.mouseOverFunc(evt);\r
9363                                         break;\r
9364                         }\r
9365                 },\r
9366 \r
9367                 _add : function(tb, o) {\r
9368                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;\r
9369 \r
9370                         if (s.separator) {\r
9371                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});\r
9372                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});\r
9373 \r
9374                                 if (n = ro.previousSibling)\r
9375                                         DOM.addClass(n, 'mceLast');\r
9376 \r
9377                                 return;\r
9378                         }\r
9379 \r
9380                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});\r
9381                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');\r
9382                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});\r
9383 \r
9384                         if (s.parent) {\r
9385                                 DOM.setAttrib(a, 'aria-haspopup', 'true');\r
9386                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);\r
9387                         }\r
9388 \r
9389                         DOM.addClass(it, s['class']);\r
9390 //                      n = DOM.add(n, 'span', {'class' : 'item'});\r
9391 \r
9392                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});\r
9393 \r
9394                         if (s.icon_src)\r
9395                                 DOM.add(ic, 'img', {src : s.icon_src});\r
9396 \r
9397                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);\r
9398 \r
9399                         if (o.settings.style)\r
9400                                 DOM.setAttrib(n, 'style', o.settings.style);\r
9401 \r
9402                         if (tb.childNodes.length == 1)\r
9403                                 DOM.addClass(ro, 'mceFirst');\r
9404 \r
9405                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))\r
9406                                 DOM.addClass(ro, 'mceFirst');\r
9407 \r
9408                         if (o.collapse)\r
9409                                 DOM.addClass(ro, cp + 'ItemSub');\r
9410 \r
9411                         if (n = ro.previousSibling)\r
9412                                 DOM.removeClass(n, 'mceLast');\r
9413 \r
9414                         DOM.addClass(ro, 'mceLast');\r
9415                 }\r
9416         });\r
9417 })(tinymce);\r
9418 (function(tinymce) {\r
9419         var DOM = tinymce.DOM;\r
9420 \r
9421         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {\r
9422                 Button : function(id, s, ed) {\r
9423                         this.parent(id, s, ed);\r
9424                         this.classPrefix = 'mceButton';\r
9425                 },\r
9426 \r
9427                 renderHTML : function() {\r
9428                         var cp = this.classPrefix, s = this.settings, h, l;\r
9429 \r
9430                         l = DOM.encode(s.label || '');\r
9431                         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
9432                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )\r
9433                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;\r
9434                         else\r
9435                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
9436 \r
9437                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; \r
9438                         h += '</a>';\r
9439                         return h;\r
9440                 },\r
9441 \r
9442                 postRender : function() {\r
9443                         var t = this, s = t.settings;\r
9444 \r
9445                         tinymce.dom.Event.add(t.id, 'click', function(e) {\r
9446                                 if (!t.isDisabled())\r
9447                                         return s.onclick.call(s.scope, e);\r
9448                         });\r
9449                 }\r
9450         });\r
9451 })(tinymce);\r
9452 \r
9453 (function(tinymce) {\r
9454         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;\r
9455 \r
9456         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {\r
9457                 ListBox : function(id, s, ed) {\r
9458                         var t = this;\r
9459 \r
9460                         t.parent(id, s, ed);\r
9461 \r
9462                         t.items = [];\r
9463 \r
9464                         t.onChange = new Dispatcher(t);\r
9465 \r
9466                         t.onPostRender = new Dispatcher(t);\r
9467 \r
9468                         t.onAdd = new Dispatcher(t);\r
9469 \r
9470                         t.onRenderMenu = new tinymce.util.Dispatcher(this);\r
9471 \r
9472                         t.classPrefix = 'mceListBox';\r
9473                 },\r
9474 \r
9475                 select : function(va) {\r
9476                         var t = this, fv, f;\r
9477 \r
9478                         if (va == undefined)\r
9479                                 return t.selectByIndex(-1);\r
9480 \r
9481                         // Is string or number make function selector\r
9482                         if (va && va.call)\r
9483                                 f = va;\r
9484                         else {\r
9485                                 f = function(v) {\r
9486                                         return v == va;\r
9487                                 };\r
9488                         }\r
9489 \r
9490                         // Do we need to do something?\r
9491                         if (va != t.selectedValue) {\r
9492                                 // Find item\r
9493                                 each(t.items, function(o, i) {\r
9494                                         if (f(o.value)) {\r
9495                                                 fv = 1;\r
9496                                                 t.selectByIndex(i);\r
9497                                                 return false;\r
9498                                         }\r
9499                                 });\r
9500 \r
9501                                 if (!fv)\r
9502                                         t.selectByIndex(-1);\r
9503                         }\r
9504                 },\r
9505 \r
9506                 selectByIndex : function(idx) {\r
9507                         var t = this, e, o;\r
9508 \r
9509                         if (idx != t.selectedIndex) {\r
9510                                 e = DOM.get(t.id + '_text');\r
9511                                 o = t.items[idx];\r
9512 \r
9513                                 if (o) {\r
9514                                         t.selectedValue = o.value;\r
9515                                         t.selectedIndex = idx;\r
9516                                         DOM.setHTML(e, DOM.encode(o.title));\r
9517                                         DOM.removeClass(e, 'mceTitle');\r
9518                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);\r
9519                                 } else {\r
9520                                         DOM.setHTML(e, DOM.encode(t.settings.title));\r
9521                                         DOM.addClass(e, 'mceTitle');\r
9522                                         t.selectedValue = t.selectedIndex = null;\r
9523                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);\r
9524                                 }\r
9525                                 e = 0;\r
9526                         }\r
9527                 },\r
9528 \r
9529                 add : function(n, v, o) {\r
9530                         var t = this;\r
9531 \r
9532                         o = o || {};\r
9533                         o = tinymce.extend(o, {\r
9534                                 title : n,\r
9535                                 value : v\r
9536                         });\r
9537 \r
9538                         t.items.push(o);\r
9539                         t.onAdd.dispatch(t, o);\r
9540                 },\r
9541 \r
9542                 getLength : function() {\r
9543                         return this.items.length;\r
9544                 },\r
9545 \r
9546                 renderHTML : function() {\r
9547                         var h = '', t = this, s = t.settings, cp = t.classPrefix;\r
9548 \r
9549                         h = '<span role="button" aria-haspopup="true" aria-labelledby="' + t.id +'_text" 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
9550                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); \r
9551                         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
9552                         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
9553                         h += '</tr></tbody></table></span>';\r
9554 \r
9555                         return h;\r
9556                 },\r
9557 \r
9558                 showMenu : function() {\r
9559                         var t = this, p2, e = DOM.get(this.id), m;\r
9560 \r
9561                         if (t.isDisabled() || t.items.length == 0)\r
9562                                 return;\r
9563 \r
9564                         if (t.menu && t.menu.isMenuVisible)\r
9565                                 return t.hideMenu();\r
9566 \r
9567                         if (!t.isMenuRendered) {\r
9568                                 t.renderMenu();\r
9569                                 t.isMenuRendered = true;\r
9570                         }\r
9571 \r
9572                         p2 = DOM.getPos(e);\r
9573 \r
9574                         m = t.menu;\r
9575                         m.settings.offset_x = p2.x;\r
9576                         m.settings.offset_y = p2.y;\r
9577                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus\r
9578 \r
9579                         // Select in menu\r
9580                         if (t.oldID)\r
9581                                 m.items[t.oldID].setSelected(0);\r
9582 \r
9583                         each(t.items, function(o) {\r
9584                                 if (o.value === t.selectedValue) {\r
9585                                         m.items[o.id].setSelected(1);\r
9586                                         t.oldID = o.id;\r
9587                                 }\r
9588                         });\r
9589 \r
9590                         m.showMenu(0, e.clientHeight);\r
9591 \r
9592                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
9593                         DOM.addClass(t.id, t.classPrefix + 'Selected');\r
9594 \r
9595                         //DOM.get(t.id + '_text').focus();\r
9596                 },\r
9597 \r
9598                 hideMenu : function(e) {\r
9599                         var t = this;\r
9600 \r
9601                         if (t.menu && t.menu.isMenuVisible) {\r
9602                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
9603 \r
9604                                 // Prevent double toogles by canceling the mouse click event to the button\r
9605                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))\r
9606                                         return;\r
9607 \r
9608                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
9609                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
9610                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
9611                                         t.menu.hideMenu();\r
9612                                 }\r
9613                         }\r
9614                 },\r
9615 \r
9616                 renderMenu : function() {\r
9617                         var t = this, m;\r
9618 \r
9619                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
9620                                 menu_line : 1,\r
9621                                 'class' : t.classPrefix + 'Menu mceNoIcons',\r
9622                                 max_width : 150,\r
9623                                 max_height : 150\r
9624                         });\r
9625 \r
9626                         m.onHideMenu.add(function() {\r
9627                                 t.hideMenu();\r
9628                                 t.focus();\r
9629                         });\r
9630 \r
9631                         m.add({\r
9632                                 title : t.settings.title,\r
9633                                 'class' : 'mceMenuItemTitle',\r
9634                                 onclick : function() {\r
9635                                         if (t.settings.onselect('') !== false)\r
9636                                                 t.select(''); // Must be runned after\r
9637                                 }\r
9638                         });\r
9639 \r
9640                         each(t.items, function(o) {\r
9641                                 // No value then treat it as a title\r
9642                                 if (o.value === undefined) {\r
9643                                         m.add({\r
9644                                                 title : o.title,\r
9645                                                 'class' : 'mceMenuItemTitle',\r
9646                                                 onclick : function() {\r
9647                                                         if (t.settings.onselect('') !== false)\r
9648                                                                 t.select(''); // Must be runned after\r
9649                                                 }\r
9650                                         });\r
9651                                 } else {\r
9652                                         o.id = DOM.uniqueId();\r
9653                                         o.onclick = function() {\r
9654                                                 if (t.settings.onselect(o.value) !== false)\r
9655                                                         t.select(o.value); // Must be runned after\r
9656                                         };\r
9657 \r
9658                                         m.add(o);\r
9659                                 }\r
9660                         });\r
9661 \r
9662                         t.onRenderMenu.dispatch(t, m);\r
9663                         t.menu = m;\r
9664                 },\r
9665 \r
9666                 postRender : function() {\r
9667                         var t = this, cp = t.classPrefix;\r
9668 \r
9669                         Event.add(t.id, 'click', t.showMenu, t);\r
9670                         Event.add(t.id, 'keydown', function(evt) {\r
9671                                 if (evt.keyCode == 32) { // Space\r
9672                                         t.showMenu(evt);\r
9673                                         Event.cancel(evt);\r
9674                                 }\r
9675                         });\r
9676                         Event.add(t.id, 'focus', function() {\r
9677                                 if (!t._focused) {\r
9678                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {\r
9679                                                 if (e.keyCode == 40) {\r
9680                                                         t.showMenu();\r
9681                                                         Event.cancel(e);\r
9682                                                 }\r
9683                                         });\r
9684                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {\r
9685                                                 var v;\r
9686                                                 if (e.keyCode == 13) {\r
9687                                                         // Fake select on enter\r
9688                                                         v = t.selectedValue;\r
9689                                                         t.selectedValue = null; // Needs to be null to fake change\r
9690                                                         Event.cancel(e);\r
9691                                                         t.settings.onselect(v);\r
9692                                                 }\r
9693                                         });\r
9694                                 }\r
9695 \r
9696                                 t._focused = 1;\r
9697                         });\r
9698                         Event.add(t.id, 'blur', function() {\r
9699                                 Event.remove(t.id, 'keydown', t.keyDownHandler);\r
9700                                 Event.remove(t.id, 'keypress', t.keyPressHandler);\r
9701                                 t._focused = 0;\r
9702                         });\r
9703 \r
9704                         // Old IE doesn't have hover on all elements\r
9705                         if (tinymce.isIE6 || !DOM.boxModel) {\r
9706                                 Event.add(t.id, 'mouseover', function() {\r
9707                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
9708                                                 DOM.addClass(t.id, cp + 'Hover');\r
9709                                 });\r
9710 \r
9711                                 Event.add(t.id, 'mouseout', function() {\r
9712                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
9713                                                 DOM.removeClass(t.id, cp + 'Hover');\r
9714                                 });\r
9715                         }\r
9716 \r
9717                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
9718                 },\r
9719 \r
9720                 destroy : function() {\r
9721                         this.parent();\r
9722 \r
9723                         Event.clear(this.id + '_text');\r
9724                         Event.clear(this.id + '_open');\r
9725                 }\r
9726         });\r
9727 })(tinymce);\r
9728 (function(tinymce) {\r
9729         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;\r
9730 \r
9731         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {\r
9732                 NativeListBox : function(id, s) {\r
9733                         this.parent(id, s);\r
9734                         this.classPrefix = 'mceNativeListBox';\r
9735                 },\r
9736 \r
9737                 setDisabled : function(s) {\r
9738                         DOM.get(this.id).disabled = s;\r
9739                         this.setAriaProperty('disabled', s);\r
9740                 },\r
9741 \r
9742                 isDisabled : function() {\r
9743                         return DOM.get(this.id).disabled;\r
9744                 },\r
9745 \r
9746                 select : function(va) {\r
9747                         var t = this, fv, f;\r
9748 \r
9749                         if (va == undefined)\r
9750                                 return t.selectByIndex(-1);\r
9751 \r
9752                         // Is string or number make function selector\r
9753                         if (va && va.call)\r
9754                                 f = va;\r
9755                         else {\r
9756                                 f = function(v) {\r
9757                                         return v == va;\r
9758                                 };\r
9759                         }\r
9760 \r
9761                         // Do we need to do something?\r
9762                         if (va != t.selectedValue) {\r
9763                                 // Find item\r
9764                                 each(t.items, function(o, i) {\r
9765                                         if (f(o.value)) {\r
9766                                                 fv = 1;\r
9767                                                 t.selectByIndex(i);\r
9768                                                 return false;\r
9769                                         }\r
9770                                 });\r
9771 \r
9772                                 if (!fv)\r
9773                                         t.selectByIndex(-1);\r
9774                         }\r
9775                 },\r
9776 \r
9777                 selectByIndex : function(idx) {\r
9778                         DOM.get(this.id).selectedIndex = idx + 1;\r
9779                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;\r
9780                 },\r
9781 \r
9782                 add : function(n, v, a) {\r
9783                         var o, t = this;\r
9784 \r
9785                         a = a || {};\r
9786                         a.value = v;\r
9787 \r
9788                         if (t.isRendered())\r
9789                                 DOM.add(DOM.get(this.id), 'option', a, n);\r
9790 \r
9791                         o = {\r
9792                                 title : n,\r
9793                                 value : v,\r
9794                                 attribs : a\r
9795                         };\r
9796 \r
9797                         t.items.push(o);\r
9798                         t.onAdd.dispatch(t, o);\r
9799                 },\r
9800 \r
9801                 getLength : function() {\r
9802                         return this.items.length;\r
9803                 },\r
9804 \r
9805                 renderHTML : function() {\r
9806                         var h, t = this;\r
9807 \r
9808                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');\r
9809 \r
9810                         each(t.items, function(it) {\r
9811                                 h += DOM.createHTML('option', {value : it.value}, it.title);\r
9812                         });\r
9813 \r
9814                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);\r
9815                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);\r
9816                         return h;\r
9817                 },\r
9818 \r
9819                 postRender : function() {\r
9820                         var t = this, ch, changeListenerAdded = true;\r
9821 \r
9822                         t.rendered = true;\r
9823 \r
9824                         function onChange(e) {\r
9825                                 var v = t.items[e.target.selectedIndex - 1];\r
9826 \r
9827                                 if (v && (v = v.value)) {\r
9828                                         t.onChange.dispatch(t, v);\r
9829 \r
9830                                         if (t.settings.onselect)\r
9831                                                 t.settings.onselect(v);\r
9832                                 }\r
9833                         };\r
9834 \r
9835                         Event.add(t.id, 'change', onChange);\r
9836 \r
9837                         // Accessibility keyhandler\r
9838                         Event.add(t.id, 'keydown', function(e) {\r
9839                                 var bf;\r
9840 \r
9841                                 Event.remove(t.id, 'change', ch);\r
9842                                 changeListenerAdded = false;\r
9843 \r
9844                                 bf = Event.add(t.id, 'blur', function() {\r
9845                                         if (changeListenerAdded) return;\r
9846                                         changeListenerAdded = true;\r
9847                                         Event.add(t.id, 'change', onChange);\r
9848                                         Event.remove(t.id, 'blur', bf);\r
9849                                 });\r
9850 \r
9851                                 if (e.keyCode == 13 || e.keyCode == 32) {\r
9852                                         onChange(e);\r
9853                                         return Event.cancel(e);\r
9854                                 }\r
9855                         });\r
9856 \r
9857                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
9858                 }\r
9859         });\r
9860 })(tinymce);\r
9861 (function(tinymce) {\r
9862         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
9863 \r
9864         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {\r
9865                 MenuButton : function(id, s, ed) {\r
9866                         this.parent(id, s, ed);\r
9867 \r
9868                         this.onRenderMenu = new tinymce.util.Dispatcher(this);\r
9869 \r
9870                         s.menu_container = s.menu_container || DOM.doc.body;\r
9871                 },\r
9872 \r
9873                 showMenu : function() {\r
9874                         var t = this, p1, p2, e = DOM.get(t.id), m;\r
9875 \r
9876                         if (t.isDisabled())\r
9877                                 return;\r
9878 \r
9879                         if (!t.isMenuRendered) {\r
9880                                 t.renderMenu();\r
9881                                 t.isMenuRendered = true;\r
9882                         }\r
9883 \r
9884                         if (t.isMenuVisible)\r
9885                                 return t.hideMenu();\r
9886 \r
9887                         p1 = DOM.getPos(t.settings.menu_container);\r
9888                         p2 = DOM.getPos(e);\r
9889 \r
9890                         m = t.menu;\r
9891                         m.settings.offset_x = p2.x;\r
9892                         m.settings.offset_y = p2.y;\r
9893                         m.settings.vp_offset_x = p2.x;\r
9894                         m.settings.vp_offset_y = p2.y;\r
9895                         m.settings.keyboard_focus = t._focused;\r
9896                         m.showMenu(0, e.clientHeight);\r
9897 \r
9898                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
9899                         t.setState('Selected', 1);\r
9900 \r
9901                         t.isMenuVisible = 1;\r
9902                 },\r
9903 \r
9904                 renderMenu : function() {\r
9905                         var t = this, m;\r
9906 \r
9907                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
9908                                 menu_line : 1,\r
9909                                 'class' : this.classPrefix + 'Menu',\r
9910                                 icons : t.settings.icons\r
9911                         });\r
9912 \r
9913                         m.onHideMenu.add(function() {\r
9914                                 t.hideMenu();\r
9915                                 t.focus();\r
9916                         });\r
9917 \r
9918                         t.onRenderMenu.dispatch(t, m);\r
9919                         t.menu = m;\r
9920                 },\r
9921 \r
9922                 hideMenu : function(e) {\r
9923                         var t = this;\r
9924 \r
9925                         // Prevent double toogles by canceling the mouse click event to the button\r
9926                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))\r
9927                                 return;\r
9928 \r
9929                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
9930                                 t.setState('Selected', 0);\r
9931                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
9932                                 if (t.menu)\r
9933                                         t.menu.hideMenu();\r
9934                         }\r
9935 \r
9936                         t.isMenuVisible = 0;\r
9937                 },\r
9938 \r
9939                 postRender : function() {\r
9940                         var t = this, s = t.settings;\r
9941 \r
9942                         Event.add(t.id, 'click', function() {\r
9943                                 if (!t.isDisabled()) {\r
9944                                         if (s.onclick)\r
9945                                                 s.onclick(t.value);\r
9946 \r
9947                                         t.showMenu();\r
9948                                 }\r
9949                         });\r
9950                 }\r
9951         });\r
9952 })(tinymce);\r
9953 \r
9954 (function(tinymce) {\r
9955         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
9956 \r
9957         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {\r
9958                 SplitButton : function(id, s, ed) {\r
9959                         this.parent(id, s, ed);\r
9960                         this.classPrefix = 'mceSplitButton';\r
9961                 },\r
9962 \r
9963                 renderHTML : function() {\r
9964                         var h, t = this, s = t.settings, h1;\r
9965 \r
9966                         h = '<tbody><tr>';\r
9967 \r
9968                         if (s.image)\r
9969                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});\r
9970                         else\r
9971                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');\r
9972 \r
9973                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);\r
9974                         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
9975         \r
9976                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');\r
9977                         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
9978 \r
9979                         h += '</tr></tbody>';\r
9980                         h = DOM.createHTML('table', {id : t.id, role: 'presentation', tabindex: '0',  'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);\r
9981                         return DOM.createHTML('span', {role: 'button', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);\r
9982                 },\r
9983 \r
9984                 postRender : function() {\r
9985                         var t = this, s = t.settings, activate;\r
9986 \r
9987                         if (s.onclick) {\r
9988                                 activate = function(evt) {\r
9989                                         if (!t.isDisabled()) {\r
9990                                                 s.onclick(t.value);\r
9991                                                 Event.cancel(evt);\r
9992                                         }\r
9993                                 };\r
9994                                 Event.add(t.id + '_action', 'click', activate);\r
9995                                 Event.add(t.id, ['click', 'keydown'], function(evt) {\r
9996                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;\r
9997                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {\r
9998                                                 activate();\r
9999                                                 Event.cancel(evt);\r
10000                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {\r
10001                                                 t.showMenu();\r
10002                                                 Event.cancel(evt);\r
10003                                         }\r
10004                                 });\r
10005                         }\r
10006 \r
10007                         Event.add(t.id + '_open', 'click', function (evt) {\r
10008                                 t.showMenu();\r
10009                                 Event.cancel(evt);\r
10010                         });\r
10011                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});\r
10012                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});\r
10013 \r
10014                         // Old IE doesn't have hover on all elements\r
10015                         if (tinymce.isIE6 || !DOM.boxModel) {\r
10016                                 Event.add(t.id, 'mouseover', function() {\r
10017                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
10018                                                 DOM.addClass(t.id, 'mceSplitButtonHover');\r
10019                                 });\r
10020 \r
10021                                 Event.add(t.id, 'mouseout', function() {\r
10022                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
10023                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');\r
10024                                 });\r
10025                         }\r
10026                 },\r
10027 \r
10028                 destroy : function() {\r
10029                         this.parent();\r
10030 \r
10031                         Event.clear(this.id + '_action');\r
10032                         Event.clear(this.id + '_open');\r
10033                         Event.clear(this.id);\r
10034                 }\r
10035         });\r
10036 })(tinymce);\r
10037 \r
10038 (function(tinymce) {\r
10039         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;\r
10040 \r
10041         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {\r
10042                 ColorSplitButton : function(id, s, ed) {\r
10043                         var t = this;\r
10044 \r
10045                         t.parent(id, s, ed);\r
10046 \r
10047                         t.settings = s = tinymce.extend({\r
10048                                 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
10049                                 grid_width : 8,\r
10050                                 default_color : '#888888'\r
10051                         }, t.settings);\r
10052 \r
10053                         t.onShowMenu = new tinymce.util.Dispatcher(t);\r
10054 \r
10055                         t.onHideMenu = new tinymce.util.Dispatcher(t);\r
10056 \r
10057                         t.value = s.default_color;\r
10058                 },\r
10059 \r
10060                 showMenu : function() {\r
10061                         var t = this, r, p, e, p2;\r
10062 \r
10063                         if (t.isDisabled())\r
10064                                 return;\r
10065 \r
10066                         if (!t.isMenuRendered) {\r
10067                                 t.renderMenu();\r
10068                                 t.isMenuRendered = true;\r
10069                         }\r
10070 \r
10071                         if (t.isMenuVisible)\r
10072                                 return t.hideMenu();\r
10073 \r
10074                         e = DOM.get(t.id);\r
10075                         DOM.show(t.id + '_menu');\r
10076                         DOM.addClass(e, 'mceSplitButtonSelected');\r
10077                         p2 = DOM.getPos(e);\r
10078                         DOM.setStyles(t.id + '_menu', {\r
10079                                 left : p2.x,\r
10080                                 top : p2.y + e.clientHeight,\r
10081                                 zIndex : 200000\r
10082                         });\r
10083                         e = 0;\r
10084 \r
10085                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
10086                         t.onShowMenu.dispatch(t);\r
10087 \r
10088                         if (t._focused) {\r
10089                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {\r
10090                                         if (e.keyCode == 27)\r
10091                                                 t.hideMenu();\r
10092                                 });\r
10093 \r
10094                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link\r
10095                         }\r
10096 \r
10097                         t.isMenuVisible = 1;\r
10098                 },\r
10099 \r
10100                 hideMenu : function(e) {\r
10101                         var t = this;\r
10102 \r
10103                         if (t.isMenuVisible) {\r
10104                                 // Prevent double toogles by canceling the mouse click event to the button\r
10105                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))\r
10106                                         return;\r
10107 \r
10108                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {\r
10109                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');\r
10110                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
10111                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);\r
10112                                         DOM.hide(t.id + '_menu');\r
10113                                 }\r
10114 \r
10115                                 t.isMenuVisible = 0;\r
10116                         }\r
10117                 },\r
10118 \r
10119                 renderMenu : function() {\r
10120                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;\r
10121 \r
10122                         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
10123                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});\r
10124                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});\r
10125 \r
10126                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});\r
10127                         tb = DOM.add(n, 'tbody');\r
10128 \r
10129                         // Generate color grid\r
10130                         i = 0;\r
10131                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {\r
10132                                 c = c.replace(/^#/, '');\r
10133 \r
10134                                 if (!i--) {\r
10135                                         tr = DOM.add(tb, 'tr');\r
10136                                         i = s.grid_width - 1;\r
10137                                 }\r
10138 \r
10139                                 n = DOM.add(tr, 'td');\r
10140                                 n = DOM.add(n, 'a', {\r
10141                                         role : 'option',\r
10142                                         href : 'javascript:;',\r
10143                                         style : {\r
10144                                                 backgroundColor : '#' + c\r
10145                                         },\r
10146                                         'title': t.editor.getLang('colors.' + c, c),\r
10147                                         'data-mce-color' : '#' + c\r
10148                                 });\r
10149 \r
10150                                 if (t.editor.forcedHighContrastMode) {\r
10151                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });\r
10152                                         if (n.getContext && (context = n.getContext("2d"))) {\r
10153                                                 context.fillStyle = '#' + c;\r
10154                                                 context.fillRect(0, 0, 16, 16);\r
10155                                         } else {\r
10156                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.\r
10157                                                 DOM.remove(n);\r
10158                                         }\r
10159                                 }\r
10160                         });\r
10161 \r
10162                         if (s.more_colors_func) {\r
10163                                 n = DOM.add(tb, 'tr');\r
10164                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});\r
10165                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);\r
10166 \r
10167                                 Event.add(n, 'click', function(e) {\r
10168                                         s.more_colors_func.call(s.more_colors_scope || this);\r
10169                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
10170                                 });\r
10171                         }\r
10172 \r
10173                         DOM.addClass(m, 'mceColorSplitMenu');\r
10174                         \r
10175                         new tinymce.ui.KeyboardNavigation({\r
10176                                 root: t.id + '_menu',\r
10177                                 items: DOM.select('a', t.id + '_menu'),\r
10178                                 onCancel: function() {\r
10179                                         t.hideMenu();\r
10180                                         t.focus();\r
10181                                 }\r
10182                         });\r
10183 \r
10184                         // Prevent IE from scrolling and hindering click to occur #4019\r
10185                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});\r
10186 \r
10187                         Event.add(t.id + '_menu', 'click', function(e) {\r
10188                                 var c;\r
10189 \r
10190                                 e = DOM.getParent(e.target, 'a', tb);\r
10191 \r
10192                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))\r
10193                                         t.setColor(c);\r
10194 \r
10195                                 return Event.cancel(e); // Prevent IE auto save warning\r
10196                         });\r
10197 \r
10198                         return w;\r
10199                 },\r
10200 \r
10201                 setColor : function(c) {\r
10202                         this.displayColor(c);\r
10203                         this.hideMenu();\r
10204                         this.settings.onselect(c);\r
10205                 },\r
10206                 \r
10207                 displayColor : function(c) {\r
10208                         var t = this;\r
10209 \r
10210                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);\r
10211 \r
10212                         t.value = c;\r
10213                 },\r
10214 \r
10215                 postRender : function() {\r
10216                         var t = this, id = t.id;\r
10217 \r
10218                         t.parent();\r
10219                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});\r
10220                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);\r
10221                 },\r
10222 \r
10223                 destroy : function() {\r
10224                         this.parent();\r
10225 \r
10226                         Event.clear(this.id + '_menu');\r
10227                         Event.clear(this.id + '_more');\r
10228                         DOM.remove(this.id + '_menu');\r
10229                 }\r
10230         });\r
10231 })(tinymce);\r
10232 \r
10233 (function(tinymce) {\r
10234 // Shorten class names\r
10235 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;\r
10236 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {\r
10237         renderHTML : function() {\r
10238                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;\r
10239 \r
10240                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');\r
10241                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.\r
10242                 h.push("<span role='application'>");\r
10243                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');\r
10244                 each(controls, function(toolbar) {\r
10245                         h.push(toolbar.renderHTML());\r
10246                 });\r
10247                 h.push("</span>");\r
10248                 h.push('</div>');\r
10249 \r
10250                 return h.join('');\r
10251         },\r
10252         \r
10253         focus : function() {\r
10254                 this.keyNav.focus();\r
10255         },\r
10256         \r
10257         postRender : function() {\r
10258                 var t = this, items = [];\r
10259 \r
10260                 each(t.controls, function(toolbar) {\r
10261                         each (toolbar.controls, function(control) {\r
10262                                 if (control.id) {\r
10263                                         items.push(control);\r
10264                                 }\r
10265                         });\r
10266                 });\r
10267 \r
10268                 t.keyNav = new tinymce.ui.KeyboardNavigation({\r
10269                         root: t.id,\r
10270                         items: items,\r
10271                         onCancel: function() {\r
10272                                 t.editor.focus();\r
10273                         },\r
10274                         excludeFromTabOrder: !t.settings.tab_focus_toolbar\r
10275                 });\r
10276         },\r
10277         \r
10278         destroy : function() {\r
10279                 var self = this;\r
10280 \r
10281                 self.parent();\r
10282                 self.keyNav.destroy();\r
10283                 Event.clear(self.id);\r
10284         }\r
10285 });\r
10286 })(tinymce);\r
10287 \r
10288 (function(tinymce) {\r
10289 // Shorten class names\r
10290 var dom = tinymce.DOM, each = tinymce.each;\r
10291 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {\r
10292         renderHTML : function() {\r
10293                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;\r
10294 \r
10295                 cl = t.controls;\r
10296                 for (i=0; i<cl.length; i++) {\r
10297                         // Get current control, prev control, next control and if the control is a list box or not\r
10298                         co = cl[i];\r
10299                         pr = cl[i - 1];\r
10300                         nx = cl[i + 1];\r
10301 \r
10302                         // Add toolbar start\r
10303                         if (i === 0) {\r
10304                                 c = 'mceToolbarStart';\r
10305 \r
10306                                 if (co.Button)\r
10307                                         c += ' mceToolbarStartButton';\r
10308                                 else if (co.SplitButton)\r
10309                                         c += ' mceToolbarStartSplitButton';\r
10310                                 else if (co.ListBox)\r
10311                                         c += ' mceToolbarStartListBox';\r
10312 \r
10313                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
10314                         }\r
10315 \r
10316                         // Add toolbar end before list box and after the previous button\r
10317                         // This is to fix the o2k7 editor skins\r
10318                         if (pr && co.ListBox) {\r
10319                                 if (pr.Button || pr.SplitButton)\r
10320                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));\r
10321                         }\r
10322 \r
10323                         // Render control HTML\r
10324 \r
10325                         // IE 8 quick fix, needed to propertly generate a hit area for anchors\r
10326                         if (dom.stdMode)\r
10327                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';\r
10328                         else\r
10329                                 h += '<td>' + co.renderHTML() + '</td>';\r
10330 \r
10331                         // Add toolbar start after list box and before the next button\r
10332                         // This is to fix the o2k7 editor skins\r
10333                         if (nx && co.ListBox) {\r
10334                                 if (nx.Button || nx.SplitButton)\r
10335                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));\r
10336                         }\r
10337                 }\r
10338 \r
10339                 c = 'mceToolbarEnd';\r
10340 \r
10341                 if (co.Button)\r
10342                         c += ' mceToolbarEndButton';\r
10343                 else if (co.SplitButton)\r
10344                         c += ' mceToolbarEndSplitButton';\r
10345                 else if (co.ListBox)\r
10346                         c += ' mceToolbarEndListBox';\r
10347 \r
10348                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
10349 \r
10350                 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
10351         }\r
10352 });\r
10353 })(tinymce);\r
10354 \r
10355 (function(tinymce) {\r
10356         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;\r
10357 \r
10358         tinymce.create('tinymce.AddOnManager', {\r
10359                 AddOnManager : function() {\r
10360                         var self = this;\r
10361 \r
10362                         self.items = [];\r
10363                         self.urls = {};\r
10364                         self.lookup = {};\r
10365                         self.onAdd = new Dispatcher(self);\r
10366                 },\r
10367 \r
10368                 get : function(n) {\r
10369                         if (this.lookup[n]) {\r
10370                                 return this.lookup[n].instance;\r
10371                         } else {\r
10372                                 return undefined;\r
10373                         }\r
10374                 },\r
10375 \r
10376                 dependencies : function(n) {\r
10377                         var result;\r
10378                         if (this.lookup[n]) {\r
10379                                 result = this.lookup[n].dependencies;\r
10380                         }\r
10381                         return result || [];\r
10382                 },\r
10383 \r
10384                 requireLangPack : function(n) {\r
10385                         var s = tinymce.settings;\r
10386 \r
10387                         if (s && s.language && s.language_load !== false)\r
10388                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');\r
10389                 },\r
10390 \r
10391                 add : function(id, o, dependencies) {\r
10392                         this.items.push(o);\r
10393                         this.lookup[id] = {instance:o, dependencies:dependencies};\r
10394                         this.onAdd.dispatch(this, id, o);\r
10395 \r
10396                         return o;\r
10397                 },\r
10398                 createUrl: function(baseUrl, dep) {\r
10399                         if (typeof dep === "object") {\r
10400                                 return dep\r
10401                         } else {\r
10402                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};\r
10403                         }\r
10404                 },\r
10405 \r
10406                 addComponents: function(pluginName, scripts) {\r
10407                         var pluginUrl = this.urls[pluginName];\r
10408                         tinymce.each(scripts, function(script){\r
10409                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); \r
10410                         });\r
10411                 },\r
10412 \r
10413                 load : function(n, u, cb, s) {\r
10414                         var t = this, url = u;\r
10415 \r
10416                         function loadDependencies() {\r
10417                                 var dependencies = t.dependencies(n);\r
10418                                 tinymce.each(dependencies, function(dep) {\r
10419                                         var newUrl = t.createUrl(u, dep);\r
10420                                         t.load(newUrl.resource, newUrl, undefined, undefined);\r
10421                                 });\r
10422                                 if (cb) {\r
10423                                         if (s) {\r
10424                                                 cb.call(s);\r
10425                                         } else {\r
10426                                                 cb.call(tinymce.ScriptLoader);\r
10427                                         }\r
10428                                 }\r
10429                         }\r
10430 \r
10431                         if (t.urls[n])\r
10432                                 return;\r
10433                         if (typeof u === "object")\r
10434                                 url = u.prefix + u.resource + u.suffix;\r
10435 \r
10436                         if (url.indexOf('/') != 0 && url.indexOf('://') == -1)\r
10437                                 url = tinymce.baseURL + '/' + url;\r
10438 \r
10439                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));\r
10440 \r
10441                         if (t.lookup[n]) {\r
10442                                 loadDependencies();\r
10443                         } else {\r
10444                                 tinymce.ScriptLoader.add(url, loadDependencies, s);\r
10445                         }\r
10446                 }\r
10447         });\r
10448 \r
10449         // Create plugin and theme managers\r
10450         tinymce.PluginManager = new tinymce.AddOnManager();\r
10451         tinymce.ThemeManager = new tinymce.AddOnManager();\r
10452 }(tinymce));\r
10453 \r
10454 (function(tinymce) {\r
10455         // Shorten names\r
10456         var each = tinymce.each, extend = tinymce.extend,\r
10457                 DOM = tinymce.DOM, Event = tinymce.dom.Event,\r
10458                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
10459                 explode = tinymce.explode,\r
10460                 Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;\r
10461 \r
10462         // Setup some URLs where the editor API is located and where the document is\r
10463         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');\r
10464         if (!/[\/\\]$/.test(tinymce.documentBaseURL))\r
10465                 tinymce.documentBaseURL += '/';\r
10466 \r
10467         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);\r
10468 \r
10469         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);\r
10470 \r
10471         // Add before unload listener\r
10472         // This was required since IE was leaking memory if you added and removed beforeunload listeners\r
10473         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event\r
10474         tinymce.onBeforeUnload = new Dispatcher(tinymce);\r
10475 \r
10476         // Must be on window or IE will leak if the editor is placed in frame or iframe\r
10477         Event.add(window, 'beforeunload', function(e) {\r
10478                 tinymce.onBeforeUnload.dispatch(tinymce, e);\r
10479         });\r
10480 \r
10481         tinymce.onAddEditor = new Dispatcher(tinymce);\r
10482 \r
10483         tinymce.onRemoveEditor = new Dispatcher(tinymce);\r
10484 \r
10485         tinymce.EditorManager = extend(tinymce, {\r
10486                 editors : [],\r
10487 \r
10488                 i18n : {},\r
10489 \r
10490                 activeEditor : null,\r
10491 \r
10492                 init : function(s) {\r
10493                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;\r
10494 \r
10495                         function execCallback(se, n, s) {\r
10496                                 var f = se[n];\r
10497 \r
10498                                 if (!f)\r
10499                                         return;\r
10500 \r
10501                                 if (tinymce.is(f, 'string')) {\r
10502                                         s = f.replace(/\.\w+$/, '');\r
10503                                         s = s ? tinymce.resolve(s) : 0;\r
10504                                         f = tinymce.resolve(f);\r
10505                                 }\r
10506 \r
10507                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));\r
10508                         };\r
10509 \r
10510                         s = extend({\r
10511                                 theme : "simple",\r
10512                                 language : "en"\r
10513                         }, s);\r
10514 \r
10515                         t.settings = s;\r
10516 \r
10517                         // Legacy call\r
10518                         Event.add(document, 'init', function() {\r
10519                                 var l, co;\r
10520 \r
10521                                 execCallback(s, 'onpageload');\r
10522 \r
10523                                 switch (s.mode) {\r
10524                                         case "exact":\r
10525                                                 l = s.elements || '';\r
10526 \r
10527                                                 if(l.length > 0) {\r
10528                                                         each(explode(l), function(v) {\r
10529                                                                 if (DOM.get(v)) {\r
10530                                                                         ed = new tinymce.Editor(v, s);\r
10531                                                                         el.push(ed);\r
10532                                                                         ed.render(1);\r
10533                                                                 } else {\r
10534                                                                         each(document.forms, function(f) {\r
10535                                                                                 each(f.elements, function(e) {\r
10536                                                                                         if (e.name === v) {\r
10537                                                                                                 v = 'mce_editor_' + instanceCounter++;\r
10538                                                                                                 DOM.setAttrib(e, 'id', v);\r
10539 \r
10540                                                                                                 ed = new tinymce.Editor(v, s);\r
10541                                                                                                 el.push(ed);\r
10542                                                                                                 ed.render(1);\r
10543                                                                                         }\r
10544                                                                                 });\r
10545                                                                         });\r
10546                                                                 }\r
10547                                                         });\r
10548                                                 }\r
10549                                                 break;\r
10550 \r
10551                                         case "textareas":\r
10552                                         case "specific_textareas":\r
10553                                                 function hasClass(n, c) {\r
10554                                                         return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);\r
10555                                                 };\r
10556 \r
10557                                                 each(DOM.select('textarea'), function(v) {\r
10558                                                         if (s.editor_deselector && hasClass(v, s.editor_deselector))\r
10559                                                                 return;\r
10560 \r
10561                                                         if (!s.editor_selector || hasClass(v, s.editor_selector)) {\r
10562                                                                 // Can we use the name\r
10563                                                                 e = DOM.get(v.name);\r
10564                                                                 if (!v.id && !e)\r
10565                                                                         v.id = v.name;\r
10566 \r
10567                                                                 // Generate unique name if missing or already exists\r
10568                                                                 if (!v.id || t.get(v.id))\r
10569                                                                         v.id = DOM.uniqueId();\r
10570 \r
10571                                                                 ed = new tinymce.Editor(v.id, s);\r
10572                                                                 el.push(ed);\r
10573                                                                 ed.render(1);\r
10574                                                         }\r
10575                                                 });\r
10576                                                 break;\r
10577                                 }\r
10578 \r
10579                                 // Call onInit when all editors are initialized\r
10580                                 if (s.oninit) {\r
10581                                         l = co = 0;\r
10582 \r
10583                                         each(el, function(ed) {\r
10584                                                 co++;\r
10585 \r
10586                                                 if (!ed.initialized) {\r
10587                                                         // Wait for it\r
10588                                                         ed.onInit.add(function() {\r
10589                                                                 l++;\r
10590 \r
10591                                                                 // All done\r
10592                                                                 if (l == co)\r
10593                                                                         execCallback(s, 'oninit');\r
10594                                                         });\r
10595                                                 } else\r
10596                                                         l++;\r
10597 \r
10598                                                 // All done\r
10599                                                 if (l == co)\r
10600                                                         execCallback(s, 'oninit');                                      \r
10601                                         });\r
10602                                 }\r
10603                         });\r
10604                 },\r
10605 \r
10606                 get : function(id) {\r
10607                         if (id === undefined)\r
10608                                 return this.editors;\r
10609 \r
10610                         return this.editors[id];\r
10611                 },\r
10612 \r
10613                 getInstanceById : function(id) {\r
10614                         return this.get(id);\r
10615                 },\r
10616 \r
10617                 add : function(editor) {\r
10618                         var self = this, editors = self.editors;\r
10619 \r
10620                         // Add named and index editor instance\r
10621                         editors[editor.id] = editor;\r
10622                         editors.push(editor);\r
10623 \r
10624                         self._setActive(editor);\r
10625                         self.onAddEditor.dispatch(self, editor);\r
10626 \r
10627 \r
10628                         return editor;\r
10629                 },\r
10630 \r
10631                 remove : function(editor) {\r
10632                         var t = this, i, editors = t.editors;\r
10633 \r
10634                         // Not in the collection\r
10635                         if (!editors[editor.id])\r
10636                                 return null;\r
10637 \r
10638                         delete editors[editor.id];\r
10639 \r
10640                         for (i = 0; i < editors.length; i++) {\r
10641                                 if (editors[i] == editor) {\r
10642                                         editors.splice(i, 1);\r
10643                                         break;\r
10644                                 }\r
10645                         }\r
10646 \r
10647                         // Select another editor since the active one was removed\r
10648                         if (t.activeEditor == editor)\r
10649                                 t._setActive(editors[0]);\r
10650 \r
10651                         editor.destroy();\r
10652                         t.onRemoveEditor.dispatch(t, editor);\r
10653 \r
10654                         return editor;\r
10655                 },\r
10656 \r
10657                 execCommand : function(c, u, v) {\r
10658                         var t = this, ed = t.get(v), w;\r
10659 \r
10660                         // Manager commands\r
10661                         switch (c) {\r
10662                                 case "mceFocus":\r
10663                                         ed.focus();\r
10664                                         return true;\r
10665 \r
10666                                 case "mceAddEditor":\r
10667                                 case "mceAddControl":\r
10668                                         if (!t.get(v))\r
10669                                                 new tinymce.Editor(v, t.settings).render();\r
10670 \r
10671                                         return true;\r
10672 \r
10673                                 case "mceAddFrameControl":\r
10674                                         w = v.window;\r
10675 \r
10676                                         // Add tinyMCE global instance and tinymce namespace to specified window\r
10677                                         w.tinyMCE = tinyMCE;\r
10678                                         w.tinymce = tinymce;\r
10679 \r
10680                                         tinymce.DOM.doc = w.document;\r
10681                                         tinymce.DOM.win = w;\r
10682 \r
10683                                         ed = new tinymce.Editor(v.element_id, v);\r
10684                                         ed.render();\r
10685 \r
10686                                         // Fix IE memory leaks\r
10687                                         if (tinymce.isIE) {\r
10688                                                 function clr() {\r
10689                                                         ed.destroy();\r
10690                                                         w.detachEvent('onunload', clr);\r
10691                                                         w = w.tinyMCE = w.tinymce = null; // IE leak\r
10692                                                 };\r
10693 \r
10694                                                 w.attachEvent('onunload', clr);\r
10695                                         }\r
10696 \r
10697                                         v.page_window = null;\r
10698 \r
10699                                         return true;\r
10700 \r
10701                                 case "mceRemoveEditor":\r
10702                                 case "mceRemoveControl":\r
10703                                         if (ed)\r
10704                                                 ed.remove();\r
10705 \r
10706                                         return true;\r
10707 \r
10708                                 case 'mceToggleEditor':\r
10709                                         if (!ed) {\r
10710                                                 t.execCommand('mceAddControl', 0, v);\r
10711                                                 return true;\r
10712                                         }\r
10713 \r
10714                                         if (ed.isHidden())\r
10715                                                 ed.show();\r
10716                                         else\r
10717                                                 ed.hide();\r
10718 \r
10719                                         return true;\r
10720                         }\r
10721 \r
10722                         // Run command on active editor\r
10723                         if (t.activeEditor)\r
10724                                 return t.activeEditor.execCommand(c, u, v);\r
10725 \r
10726                         return false;\r
10727                 },\r
10728 \r
10729                 execInstanceCommand : function(id, c, u, v) {\r
10730                         var ed = this.get(id);\r
10731 \r
10732                         if (ed)\r
10733                                 return ed.execCommand(c, u, v);\r
10734 \r
10735                         return false;\r
10736                 },\r
10737 \r
10738                 triggerSave : function() {\r
10739                         each(this.editors, function(e) {\r
10740                                 e.save();\r
10741                         });\r
10742                 },\r
10743 \r
10744                 addI18n : function(p, o) {\r
10745                         var lo, i18n = this.i18n;\r
10746 \r
10747                         if (!tinymce.is(p, 'string')) {\r
10748                                 each(p, function(o, lc) {\r
10749                                         each(o, function(o, g) {\r
10750                                                 each(o, function(o, k) {\r
10751                                                         if (g === 'common')\r
10752                                                                 i18n[lc + '.' + k] = o;\r
10753                                                         else\r
10754                                                                 i18n[lc + '.' + g + '.' + k] = o;\r
10755                                                 });\r
10756                                         });\r
10757                                 });\r
10758                         } else {\r
10759                                 each(o, function(o, k) {\r
10760                                         i18n[p + '.' + k] = o;\r
10761                                 });\r
10762                         }\r
10763                 },\r
10764 \r
10765                 // Private methods\r
10766 \r
10767                 _setActive : function(editor) {\r
10768                         this.selectedInstance = this.activeEditor = editor;\r
10769                 }\r
10770         });\r
10771 })(tinymce);\r
10772 \r
10773 (function(tinymce) {\r
10774         // Shorten these names\r
10775         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,\r
10776                 Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,\r
10777                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,\r
10778                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
10779                 inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;\r
10780 \r
10781         tinymce.create('tinymce.Editor', {\r
10782                 Editor : function(id, s) {\r
10783                         var t = this;\r
10784 \r
10785                         t.id = t.editorId = id;\r
10786 \r
10787                         t.execCommands = {};\r
10788                         t.queryStateCommands = {};\r
10789                         t.queryValueCommands = {};\r
10790 \r
10791                         t.isNotDirty = false;\r
10792 \r
10793                         t.plugins = {};\r
10794 \r
10795                         // Add events to the editor\r
10796                         each([\r
10797                                 'onPreInit',\r
10798 \r
10799                                 'onBeforeRenderUI',\r
10800 \r
10801                                 'onPostRender',\r
10802 \r
10803                                 'onInit',\r
10804 \r
10805                                 'onRemove',\r
10806 \r
10807                                 'onActivate',\r
10808 \r
10809                                 'onDeactivate',\r
10810 \r
10811                                 'onClick',\r
10812 \r
10813                                 'onEvent',\r
10814 \r
10815                                 'onMouseUp',\r
10816 \r
10817                                 'onMouseDown',\r
10818 \r
10819                                 'onDblClick',\r
10820 \r
10821                                 'onKeyDown',\r
10822 \r
10823                                 'onKeyUp',\r
10824 \r
10825                                 'onKeyPress',\r
10826 \r
10827                                 'onContextMenu',\r
10828 \r
10829                                 'onSubmit',\r
10830 \r
10831                                 'onReset',\r
10832 \r
10833                                 'onPaste',\r
10834 \r
10835                                 'onPreProcess',\r
10836 \r
10837                                 'onPostProcess',\r
10838 \r
10839                                 'onBeforeSetContent',\r
10840 \r
10841                                 'onBeforeGetContent',\r
10842 \r
10843                                 'onSetContent',\r
10844 \r
10845                                 'onGetContent',\r
10846 \r
10847                                 'onLoadContent',\r
10848 \r
10849                                 'onSaveContent',\r
10850 \r
10851                                 'onNodeChange',\r
10852 \r
10853                                 'onChange',\r
10854 \r
10855                                 'onBeforeExecCommand',\r
10856 \r
10857                                 'onExecCommand',\r
10858 \r
10859                                 'onUndo',\r
10860 \r
10861                                 'onRedo',\r
10862 \r
10863                                 'onVisualAid',\r
10864 \r
10865                                 'onSetProgressState'\r
10866                         ], function(e) {\r
10867                                 t[e] = new Dispatcher(t);\r
10868                         });\r
10869 \r
10870                         t.settings = s = extend({\r
10871                                 id : id,\r
10872                                 language : 'en',\r
10873                                 docs_language : 'en',\r
10874                                 theme : 'simple',\r
10875                                 skin : 'default',\r
10876                                 delta_width : 0,\r
10877                                 delta_height : 0,\r
10878                                 popup_css : '',\r
10879                                 plugins : '',\r
10880                                 document_base_url : tinymce.documentBaseURL,\r
10881                                 add_form_submit_trigger : 1,\r
10882                                 submit_patch : 1,\r
10883                                 add_unload_trigger : 1,\r
10884                                 convert_urls : 1,\r
10885                                 relative_urls : 1,\r
10886                                 remove_script_host : 1,\r
10887                                 table_inline_editing : 0,\r
10888                                 object_resizing : 1,\r
10889                                 cleanup : 1,\r
10890                                 accessibility_focus : 1,\r
10891                                 custom_shortcuts : 1,\r
10892                                 custom_undo_redo_keyboard_shortcuts : 1,\r
10893                                 custom_undo_redo_restore_selection : 1,\r
10894                                 custom_undo_redo : 1,\r
10895                                 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
10896                                 visual_table_class : 'mceItemTable',\r
10897                                 visual : 1,\r
10898                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',\r
10899                                 apply_source_formatting : 1,\r
10900                                 directionality : 'ltr',\r
10901                                 forced_root_block : 'p',\r
10902                                 hidden_input : 1,\r
10903                                 padd_empty_editor : 1,\r
10904                                 render_ui : 1,\r
10905                                 init_theme : 1,\r
10906                                 force_p_newlines : 1,\r
10907                                 indentation : '30px',\r
10908                                 keep_styles : 1,\r
10909                                 fix_table_elements : 1,\r
10910                                 inline_styles : 1,\r
10911                                 convert_fonts_to_spans : true,\r
10912                                 indent : 'simple',\r
10913                                 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
10914                                 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
10915                                 validate : true,\r
10916                                 entity_encoding : 'named',\r
10917                                 url_converter : t.convertURL,\r
10918                                 url_converter_scope : t,\r
10919                                 ie7_compat : true\r
10920                         }, s);\r
10921 \r
10922                         t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {\r
10923                                 base_uri : tinyMCE.baseURI\r
10924                         });\r
10925 \r
10926                         t.baseURI = tinymce.baseURI;\r
10927 \r
10928                         t.contentCSS = [];\r
10929 \r
10930                         // Call setup\r
10931                         t.execCallback('setup', t);\r
10932                 },\r
10933 \r
10934                 render : function(nst) {\r
10935                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;\r
10936 \r
10937                         // Page is not loaded yet, wait for it\r
10938                         if (!Event.domLoaded) {\r
10939                                 Event.add(document, 'init', function() {\r
10940                                         t.render();\r
10941                                 });\r
10942                                 return;\r
10943                         }\r
10944 \r
10945                         tinyMCE.settings = s;\r
10946 \r
10947                         // Element not found, then skip initialization\r
10948                         if (!t.getElement())\r
10949                                 return;\r
10950 \r
10951                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff \r
10952                         // here since the browser says it has contentEditable support but there is no visible\r
10953                         // caret We will remove this check ones Apple implements full contentEditable support\r
10954                         if (tinymce.isIDevice && !tinymce.isIOS5)\r
10955                                 return;\r
10956 \r
10957                         // Add hidden input for non input elements inside form elements\r
10958                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))\r
10959                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);\r
10960 \r
10961                         if (tinymce.WindowManager)\r
10962                                 t.windowManager = new tinymce.WindowManager(t);\r
10963 \r
10964                         if (s.encoding == 'xml') {\r
10965                                 t.onGetContent.add(function(ed, o) {\r
10966                                         if (o.save)\r
10967                                                 o.content = DOM.encode(o.content);\r
10968                                 });\r
10969                         }\r
10970 \r
10971                         if (s.add_form_submit_trigger) {\r
10972                                 t.onSubmit.addToTop(function() {\r
10973                                         if (t.initialized) {\r
10974                                                 t.save();\r
10975                                                 t.isNotDirty = 1;\r
10976                                         }\r
10977                                 });\r
10978                         }\r
10979 \r
10980                         if (s.add_unload_trigger) {\r
10981                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {\r
10982                                         if (t.initialized && !t.destroyed && !t.isHidden())\r
10983                                                 t.save({format : 'raw', no_events : true});\r
10984                                 });\r
10985                         }\r
10986 \r
10987                         tinymce.addUnload(t.destroy, t);\r
10988 \r
10989                         if (s.submit_patch) {\r
10990                                 t.onBeforeRenderUI.add(function() {\r
10991                                         var n = t.getElement().form;\r
10992 \r
10993                                         if (!n)\r
10994                                                 return;\r
10995 \r
10996                                         // Already patched\r
10997                                         if (n._mceOldSubmit)\r
10998                                                 return;\r
10999 \r
11000                                         // Check page uses id="submit" or name="submit" for it's submit button\r
11001                                         if (!n.submit.nodeType && !n.submit.length) {\r
11002                                                 t.formElement = n;\r
11003                                                 n._mceOldSubmit = n.submit;\r
11004                                                 n.submit = function() {\r
11005                                                         // Save all instances\r
11006                                                         tinymce.triggerSave();\r
11007                                                         t.isNotDirty = 1;\r
11008 \r
11009                                                         return t.formElement._mceOldSubmit(t.formElement);\r
11010                                                 };\r
11011                                         }\r
11012 \r
11013                                         n = null;\r
11014                                 });\r
11015                         }\r
11016 \r
11017                         // Load scripts\r
11018                         function loadScripts() {\r
11019                                 if (s.language && s.language_load !== false)\r
11020                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');\r
11021 \r
11022                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])\r
11023                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');\r
11024 \r
11025                                 each(explode(s.plugins), function(p) {\r
11026                                         if (p &&!PluginManager.urls[p]) {\r
11027                                                 if (p.charAt(0) == '-') {\r
11028                                                         p = p.substr(1, p.length);\r
11029                                                         var dependencies = PluginManager.dependencies(p);\r
11030                                                         each(dependencies, function(dep) {\r
11031                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};\r
11032                                                                 var dep = PluginManager.createUrl(defaultSettings, dep);\r
11033                                                                 PluginManager.load(dep.resource, dep);\r
11034                                                                 \r
11035                                                         });\r
11036                                                 } else {\r
11037                                                         // Skip safari plugin, since it is removed as of 3.3b1\r
11038                                                         if (p == 'safari') {\r
11039                                                                 return;\r
11040                                                         }\r
11041                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});\r
11042                                                 }\r
11043                                         }\r
11044                                 });\r
11045 \r
11046                                 // Init when que is loaded\r
11047                                 sl.loadQueue(function() {\r
11048                                         if (!t.removed)\r
11049                                                 t.init();\r
11050                                 });\r
11051                         };\r
11052 \r
11053                         loadScripts();\r
11054                 },\r
11055 \r
11056                 init : function() {\r
11057                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];\r
11058 \r
11059                         tinymce.add(t);\r
11060 \r
11061                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));\r
11062 \r
11063                         if (s.theme) {\r
11064                                 s.theme = s.theme.replace(/-/, '');\r
11065                                 o = ThemeManager.get(s.theme);\r
11066                                 t.theme = new o();\r
11067 \r
11068                                 if (t.theme.init && s.init_theme)\r
11069                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));\r
11070                         }\r
11071                         function initPlugin(p) {\r
11072                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;\r
11073                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {\r
11074                                         each(PluginManager.dependencies(p), function(dep){\r
11075                                                 initPlugin(dep);\r
11076                                         });\r
11077                                         po = new c(t, u);\r
11078 \r
11079                                         t.plugins[p] = po;\r
11080 \r
11081                                         if (po.init) {\r
11082                                                 po.init(t, u);\r
11083                                                 initializedPlugins.push(p);\r
11084                                         }\r
11085                                 }\r
11086                         }\r
11087                         \r
11088                         // Create all plugins\r
11089                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);\r
11090 \r
11091                         // Setup popup CSS path(s)\r
11092                         if (s.popup_css !== false) {\r
11093                                 if (s.popup_css)\r
11094                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);\r
11095                                 else\r
11096                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");\r
11097                         }\r
11098 \r
11099                         if (s.popup_css_add)\r
11100                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);\r
11101 \r
11102                         t.controlManager = new tinymce.ControlManager(t);\r
11103 \r
11104                         if (s.custom_undo_redo) {\r
11105                                 t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {\r
11106                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
11107                                                 t.undoManager.beforeChange();\r
11108                                 });\r
11109 \r
11110                                 t.onExecCommand.add(function(ed, cmd, ui, val, a) {\r
11111                                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))\r
11112                                                 t.undoManager.add();\r
11113                                 });\r
11114                         }\r
11115 \r
11116                         t.onExecCommand.add(function(ed, c) {\r
11117                                 // Don't refresh the select lists until caret move\r
11118                                 if (!/^(FontName|FontSize)$/.test(c))\r
11119                                         t.nodeChanged();\r
11120                         });\r
11121 \r
11122                         // Remove ghost selections on images and tables in Gecko\r
11123                         if (isGecko) {\r
11124                                 function repaint(a, o) {\r
11125                                         if (!o || !o.initial)\r
11126                                                 t.execCommand('mceRepaint');\r
11127                                 };\r
11128 \r
11129                                 t.onUndo.add(repaint);\r
11130                                 t.onRedo.add(repaint);\r
11131                                 t.onSetContent.add(repaint);\r
11132                         }\r
11133 \r
11134                         // Enables users to override the control factory\r
11135                         t.onBeforeRenderUI.dispatch(t, t.controlManager);\r
11136 \r
11137                         // Measure box\r
11138                         if (s.render_ui) {\r
11139                                 w = s.width || e.style.width || e.offsetWidth;\r
11140                                 h = s.height || e.style.height || e.offsetHeight;\r
11141                                 t.orgDisplay = e.style.display;\r
11142                                 re = /^[0-9\.]+(|px)$/i;\r
11143 \r
11144                                 if (re.test('' + w))\r
11145                                         w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);\r
11146 \r
11147                                 if (re.test('' + h))\r
11148                                         h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);\r
11149 \r
11150                                 // Render UI\r
11151                                 o = t.theme.renderUI({\r
11152                                         targetNode : e,\r
11153                                         width : w,\r
11154                                         height : h,\r
11155                                         deltaWidth : s.delta_width,\r
11156                                         deltaHeight : s.delta_height\r
11157                                 });\r
11158 \r
11159                                 t.editorContainer = o.editorContainer;\r
11160                         }\r
11161 \r
11162 \r
11163                         // User specified a document.domain value\r
11164                         if (document.domain && location.hostname != document.domain)\r
11165                                 tinymce.relaxedDomain = document.domain;\r
11166 \r
11167                         // Resize editor\r
11168                         DOM.setStyles(o.sizeContainer || o.editorContainer, {\r
11169                                 width : w,\r
11170                                 height : h\r
11171                         });\r
11172 \r
11173                         // Load specified content CSS last\r
11174                         if (s.content_css) {\r
11175                                 tinymce.each(explode(s.content_css), function(u) {\r
11176                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));\r
11177                                 });\r
11178                         }\r
11179 \r
11180                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');\r
11181                         if (h < 100)\r
11182                                 h = 100;\r
11183 \r
11184                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';\r
11185 \r
11186                         // We only need to override paths if we have to\r
11187                         // IE has a bug where it remove site absolute urls to relative ones if this is specified\r
11188                         if (s.document_base_url != tinymce.documentBaseURL)\r
11189                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';\r
11190 \r
11191                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.\r
11192                         if (s.ie7_compat)\r
11193                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';\r
11194                         else\r
11195                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';\r
11196 \r
11197                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';\r
11198 \r
11199                         // Firefox 2 doesn't load stylesheets correctly this way\r
11200                         if (!isGecko || !/Firefox\/2/.test(navigator.userAgent)) {\r
11201                                 for (i = 0; i < t.contentCSS.length; i++)\r
11202                                         t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';\r
11203 \r
11204                                 t.contentCSS = [];\r
11205                         }\r
11206 \r
11207                         bi = s.body_id || 'tinymce';\r
11208                         if (bi.indexOf('=') != -1) {\r
11209                                 bi = t.getParam('body_id', '', 'hash');\r
11210                                 bi = bi[t.id] || bi;\r
11211                         }\r
11212 \r
11213                         bc = s.body_class || '';\r
11214                         if (bc.indexOf('=') != -1) {\r
11215                                 bc = t.getParam('body_class', '', 'hash');\r
11216                                 bc = bc[t.id] || '';\r
11217                         }\r
11218 \r
11219                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';\r
11220 \r
11221                         // Domain relaxing enabled, then set document domain\r
11222                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {\r
11223                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button\r
11224                                 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
11225                         }\r
11226 \r
11227                         // Create iframe\r
11228                         // TODO: ACC add the appropriate description on this.\r
11229                         n = DOM.add(o.iframeContainer, 'iframe', { \r
11230                                 id : t.id + "_ifr",\r
11231                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7\r
11232                                 frameBorder : '0',\r
11233                                 allowTransparency : "true",\r
11234                                 title : s.aria_label,\r
11235                                 style : {\r
11236                                         width : '100%',\r
11237                                         height : h\r
11238                                 }\r
11239                         });\r
11240 \r
11241                         t.contentAreaContainer = o.iframeContainer;\r
11242                         DOM.get(o.editorContainer).style.display = t.orgDisplay;\r
11243                         DOM.get(t.id).style.display = 'none';\r
11244                         DOM.setAttrib(t.id, 'aria-hidden', true);\r
11245 \r
11246                         if (!tinymce.relaxedDomain || !u)\r
11247                                 t.setupIframe();\r
11248 \r
11249                         e = n = o = null; // Cleanup\r
11250                 },\r
11251 \r
11252                 setupIframe : function(filled) {\r
11253                         var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;\r
11254 \r
11255                         // Setup iframe body\r
11256                         if ((!isIE || !tinymce.relaxedDomain) && !filled) {\r
11257                                 // We need to wait for the load event on Gecko\r
11258                                 if (isGecko && !s.readonly) {\r
11259                                         t.getWin().onload = function() {\r
11260                                                 window.setTimeout(function() {\r
11261                                                         var b = t.getBody(), undef;\r
11262 \r
11263                                                         // Editable element needs to have some contents or backspace/delete won't work properly for some odd reason on FF 3.6 or older\r
11264                                                         b.innerHTML = '<br>';\r
11265 \r
11266                                                         // Check if Gecko supports contentEditable mode FF2 doesn't\r
11267                                                         if (b.contentEditable !== undef) {\r
11268                                                                 // Setting the contentEditable off/on seems to force caret mode in the editor and enabled auto focus\r
11269                                                                 b.contentEditable = false;\r
11270                                                                 b.contentEditable = true;\r
11271 \r
11272                                                                 // Caret doesn't get rendered when you mousedown on the HTML element on FF 3.x\r
11273                                                                 t.onMouseDown.add(function(ed, e) {\r
11274                                                                         if (e.target.nodeName === "HTML") {\r
11275                                                                                 d.designMode = 'on'; // Render the caret\r
11276 \r
11277                                                                                 // Remove design mode again after a while so it has some time to execute\r
11278                                                                                 window.setTimeout(function() {\r
11279                                                                                         d.designMode = 'off';\r
11280                                                                                         t.getBody().focus();\r
11281                                                                                 }, 1);\r
11282                                                                         }\r
11283                                                                 });\r
11284                                                         } else\r
11285                                                                 d.designMode = 'on';\r
11286 \r
11287                                                         // Call setup frame once the contentEditable/designMode has been initialized\r
11288                                                         // since the caret won't be rendered some times otherwise.\r
11289                                                         t.setupIframe(true);\r
11290                                                 }, 1);\r
11291                                         };\r
11292                                 }\r
11293 \r
11294                                 d.open();\r
11295                                 d.write(t.iframeHTML);\r
11296                                 d.close();\r
11297 \r
11298                                 if (tinymce.relaxedDomain)\r
11299                                         d.domain = tinymce.relaxedDomain;\r
11300 \r
11301                                 // Wait for iframe onload event on Gecko\r
11302                                 if (isGecko && !s.readonly)\r
11303                                         return;\r
11304                         }\r
11305 \r
11306                         // It will not steal focus while setting contentEditable\r
11307                         b = t.getBody();\r
11308                         b.disabled = true;\r
11309 \r
11310                         if (!isGecko && !s.readonly)\r
11311                                 b.contentEditable = true;\r
11312 \r
11313                         b.disabled = false;\r
11314 \r
11315                         t.schema = new tinymce.html.Schema(s);\r
11316 \r
11317                         t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {\r
11318                                 keep_values : true,\r
11319                                 url_converter : t.convertURL,\r
11320                                 url_converter_scope : t,\r
11321                                 hex_colors : s.force_hex_style_colors,\r
11322                                 class_filter : s.class_filter,\r
11323                                 update_styles : 1,\r
11324                                 fix_ie_paragraphs : 1,\r
11325                                 schema : t.schema\r
11326                         });\r
11327 \r
11328                         t.parser = new tinymce.html.DomParser(s, t.schema);\r
11329 \r
11330                         // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.\r
11331                         if (!t.settings.allow_html_in_named_anchor) {\r
11332                                 t.parser.addAttributeFilter('name', function(nodes, name) {\r
11333                                         var i = nodes.length, sibling, prevSibling, parent, node;\r
11334         \r
11335                                         while (i--) {\r
11336                                                 node = nodes[i];\r
11337                                                 if (node.name === 'a' && node.firstChild) {\r
11338                                                         parent = node.parent;\r
11339         \r
11340                                                         // Move children after current node\r
11341                                                         sibling = node.lastChild;\r
11342                                                         do {\r
11343                                                                 prevSibling = sibling.prev;\r
11344                                                                 parent.insert(sibling, node);\r
11345                                                                 sibling = prevSibling;\r
11346                                                         } while (sibling);\r
11347                                                 }\r
11348                                         }\r
11349                                 });\r
11350                         }\r
11351 \r
11352                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style\r
11353                         t.parser.addAttributeFilter('src,href,style', function(nodes, name) {\r
11354                                 var i = nodes.length, node, dom = t.dom, value, internalName;\r
11355 \r
11356                                 while (i--) {\r
11357                                         node = nodes[i];\r
11358                                         value = node.attr(name);\r
11359                                         internalName = 'data-mce-' + name;\r
11360 \r
11361                                         // Add internal attribute if we need to we don't on a refresh of the document\r
11362                                         if (!node.attributes.map[internalName]) {       \r
11363                                                 if (name === "style")\r
11364                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));\r
11365                                                 else\r
11366                                                         node.attr(internalName, t.convertURL(value, name, node.name));\r
11367                                         }\r
11368                                 }\r
11369                         });\r
11370 \r
11371                         // Keep scripts from executing\r
11372                         t.parser.addNodeFilter('script', function(nodes, name) {\r
11373                                 var i = nodes.length;\r
11374 \r
11375                                 while (i--)\r
11376                                         nodes[i].attr('type', 'mce-text/javascript');\r
11377                         });\r
11378 \r
11379                         t.parser.addNodeFilter('#cdata', function(nodes, name) {\r
11380                                 var i = nodes.length, node;\r
11381 \r
11382                                 while (i--) {\r
11383                                         node = nodes[i];\r
11384                                         node.type = 8;\r
11385                                         node.name = '#comment';\r
11386                                         node.value = '[CDATA[' + node.value + ']]';\r
11387                                 }\r
11388                         });\r
11389 \r
11390                         t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {\r
11391                                 var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();\r
11392 \r
11393                                 while (i--) {\r
11394                                         node = nodes[i];\r
11395 \r
11396                                         if (node.isEmpty(nonEmptyElements))\r
11397                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;\r
11398                                 }\r
11399                         });\r
11400 \r
11401                         t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);\r
11402 \r
11403                         t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);\r
11404 \r
11405                         t.formatter = new tinymce.Formatter(this);\r
11406 \r
11407                         // Register default formats\r
11408                         t.formatter.register({\r
11409                                 alignleft : [\r
11410                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},\r
11411                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}\r
11412                                 ],\r
11413 \r
11414                                 aligncenter : [\r
11415                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},\r
11416                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},\r
11417                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}\r
11418                                 ],\r
11419 \r
11420                                 alignright : [\r
11421                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},\r
11422                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}\r
11423                                 ],\r
11424 \r
11425                                 alignfull : [\r
11426                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}\r
11427                                 ],\r
11428 \r
11429                                 bold : [\r
11430                                         {inline : 'strong', remove : 'all'},\r
11431                                         {inline : 'span', styles : {fontWeight : 'bold'}},\r
11432                                         {inline : 'b', remove : 'all'}\r
11433                                 ],\r
11434 \r
11435                                 italic : [\r
11436                                         {inline : 'em', remove : 'all'},\r
11437                                         {inline : 'span', styles : {fontStyle : 'italic'}},\r
11438                                         {inline : 'i', remove : 'all'}\r
11439                                 ],\r
11440 \r
11441                                 underline : [\r
11442                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},\r
11443                                         {inline : 'u', remove : 'all'}\r
11444                                 ],\r
11445 \r
11446                                 strikethrough : [\r
11447                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},\r
11448                                         {inline : 'strike', remove : 'all'}\r
11449                                 ],\r
11450 \r
11451                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},\r
11452                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},\r
11453                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},\r
11454                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},\r
11455                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},\r
11456                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},\r
11457                                 subscript : {inline : 'sub'},\r
11458                                 superscript : {inline : 'sup'},\r
11459 \r
11460                                 removeformat : [\r
11461                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},\r
11462                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},\r
11463                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}\r
11464                                 ]\r
11465                         });\r
11466 \r
11467                         // Register default block formats\r
11468                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {\r
11469                                 t.formatter.register(name, {block : name, remove : 'all'});\r
11470                         });\r
11471 \r
11472                         // Register user defined formats\r
11473                         t.formatter.register(t.settings.formats);\r
11474 \r
11475                         t.undoManager = new tinymce.UndoManager(t);\r
11476 \r
11477                         // Pass through\r
11478                         t.undoManager.onAdd.add(function(um, l) {\r
11479                                 if (um.hasUndo())\r
11480                                         return t.onChange.dispatch(t, l, um);\r
11481                         });\r
11482 \r
11483                         t.undoManager.onUndo.add(function(um, l) {\r
11484                                 return t.onUndo.dispatch(t, l, um);\r
11485                         });\r
11486 \r
11487                         t.undoManager.onRedo.add(function(um, l) {\r
11488                                 return t.onRedo.dispatch(t, l, um);\r
11489                         });\r
11490 \r
11491                         t.forceBlocks = new tinymce.ForceBlocks(t, {\r
11492                                 forced_root_block : s.forced_root_block\r
11493                         });\r
11494 \r
11495                         t.editorCommands = new tinymce.EditorCommands(t);\r
11496 \r
11497                         // Pass through\r
11498                         t.serializer.onPreProcess.add(function(se, o) {\r
11499                                 return t.onPreProcess.dispatch(t, o, se);\r
11500                         });\r
11501 \r
11502                         t.serializer.onPostProcess.add(function(se, o) {\r
11503                                 return t.onPostProcess.dispatch(t, o, se);\r
11504                         });\r
11505 \r
11506                         t.onPreInit.dispatch(t);\r
11507 \r
11508                         if (!s.gecko_spellcheck)\r
11509                                 t.getBody().spellcheck = 0;\r
11510 \r
11511                         if (!s.readonly)\r
11512                                 t._addEvents();\r
11513 \r
11514                         t.controlManager.onPostRender.dispatch(t, t.controlManager);\r
11515                         t.onPostRender.dispatch(t);\r
11516 \r
11517                         if (s.directionality)\r
11518                                 t.getBody().dir = s.directionality;\r
11519 \r
11520                         if (s.nowrap)\r
11521                                 t.getBody().style.whiteSpace = "nowrap";\r
11522 \r
11523                         if (s.handle_node_change_callback) {\r
11524                                 t.onNodeChange.add(function(ed, cm, n) {\r
11525                                         t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());\r
11526                                 });\r
11527                         }\r
11528 \r
11529                         if (s.save_callback) {\r
11530                                 t.onSaveContent.add(function(ed, o) {\r
11531                                         var h = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
11532 \r
11533                                         if (h)\r
11534                                                 o.content = h;\r
11535                                 });\r
11536                         }\r
11537 \r
11538                         if (s.onchange_callback) {\r
11539                                 t.onChange.add(function(ed, l) {\r
11540                                         t.execCallback('onchange_callback', t, l);\r
11541                                 });\r
11542                         }\r
11543 \r
11544                         if (s.protect) {\r
11545                                 t.onBeforeSetContent.add(function(ed, o) {\r
11546                                         if (s.protect) {\r
11547                                                 each(s.protect, function(pattern) {\r
11548                                                         o.content = o.content.replace(pattern, function(str) {\r
11549                                                                 return '<!--mce:protected ' + escape(str) + '-->';\r
11550                                                         });\r
11551                                                 });\r
11552                                         }\r
11553                                 });\r
11554                         }\r
11555 \r
11556                         if (s.convert_newlines_to_brs) {\r
11557                                 t.onBeforeSetContent.add(function(ed, o) {\r
11558                                         if (o.initial)\r
11559                                                 o.content = o.content.replace(/\r?\n/g, '<br />');\r
11560                                 });\r
11561                         }\r
11562 \r
11563                         if (s.preformatted) {\r
11564                                 t.onPostProcess.add(function(ed, o) {\r
11565                                         o.content = o.content.replace(/^\s*<pre.*?>/, '');\r
11566                                         o.content = o.content.replace(/<\/pre>\s*$/, '');\r
11567 \r
11568                                         if (o.set)\r
11569                                                 o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';\r
11570                                 });\r
11571                         }\r
11572 \r
11573                         if (s.verify_css_classes) {\r
11574                                 t.serializer.attribValueFilter = function(n, v) {\r
11575                                         var s, cl;\r
11576 \r
11577                                         if (n == 'class') {\r
11578                                                 // Build regexp for classes\r
11579                                                 if (!t.classesRE) {\r
11580                                                         cl = t.dom.getClasses();\r
11581 \r
11582                                                         if (cl.length > 0) {\r
11583                                                                 s = '';\r
11584 \r
11585                                                                 each (cl, function(o) {\r
11586                                                                         s += (s ? '|' : '') + o['class'];\r
11587                                                                 });\r
11588 \r
11589                                                                 t.classesRE = new RegExp('(' + s + ')', 'gi');\r
11590                                                         }\r
11591                                                 }\r
11592 \r
11593                                                 return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';\r
11594                                         }\r
11595 \r
11596                                         return v;\r
11597                                 };\r
11598                         }\r
11599 \r
11600                         if (s.cleanup_callback) {\r
11601                                 t.onBeforeSetContent.add(function(ed, o) {\r
11602                                         o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
11603                                 });\r
11604 \r
11605                                 t.onPreProcess.add(function(ed, o) {\r
11606                                         if (o.set)\r
11607                                                 t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);\r
11608 \r
11609                                         if (o.get)\r
11610                                                 t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);\r
11611                                 });\r
11612 \r
11613                                 t.onPostProcess.add(function(ed, o) {\r
11614                                         if (o.set)\r
11615                                                 o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
11616 \r
11617                                         if (o.get)                                              \r
11618                                                 o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);\r
11619                                 });\r
11620                         }\r
11621 \r
11622                         if (s.save_callback) {\r
11623                                 t.onGetContent.add(function(ed, o) {\r
11624                                         if (o.save)\r
11625                                                 o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());\r
11626                                 });\r
11627                         }\r
11628 \r
11629                         if (s.handle_event_callback) {\r
11630                                 t.onEvent.add(function(ed, e, o) {\r
11631                                         if (t.execCallback('handle_event_callback', e, ed, o) === false)\r
11632                                                 Event.cancel(e);\r
11633                                 });\r
11634                         }\r
11635 \r
11636                         // Add visual aids when new contents is added\r
11637                         t.onSetContent.add(function() {\r
11638                                 t.addVisual(t.getBody());\r
11639                         });\r
11640 \r
11641                         // Remove empty contents\r
11642                         if (s.padd_empty_editor) {\r
11643                                 t.onPostProcess.add(function(ed, o) {\r
11644                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');\r
11645                                 });\r
11646                         }\r
11647 \r
11648                         if (isGecko) {\r
11649                                 // Fix gecko link bug, when a link is placed at the end of block elements there is\r
11650                                 // no way to move the caret behind the link. This fix adds a bogus br element after the link\r
11651                                 function fixLinks(ed, o) {\r
11652                                         each(ed.dom.select('a'), function(n) {\r
11653                                                 var pn = n.parentNode;\r
11654 \r
11655                                                 if (ed.dom.isBlock(pn) && pn.lastChild === n)\r
11656                                                         ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});\r
11657                                         });\r
11658                                 };\r
11659 \r
11660                                 t.onExecCommand.add(function(ed, cmd) {\r
11661                                         if (cmd === 'CreateLink')\r
11662                                                 fixLinks(ed);\r
11663                                 });\r
11664 \r
11665                                 t.onSetContent.add(t.selection.onSetContent.add(fixLinks));\r
11666                         }\r
11667 \r
11668                         t.load({initial : true, format : 'html'});\r
11669                         t.startContent = t.getContent({format : 'raw'});\r
11670                         t.undoManager.add();\r
11671                         t.initialized = true;\r
11672 \r
11673                         t.onInit.dispatch(t);\r
11674                         t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());\r
11675                         t.execCallback('init_instance_callback', t);\r
11676                         t.focus(true);\r
11677                         t.nodeChanged({initial : 1});\r
11678 \r
11679                         // Load specified content CSS last\r
11680                         each(t.contentCSS, function(u) {\r
11681                                 t.dom.loadCSS(u);\r
11682                         });\r
11683 \r
11684                         // Handle auto focus\r
11685                         if (s.auto_focus) {\r
11686                                 setTimeout(function () {\r
11687                                         var ed = tinymce.get(s.auto_focus);\r
11688 \r
11689                                         ed.selection.select(ed.getBody(), 1);\r
11690                                         ed.selection.collapse(1);\r
11691                                         ed.getBody().focus();\r
11692                                         ed.getWin().focus();\r
11693                                 }, 100);\r
11694                         }\r
11695 \r
11696                         e = null;\r
11697                 },\r
11698 \r
11699 \r
11700                 focus : function(sf) {\r
11701                         var oed, t = this, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();\r
11702 \r
11703                         if (!sf) {\r
11704                                 // Get selected control element\r
11705                                 ieRng = t.selection.getRng();\r
11706                                 if (ieRng.item) {\r
11707                                         controlElm = ieRng.item(0);\r
11708                                 }\r
11709 \r
11710                                 // Is not content editable\r
11711                                 if (!ce)\r
11712                                         t.getWin().focus();\r
11713 \r
11714                                 // Restore selected control element\r
11715                                 // This is needed when for example an image is selected within a\r
11716                                 // layer a call to focus will then remove the control selection\r
11717                                 if (controlElm && controlElm.ownerDocument == doc) {\r
11718                                         ieRng = doc.body.createControlRange();\r
11719                                         ieRng.addElement(controlElm);\r
11720                                         ieRng.select();\r
11721                                 }\r
11722 \r
11723                         }\r
11724 \r
11725                         if (tinymce.activeEditor != t) {\r
11726                                 if ((oed = tinymce.activeEditor) != null)\r
11727                                         oed.onDeactivate.dispatch(oed, t);\r
11728 \r
11729                                 t.onActivate.dispatch(t, oed);\r
11730                         }\r
11731 \r
11732                         tinymce._setActive(t);\r
11733                 },\r
11734 \r
11735                 execCallback : function(n) {\r
11736                         var t = this, f = t.settings[n], s;\r
11737 \r
11738                         if (!f)\r
11739                                 return;\r
11740 \r
11741                         // Look through lookup\r
11742                         if (t.callbackLookup && (s = t.callbackLookup[n])) {\r
11743                                 f = s.func;\r
11744                                 s = s.scope;\r
11745                         }\r
11746 \r
11747                         if (is(f, 'string')) {\r
11748                                 s = f.replace(/\.\w+$/, '');\r
11749                                 s = s ? tinymce.resolve(s) : 0;\r
11750                                 f = tinymce.resolve(f);\r
11751                                 t.callbackLookup = t.callbackLookup || {};\r
11752                                 t.callbackLookup[n] = {func : f, scope : s};\r
11753                         }\r
11754 \r
11755                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));\r
11756                 },\r
11757 \r
11758                 translate : function(s) {\r
11759                         var c = this.settings.language || 'en', i18n = tinymce.i18n;\r
11760 \r
11761                         if (!s)\r
11762                                 return '';\r
11763 \r
11764                         return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {\r
11765                                 return i18n[c + '.' + b] || '{#' + b + '}';\r
11766                         });\r
11767                 },\r
11768 \r
11769                 getLang : function(n, dv) {\r
11770                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');\r
11771                 },\r
11772 \r
11773                 getParam : function(n, dv, ty) {\r
11774                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;\r
11775 \r
11776                         if (ty === 'hash') {\r
11777                                 o = {};\r
11778 \r
11779                                 if (is(v, 'string')) {\r
11780                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {\r
11781                                                 v = v.split('=');\r
11782 \r
11783                                                 if (v.length > 1)\r
11784                                                         o[tr(v[0])] = tr(v[1]);\r
11785                                                 else\r
11786                                                         o[tr(v[0])] = tr(v);\r
11787                                         });\r
11788                                 } else\r
11789                                         o = v;\r
11790 \r
11791                                 return o;\r
11792                         }\r
11793 \r
11794                         return v;\r
11795                 },\r
11796 \r
11797                 nodeChanged : function(o) {\r
11798                         var t = this, s = t.selection, n = s.getStart() || t.getBody();\r
11799 \r
11800                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading\r
11801                         if (t.initialized) {\r
11802                                 o = o || {};\r
11803                                 n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state\r
11804 \r
11805                                 // Get parents and add them to object\r
11806                                 o.parents = [];\r
11807                                 t.dom.getParent(n, function(node) {\r
11808                                         if (node.nodeName == 'BODY')\r
11809                                                 return true;\r
11810 \r
11811                                         o.parents.push(node);\r
11812                                 });\r
11813 \r
11814                                 t.onNodeChange.dispatch(\r
11815                                         t,\r
11816                                         o ? o.controlManager || t.controlManager : t.controlManager,\r
11817                                         n,\r
11818                                         s.isCollapsed(),\r
11819                                         o\r
11820                                 );\r
11821                         }\r
11822                 },\r
11823 \r
11824                 addButton : function(n, s) {\r
11825                         var t = this;\r
11826 \r
11827                         t.buttons = t.buttons || {};\r
11828                         t.buttons[n] = s;\r
11829                 },\r
11830 \r
11831                 addCommand : function(name, callback, scope) {\r
11832                         this.execCommands[name] = {func : callback, scope : scope || this};\r
11833                 },\r
11834 \r
11835                 addQueryStateHandler : function(name, callback, scope) {\r
11836                         this.queryStateCommands[name] = {func : callback, scope : scope || this};\r
11837                 },\r
11838 \r
11839                 addQueryValueHandler : function(name, callback, scope) {\r
11840                         this.queryValueCommands[name] = {func : callback, scope : scope || this};\r
11841                 },\r
11842 \r
11843                 addShortcut : function(pa, desc, cmd_func, sc) {\r
11844                         var t = this, c;\r
11845 \r
11846                         if (!t.settings.custom_shortcuts)\r
11847                                 return false;\r
11848 \r
11849                         t.shortcuts = t.shortcuts || {};\r
11850 \r
11851                         if (is(cmd_func, 'string')) {\r
11852                                 c = cmd_func;\r
11853 \r
11854                                 cmd_func = function() {\r
11855                                         t.execCommand(c, false, null);\r
11856                                 };\r
11857                         }\r
11858 \r
11859                         if (is(cmd_func, 'object')) {\r
11860                                 c = cmd_func;\r
11861 \r
11862                                 cmd_func = function() {\r
11863                                         t.execCommand(c[0], c[1], c[2]);\r
11864                                 };\r
11865                         }\r
11866 \r
11867                         each(explode(pa), function(pa) {\r
11868                                 var o = {\r
11869                                         func : cmd_func,\r
11870                                         scope : sc || this,\r
11871                                         desc : desc,\r
11872                                         alt : false,\r
11873                                         ctrl : false,\r
11874                                         shift : false\r
11875                                 };\r
11876 \r
11877                                 each(explode(pa, '+'), function(v) {\r
11878                                         switch (v) {\r
11879                                                 case 'alt':\r
11880                                                 case 'ctrl':\r
11881                                                 case 'shift':\r
11882                                                         o[v] = true;\r
11883                                                         break;\r
11884 \r
11885                                                 default:\r
11886                                                         o.charCode = v.charCodeAt(0);\r
11887                                                         o.keyCode = v.toUpperCase().charCodeAt(0);\r
11888                                         }\r
11889                                 });\r
11890 \r
11891                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;\r
11892                         });\r
11893 \r
11894                         return true;\r
11895                 },\r
11896 \r
11897                 execCommand : function(cmd, ui, val, a) {\r
11898                         var t = this, s = 0, o, st;\r
11899 \r
11900                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))\r
11901                                 t.focus();\r
11902 \r
11903                         o = {};\r
11904                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);\r
11905                         if (o.terminate)\r
11906                                 return false;\r
11907 \r
11908                         // Command callback\r
11909                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {\r
11910                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11911                                 return true;\r
11912                         }\r
11913 \r
11914                         // Registred commands\r
11915                         if (o = t.execCommands[cmd]) {\r
11916                                 st = o.func.call(o.scope, ui, val);\r
11917 \r
11918                                 // Fall through on true\r
11919                                 if (st !== true) {\r
11920                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11921                                         return st;\r
11922                                 }\r
11923                         }\r
11924 \r
11925                         // Plugin commands\r
11926                         each(t.plugins, function(p) {\r
11927                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {\r
11928                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11929                                         s = 1;\r
11930                                         return false;\r
11931                                 }\r
11932                         });\r
11933 \r
11934                         if (s)\r
11935                                 return true;\r
11936 \r
11937                         // Theme commands\r
11938                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {\r
11939                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11940                                 return true;\r
11941                         }\r
11942 \r
11943                         // Editor commands\r
11944                         if (t.editorCommands.execCommand(cmd, ui, val)) {\r
11945                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11946                                 return true;\r
11947                         }\r
11948 \r
11949                         // Browser commands\r
11950                         t.getDoc().execCommand(cmd, ui, val);\r
11951                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
11952                 },\r
11953 \r
11954                 queryCommandState : function(cmd) {\r
11955                         var t = this, o, s;\r
11956 \r
11957                         // Is hidden then return undefined\r
11958                         if (t._isHidden())\r
11959                                 return;\r
11960 \r
11961                         // Registred commands\r
11962                         if (o = t.queryStateCommands[cmd]) {\r
11963                                 s = o.func.call(o.scope);\r
11964 \r
11965                                 // Fall though on true\r
11966                                 if (s !== true)\r
11967                                         return s;\r
11968                         }\r
11969 \r
11970                         // Registred commands\r
11971                         o = t.editorCommands.queryCommandState(cmd);\r
11972                         if (o !== -1)\r
11973                                 return o;\r
11974 \r
11975                         // Browser commands\r
11976                         try {\r
11977                                 return this.getDoc().queryCommandState(cmd);\r
11978                         } catch (ex) {\r
11979                                 // Fails sometimes see bug: 1896577\r
11980                         }\r
11981                 },\r
11982 \r
11983                 queryCommandValue : function(c) {\r
11984                         var t = this, o, s;\r
11985 \r
11986                         // Is hidden then return undefined\r
11987                         if (t._isHidden())\r
11988                                 return;\r
11989 \r
11990                         // Registred commands\r
11991                         if (o = t.queryValueCommands[c]) {\r
11992                                 s = o.func.call(o.scope);\r
11993 \r
11994                                 // Fall though on true\r
11995                                 if (s !== true)\r
11996                                         return s;\r
11997                         }\r
11998 \r
11999                         // Registred commands\r
12000                         o = t.editorCommands.queryCommandValue(c);\r
12001                         if (is(o))\r
12002                                 return o;\r
12003 \r
12004                         // Browser commands\r
12005                         try {\r
12006                                 return this.getDoc().queryCommandValue(c);\r
12007                         } catch (ex) {\r
12008                                 // Fails sometimes see bug: 1896577\r
12009                         }\r
12010                 },\r
12011 \r
12012                 show : function() {\r
12013                         var t = this;\r
12014 \r
12015                         DOM.show(t.getContainer());\r
12016                         DOM.hide(t.id);\r
12017                         t.load();\r
12018                 },\r
12019 \r
12020                 hide : function() {\r
12021                         var t = this, d = t.getDoc();\r
12022 \r
12023                         // Fixed bug where IE has a blinking cursor left from the editor\r
12024                         if (isIE && d)\r
12025                                 d.execCommand('SelectAll');\r
12026 \r
12027                         // We must save before we hide so Safari doesn't crash\r
12028                         t.save();\r
12029                         DOM.hide(t.getContainer());\r
12030                         DOM.setStyle(t.id, 'display', t.orgDisplay);\r
12031                 },\r
12032 \r
12033                 isHidden : function() {\r
12034                         return !DOM.isHidden(this.id);\r
12035                 },\r
12036 \r
12037                 setProgressState : function(b, ti, o) {\r
12038                         this.onSetProgressState.dispatch(this, b, ti, o);\r
12039 \r
12040                         return b;\r
12041                 },\r
12042 \r
12043                 load : function(o) {\r
12044                         var t = this, e = t.getElement(), h;\r
12045 \r
12046                         if (e) {\r
12047                                 o = o || {};\r
12048                                 o.load = true;\r
12049 \r
12050                                 // Double encode existing entities in the value\r
12051                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);\r
12052                                 o.element = e;\r
12053 \r
12054                                 if (!o.no_events)\r
12055                                         t.onLoadContent.dispatch(t, o);\r
12056 \r
12057                                 o.element = e = null;\r
12058 \r
12059                                 return h;\r
12060                         }\r
12061                 },\r
12062 \r
12063                 save : function(o) {\r
12064                         var t = this, e = t.getElement(), h, f;\r
12065 \r
12066                         if (!e || !t.initialized)\r
12067                                 return;\r
12068 \r
12069                         o = o || {};\r
12070                         o.save = true;\r
12071 \r
12072                         // Add undo level will trigger onchange event\r
12073                         if (!o.no_events) {\r
12074                                 t.undoManager.typing = false;\r
12075                                 t.undoManager.add();\r
12076                         }\r
12077 \r
12078                         o.element = e;\r
12079                         h = o.content = t.getContent(o);\r
12080 \r
12081                         if (!o.no_events)\r
12082                                 t.onSaveContent.dispatch(t, o);\r
12083 \r
12084                         h = o.content;\r
12085 \r
12086                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {\r
12087                                 e.innerHTML = h;\r
12088 \r
12089                                 // Update hidden form element\r
12090                                 if (f = DOM.getParent(t.id, 'form')) {\r
12091                                         each(f.elements, function(e) {\r
12092                                                 if (e.name == t.id) {\r
12093                                                         e.value = h;\r
12094                                                         return false;\r
12095                                                 }\r
12096                                         });\r
12097                                 }\r
12098                         } else\r
12099                                 e.value = h;\r
12100 \r
12101                         o.element = e = null;\r
12102 \r
12103                         return h;\r
12104                 },\r
12105 \r
12106                 setContent : function(content, args) {\r
12107                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;\r
12108 \r
12109                         // Setup args object\r
12110                         args = args || {};\r
12111                         args.format = args.format || 'html';\r
12112                         args.set = true;\r
12113                         args.content = content;\r
12114 \r
12115                         // Do preprocessing\r
12116                         if (!args.no_events)\r
12117                                 self.onBeforeSetContent.dispatch(self, args);\r
12118 \r
12119                         content = args.content;\r
12120 \r
12121                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content\r
12122                         // It will also be impossible to place the caret in the editor unless there is a BR element present\r
12123                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {\r
12124                                 forcedRootBlockName = self.settings.forced_root_block;\r
12125                                 if (forcedRootBlockName)\r
12126                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';\r
12127                                 else\r
12128                                         content = '<br data-mce-bogus="1">';\r
12129 \r
12130                                 body.innerHTML = content;\r
12131                                 self.selection.select(body, true);\r
12132                                 self.selection.collapse(true);\r
12133                                 return;\r
12134                         }\r
12135 \r
12136                         // Parse and serialize the html\r
12137                         if (args.format !== 'raw') {\r
12138                                 content = new tinymce.html.Serializer({}, self.schema).serialize(\r
12139                                         self.parser.parse(content)\r
12140                                 );\r
12141                         }\r
12142 \r
12143                         // Set the new cleaned contents to the editor\r
12144                         args.content = tinymce.trim(content);\r
12145                         self.dom.setHTML(body, args.content);\r
12146 \r
12147                         // Do post processing\r
12148                         if (!args.no_events)\r
12149                                 self.onSetContent.dispatch(self, args);\r
12150 \r
12151                         return args.content;\r
12152                 },\r
12153 \r
12154                 getContent : function(args) {\r
12155                         var self = this, content;\r
12156 \r
12157                         // Setup args object\r
12158                         args = args || {};\r
12159                         args.format = args.format || 'html';\r
12160                         args.get = true;\r
12161 \r
12162                         // Do preprocessing\r
12163                         if (!args.no_events)\r
12164                                 self.onBeforeGetContent.dispatch(self, args);\r
12165 \r
12166                         // Get raw contents or by default the cleaned contents\r
12167                         if (args.format == 'raw')\r
12168                                 content = self.getBody().innerHTML;\r
12169                         else\r
12170                                 content = self.serializer.serialize(self.getBody(), args);\r
12171 \r
12172                         args.content = tinymce.trim(content);\r
12173 \r
12174                         // Do post processing\r
12175                         if (!args.no_events)\r
12176                                 self.onGetContent.dispatch(self, args);\r
12177 \r
12178                         return args.content;\r
12179                 },\r
12180 \r
12181                 isDirty : function() {\r
12182                         var self = this;\r
12183 \r
12184                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;\r
12185                 },\r
12186 \r
12187                 getContainer : function() {\r
12188                         var t = this;\r
12189 \r
12190                         if (!t.container)\r
12191                                 t.container = DOM.get(t.editorContainer || t.id + '_parent');\r
12192 \r
12193                         return t.container;\r
12194                 },\r
12195 \r
12196                 getContentAreaContainer : function() {\r
12197                         return this.contentAreaContainer;\r
12198                 },\r
12199 \r
12200                 getElement : function() {\r
12201                         return DOM.get(this.settings.content_element || this.id);\r
12202                 },\r
12203 \r
12204                 getWin : function() {\r
12205                         var t = this, e;\r
12206 \r
12207                         if (!t.contentWindow) {\r
12208                                 e = DOM.get(t.id + "_ifr");\r
12209 \r
12210                                 if (e)\r
12211                                         t.contentWindow = e.contentWindow;\r
12212                         }\r
12213 \r
12214                         return t.contentWindow;\r
12215                 },\r
12216 \r
12217                 getDoc : function() {\r
12218                         var t = this, w;\r
12219 \r
12220                         if (!t.contentDocument) {\r
12221                                 w = t.getWin();\r
12222 \r
12223                                 if (w)\r
12224                                         t.contentDocument = w.document;\r
12225                         }\r
12226 \r
12227                         return t.contentDocument;\r
12228                 },\r
12229 \r
12230                 getBody : function() {\r
12231                         return this.bodyElement || this.getDoc().body;\r
12232                 },\r
12233 \r
12234                 convertURL : function(u, n, e) {\r
12235                         var t = this, s = t.settings;\r
12236 \r
12237                         // Use callback instead\r
12238                         if (s.urlconverter_callback)\r
12239                                 return t.execCallback('urlconverter_callback', u, e, true, n);\r
12240 \r
12241                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs\r
12242                         if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)\r
12243                                 return u;\r
12244 \r
12245                         // Convert to relative\r
12246                         if (s.relative_urls)\r
12247                                 return t.documentBaseURI.toRelative(u);\r
12248 \r
12249                         // Convert to absolute\r
12250                         u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);\r
12251 \r
12252                         return u;\r
12253                 },\r
12254 \r
12255                 addVisual : function(e) {\r
12256                         var t = this, s = t.settings;\r
12257 \r
12258                         e = e || t.getBody();\r
12259 \r
12260                         if (!is(t.hasVisual))\r
12261                                 t.hasVisual = s.visual;\r
12262 \r
12263                         each(t.dom.select('table,a', e), function(e) {\r
12264                                 var v;\r
12265 \r
12266                                 switch (e.nodeName) {\r
12267                                         case 'TABLE':\r
12268                                                 v = t.dom.getAttrib(e, 'border');\r
12269 \r
12270                                                 if (!v || v == '0') {\r
12271                                                         if (t.hasVisual)\r
12272                                                                 t.dom.addClass(e, s.visual_table_class);\r
12273                                                         else\r
12274                                                                 t.dom.removeClass(e, s.visual_table_class);\r
12275                                                 }\r
12276 \r
12277                                                 return;\r
12278 \r
12279                                         case 'A':\r
12280                                                 v = t.dom.getAttrib(e, 'name');\r
12281 \r
12282                                                 if (v) {\r
12283                                                         if (t.hasVisual)\r
12284                                                                 t.dom.addClass(e, 'mceItemAnchor');\r
12285                                                         else\r
12286                                                                 t.dom.removeClass(e, 'mceItemAnchor');\r
12287                                                 }\r
12288 \r
12289                                                 return;\r
12290                                 }\r
12291                         });\r
12292 \r
12293                         t.onVisualAid.dispatch(t, e, t.hasVisual);\r
12294                 },\r
12295 \r
12296                 remove : function() {\r
12297                         var t = this, e = t.getContainer();\r
12298 \r
12299                         t.removed = 1; // Cancels post remove event execution\r
12300                         t.hide();\r
12301 \r
12302                         t.execCallback('remove_instance_callback', t);\r
12303                         t.onRemove.dispatch(t);\r
12304 \r
12305                         // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command\r
12306                         t.onExecCommand.listeners = [];\r
12307 \r
12308                         tinymce.remove(t);\r
12309                         DOM.remove(e);\r
12310                 },\r
12311 \r
12312                 destroy : function(s) {\r
12313                         var t = this;\r
12314 \r
12315                         // One time is enough\r
12316                         if (t.destroyed)\r
12317                                 return;\r
12318 \r
12319                         if (!s) {\r
12320                                 tinymce.removeUnload(t.destroy);\r
12321                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);\r
12322 \r
12323                                 // Manual destroy\r
12324                                 if (t.theme && t.theme.destroy)\r
12325                                         t.theme.destroy();\r
12326 \r
12327                                 // Destroy controls, selection and dom\r
12328                                 t.controlManager.destroy();\r
12329                                 t.selection.destroy();\r
12330                                 t.dom.destroy();\r
12331 \r
12332                                 // Remove all events\r
12333 \r
12334                                 // Don't clear the window or document if content editable\r
12335                                 // is enabled since other instances might still be present\r
12336                                 if (!t.settings.content_editable) {\r
12337                                         Event.clear(t.getWin());\r
12338                                         Event.clear(t.getDoc());\r
12339                                 }\r
12340 \r
12341                                 Event.clear(t.getBody());\r
12342                                 Event.clear(t.formElement);\r
12343                         }\r
12344 \r
12345                         if (t.formElement) {\r
12346                                 t.formElement.submit = t.formElement._mceOldSubmit;\r
12347                                 t.formElement._mceOldSubmit = null;\r
12348                         }\r
12349 \r
12350                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;\r
12351 \r
12352                         if (t.selection)\r
12353                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;\r
12354 \r
12355                         t.destroyed = 1;\r
12356                 },\r
12357 \r
12358                 // Internal functions\r
12359 \r
12360                 _addEvents : function() {\r
12361                         // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset\r
12362                         var t = this, i, s = t.settings, dom = t.dom, lo = {\r
12363                                 mouseup : 'onMouseUp',\r
12364                                 mousedown : 'onMouseDown',\r
12365                                 click : 'onClick',\r
12366                                 keyup : 'onKeyUp',\r
12367                                 keydown : 'onKeyDown',\r
12368                                 keypress : 'onKeyPress',\r
12369                                 submit : 'onSubmit',\r
12370                                 reset : 'onReset',\r
12371                                 contextmenu : 'onContextMenu',\r
12372                                 dblclick : 'onDblClick',\r
12373                                 paste : 'onPaste' // Doesn't work in all browsers yet\r
12374                         };\r
12375 \r
12376                         function eventHandler(e, o) {\r
12377                                 var ty = e.type;\r
12378 \r
12379                                 // Don't fire events when it's removed\r
12380                                 if (t.removed)\r
12381                                         return;\r
12382 \r
12383                                 // Generic event handler\r
12384                                 if (t.onEvent.dispatch(t, e, o) !== false) {\r
12385                                         // Specific event handler\r
12386                                         t[lo[e.fakeType || e.type]].dispatch(t, e, o);\r
12387                                 }\r
12388                         };\r
12389 \r
12390                         // Add DOM events\r
12391                         each(lo, function(v, k) {\r
12392                                 switch (k) {\r
12393                                         case 'contextmenu':\r
12394                                                 dom.bind(t.getDoc(), k, eventHandler);\r
12395                                                 break;\r
12396 \r
12397                                         case 'paste':\r
12398                                                 dom.bind(t.getBody(), k, function(e) {\r
12399                                                         eventHandler(e);\r
12400                                                 });\r
12401                                                 break;\r
12402 \r
12403                                         case 'submit':\r
12404                                         case 'reset':\r
12405                                                 dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);\r
12406                                                 break;\r
12407 \r
12408                                         default:\r
12409                                                 dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);\r
12410                                 }\r
12411                         });\r
12412 \r
12413                         dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {\r
12414                                 t.focus(true);\r
12415                         });\r
12416 \r
12417 \r
12418                         // Fixes bug where a specified document_base_uri could result in broken images\r
12419                         // This will also fix drag drop of images in Gecko\r
12420                         if (tinymce.isGecko) {\r
12421                                 dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {\r
12422                                         var v;\r
12423 \r
12424                                         e = e.target;\r
12425 \r
12426                                         if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))\r
12427                                                 e.src = t.documentBaseURI.toAbsolute(v);\r
12428                                 });\r
12429                         }\r
12430 \r
12431                         // Set various midas options in Gecko\r
12432                         if (isGecko) {\r
12433                                 function setOpts() {\r
12434                                         var t = this, d = t.getDoc(), s = t.settings;\r
12435 \r
12436                                         if (isGecko && !s.readonly) {\r
12437                                                 if (t._isHidden()) {\r
12438                                                         try {\r
12439                                                                 if (!s.content_editable) {\r
12440                                                                         d.body.contentEditable = false;\r
12441                                                                         d.body.contentEditable = true;\r
12442                                                                 }\r
12443                                                         } catch (ex) {\r
12444                                                                 // Fails if it's hidden\r
12445                                                         }\r
12446                                                 }\r
12447 \r
12448                                                 try {\r
12449                                                         // Try new Gecko method\r
12450                                                         d.execCommand("styleWithCSS", 0, false);\r
12451                                                 } catch (ex) {\r
12452                                                         // Use old method\r
12453                                                         if (!t._isHidden())\r
12454                                                                 try {d.execCommand("useCSS", 0, true);} catch (ex) {}\r
12455                                                 }\r
12456 \r
12457                                                 if (!s.table_inline_editing)\r
12458                                                         try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}\r
12459 \r
12460                                                 if (!s.object_resizing)\r
12461                                                         try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}\r
12462                                         }\r
12463                                 };\r
12464 \r
12465                                 t.onBeforeExecCommand.add(setOpts);\r
12466                                 t.onMouseDown.add(setOpts);\r
12467                         }\r
12468 \r
12469                         t.onClick.add(function(ed, e) {\r
12470                                 e = e.target;\r
12471 \r
12472                                 // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250\r
12473                                 // WebKit can't even do simple things like selecting an image\r
12474                                 // Needs tobe the setBaseAndExtend or it will fail to select floated images\r
12475                                 if (tinymce.isWebKit && e.nodeName == 'IMG')\r
12476                                         t.selection.getSel().setBaseAndExtent(e, 0, e, 1);\r
12477 \r
12478                                 if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor'))\r
12479                                         t.selection.select(e);\r
12480 \r
12481                                 t.nodeChanged();\r
12482                         });\r
12483 \r
12484                         // Add node change handlers\r
12485                         t.onMouseUp.add(t.nodeChanged);\r
12486                         //t.onClick.add(t.nodeChanged);\r
12487                         t.onKeyUp.add(function(ed, e) {\r
12488                                 var c = e.keyCode;\r
12489 \r
12490                                 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
12491                                         t.nodeChanged();\r
12492                         });\r
12493 \r
12494                         // Add reset handler\r
12495                         t.onReset.add(function() {\r
12496                                 t.setContent(t.startContent, {format : 'raw'});\r
12497                         });\r
12498 \r
12499                         // Add shortcuts\r
12500                         if (s.custom_shortcuts) {\r
12501                                 if (s.custom_undo_redo_keyboard_shortcuts) {\r
12502                                         t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');\r
12503                                         t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');\r
12504                                 }\r
12505 \r
12506                                 // Add default shortcuts for gecko\r
12507                                 t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');\r
12508                                 t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');\r
12509                                 t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');\r
12510 \r
12511                                 // BlockFormat shortcuts keys\r
12512                                 for (i=1; i<=6; i++)\r
12513                                         t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);\r
12514 \r
12515                                 t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);\r
12516                                 t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);\r
12517                                 t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);\r
12518 \r
12519                                 function find(e) {\r
12520                                         var v = null;\r
12521 \r
12522                                         if (!e.altKey && !e.ctrlKey && !e.metaKey)\r
12523                                                 return v;\r
12524 \r
12525                                         each(t.shortcuts, function(o) {\r
12526                                                 if (tinymce.isMac && o.ctrl != e.metaKey)\r
12527                                                         return;\r
12528                                                 else if (!tinymce.isMac && o.ctrl != e.ctrlKey)\r
12529                                                         return;\r
12530 \r
12531                                                 if (o.alt != e.altKey)\r
12532                                                         return;\r
12533 \r
12534                                                 if (o.shift != e.shiftKey)\r
12535                                                         return;\r
12536 \r
12537                                                 if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {\r
12538                                                         v = o;\r
12539                                                         return false;\r
12540                                                 }\r
12541                                         });\r
12542 \r
12543                                         return v;\r
12544                                 };\r
12545 \r
12546                                 t.onKeyUp.add(function(ed, e) {\r
12547                                         var o = find(e);\r
12548 \r
12549                                         if (o)\r
12550                                                 return Event.cancel(e);\r
12551                                 });\r
12552 \r
12553                                 t.onKeyPress.add(function(ed, e) {\r
12554                                         var o = find(e);\r
12555 \r
12556                                         if (o)\r
12557                                                 return Event.cancel(e);\r
12558                                 });\r
12559 \r
12560                                 t.onKeyDown.add(function(ed, e) {\r
12561                                         var o = find(e);\r
12562 \r
12563                                         if (o) {\r
12564                                                 o.func.call(o.scope);\r
12565                                                 return Event.cancel(e);\r
12566                                         }\r
12567                                 });\r
12568                         }\r
12569 \r
12570                         if (tinymce.isIE) {\r
12571                                 // Fix so resize will only update the width and height attributes not the styles of an image\r
12572                                 // It will also block mceItemNoResize items\r
12573                                 dom.bind(t.getDoc(), 'controlselect', function(e) {\r
12574                                         var re = t.resizeInfo, cb;\r
12575 \r
12576                                         e = e.target;\r
12577 \r
12578                                         // Don't do this action for non image elements\r
12579                                         if (e.nodeName !== 'IMG')\r
12580                                                 return;\r
12581 \r
12582                                         if (re)\r
12583                                                 dom.unbind(re.node, re.ev, re.cb);\r
12584 \r
12585                                         if (!dom.hasClass(e, 'mceItemNoResize')) {\r
12586                                                 ev = 'resizeend';\r
12587                                                 cb = dom.bind(e, ev, function(e) {\r
12588                                                         var v;\r
12589 \r
12590                                                         e = e.target;\r
12591 \r
12592                                                         if (v = dom.getStyle(e, 'width')) {\r
12593                                                                 dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));\r
12594                                                                 dom.setStyle(e, 'width', '');\r
12595                                                         }\r
12596 \r
12597                                                         if (v = dom.getStyle(e, 'height')) {\r
12598                                                                 dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));\r
12599                                                                 dom.setStyle(e, 'height', '');\r
12600                                                         }\r
12601                                                 });\r
12602                                         } else {\r
12603                                                 ev = 'resizestart';\r
12604                                                 cb = dom.bind(e, 'resizestart', Event.cancel, Event);\r
12605                                         }\r
12606 \r
12607                                         re = t.resizeInfo = {\r
12608                                                 node : e,\r
12609                                                 ev : ev,\r
12610                                                 cb : cb\r
12611                                         };\r
12612                                 });\r
12613                         }\r
12614 \r
12615                         if (tinymce.isOpera) {\r
12616                                 t.onClick.add(function(ed, e) {\r
12617                                         Event.prevent(e);\r
12618                                 });\r
12619                         }\r
12620 \r
12621                         // Add custom undo/redo handlers\r
12622                         if (s.custom_undo_redo) {\r
12623                                 function addUndo() {\r
12624                                         t.undoManager.typing = false;\r
12625                                         t.undoManager.add();\r
12626                                 };\r
12627 \r
12628                                 dom.bind(t.getDoc(), 'focusout', function(e) {\r
12629                                         if (!t.removed && t.undoManager.typing)\r
12630                                                 addUndo();\r
12631                                 });\r
12632 \r
12633                                 // Add undo level when contents is drag/dropped within the editor\r
12634                                 t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {\r
12635                                         addUndo();\r
12636                                 });\r
12637 \r
12638                                 t.onKeyUp.add(function(ed, e) {\r
12639                                         var keyCode = e.keyCode;\r
12640 \r
12641                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)\r
12642                                                 addUndo();\r
12643                                 });\r
12644 \r
12645                                 t.onKeyDown.add(function(ed, e) {\r
12646                                         var keyCode = e.keyCode, sel;\r
12647 \r
12648                                         if (keyCode == 8) {\r
12649                                                 sel = t.getDoc().selection;\r
12650 \r
12651                                                 // Fix IE control + backspace browser bug\r
12652                                                 if (sel && sel.createRange && sel.createRange().item) {\r
12653                                                         t.undoManager.beforeChange();\r
12654                                                         ed.dom.remove(sel.createRange().item(0));\r
12655                                                         addUndo();\r
12656 \r
12657                                                         return Event.cancel(e);\r
12658                                                 }\r
12659                                         }\r
12660 \r
12661                                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter\r
12662                                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {\r
12663                                                 // Add position before enter key is pressed, used by IE since it still uses the default browser behavior\r
12664                                                 // Todo: Remove this once we normalize enter behavior on IE\r
12665                                                 if (tinymce.isIE && keyCode == 13)\r
12666                                                         t.undoManager.beforeChange();\r
12667 \r
12668                                                 if (t.undoManager.typing)\r
12669                                                         addUndo();\r
12670 \r
12671                                                 return;\r
12672                                         }\r
12673 \r
12674                                         // If key isn't shift,ctrl,alt,capslock,metakey\r
12675                                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {\r
12676                                                 t.undoManager.beforeChange();\r
12677                                                 t.undoManager.typing = true;\r
12678                                                 t.undoManager.add();\r
12679                                         }\r
12680                                 });\r
12681 \r
12682                                 t.onMouseDown.add(function() {\r
12683                                         if (t.undoManager.typing)\r
12684                                                 addUndo();\r
12685                                 });\r
12686                         }\r
12687 \r
12688                         // Bug fix for FireFox keeping styles from end of selection instead of start.\r
12689                         if (tinymce.isGecko) {\r
12690                                 function getAttributeApplyFunction() {\r
12691                                         var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));\r
12692 \r
12693                                         return function() {\r
12694                                                 var target = t.selection.getStart();\r
12695                                                 t.dom.removeAllAttribs(target);\r
12696                                                 each(template, function(attr) {\r
12697                                                         target.setAttributeNode(attr.cloneNode(true));\r
12698                                                 });\r
12699                                         };\r
12700                                 }\r
12701 \r
12702                                 function isSelectionAcrossElements() {\r
12703                                         var s = t.selection;\r
12704 \r
12705                                         return !s.isCollapsed() && s.getStart() != s.getEnd();\r
12706                                 }\r
12707 \r
12708                                 t.onKeyPress.add(function(ed, e) {\r
12709                                         var applyAttributes;\r
12710 \r
12711                                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {\r
12712                                                 applyAttributes = getAttributeApplyFunction();\r
12713                                                 t.getDoc().execCommand('delete', false, null);\r
12714                                                 applyAttributes();\r
12715 \r
12716                                                 return Event.cancel(e);\r
12717                                         }\r
12718                                 });\r
12719 \r
12720                                 t.dom.bind(t.getDoc(), 'cut', function(e) {\r
12721                                         var applyAttributes;\r
12722 \r
12723                                         if (isSelectionAcrossElements()) {\r
12724                                                 applyAttributes = getAttributeApplyFunction();\r
12725                                                 t.onKeyUp.addToTop(Event.cancel, Event);\r
12726 \r
12727                                                 setTimeout(function() {\r
12728                                                         applyAttributes();\r
12729                                                         t.onKeyUp.remove(Event.cancel, Event);\r
12730                                                 }, 0);\r
12731                                         }\r
12732                                 });\r
12733                         }\r
12734                 },\r
12735 \r
12736                 _isHidden : function() {\r
12737                         var s;\r
12738 \r
12739                         if (!isGecko)\r
12740                                 return 0;\r
12741 \r
12742                         // Weird, wheres that cursor selection?\r
12743                         s = this.selection.getSel();\r
12744                         return (!s || !s.rangeCount || s.rangeCount == 0);\r
12745                 }\r
12746         });\r
12747 })(tinymce);\r
12748 \r
12749 (function(tinymce) {\r
12750         // Added for compression purposes\r
12751         var each = tinymce.each, undefined, TRUE = true, FALSE = false;\r
12752 \r
12753         tinymce.EditorCommands = function(editor) {\r
12754                 var dom = editor.dom,\r
12755                         selection = editor.selection,\r
12756                         commands = {state: {}, exec : {}, value : {}},\r
12757                         settings = editor.settings,\r
12758                         bookmark;\r
12759 \r
12760                 function execCommand(command, ui, value) {\r
12761                         var func;\r
12762 \r
12763                         command = command.toLowerCase();\r
12764                         if (func = commands.exec[command]) {\r
12765                                 func(command, ui, value);\r
12766                                 return TRUE;\r
12767                         }\r
12768 \r
12769                         return FALSE;\r
12770                 };\r
12771 \r
12772                 function queryCommandState(command) {\r
12773                         var func;\r
12774 \r
12775                         command = command.toLowerCase();\r
12776                         if (func = commands.state[command])\r
12777                                 return func(command);\r
12778 \r
12779                         return -1;\r
12780                 };\r
12781 \r
12782                 function queryCommandValue(command) {\r
12783                         var func;\r
12784 \r
12785                         command = command.toLowerCase();\r
12786                         if (func = commands.value[command])\r
12787                                 return func(command);\r
12788 \r
12789                         return FALSE;\r
12790                 };\r
12791 \r
12792                 function addCommands(command_list, type) {\r
12793                         type = type || 'exec';\r
12794 \r
12795                         each(command_list, function(callback, command) {\r
12796                                 each(command.toLowerCase().split(','), function(command) {\r
12797                                         commands[type][command] = callback;\r
12798                                 });\r
12799                         });\r
12800                 };\r
12801 \r
12802                 // Expose public methods\r
12803                 tinymce.extend(this, {\r
12804                         execCommand : execCommand,\r
12805                         queryCommandState : queryCommandState,\r
12806                         queryCommandValue : queryCommandValue,\r
12807                         addCommands : addCommands\r
12808                 });\r
12809 \r
12810                 // Private methods\r
12811 \r
12812                 function execNativeCommand(command, ui, value) {\r
12813                         if (ui === undefined)\r
12814                                 ui = FALSE;\r
12815 \r
12816                         if (value === undefined)\r
12817                                 value = null;\r
12818 \r
12819                         return editor.getDoc().execCommand(command, ui, value);\r
12820                 };\r
12821 \r
12822                 function isFormatMatch(name) {\r
12823                         return editor.formatter.match(name);\r
12824                 };\r
12825 \r
12826                 function toggleFormat(name, value) {\r
12827                         editor.formatter.toggle(name, value ? {value : value} : undefined);\r
12828                 };\r
12829 \r
12830                 function storeSelection(type) {\r
12831                         bookmark = selection.getBookmark(type);\r
12832                 };\r
12833 \r
12834                 function restoreSelection() {\r
12835                         selection.moveToBookmark(bookmark);\r
12836                 };\r
12837 \r
12838                 // Add execCommand overrides\r
12839                 addCommands({\r
12840                         // Ignore these, added for compatibility\r
12841                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},\r
12842 \r
12843                         // Add undo manager logic\r
12844                         'mceEndUndoLevel,mceAddUndoLevel' : function() {\r
12845                                 editor.undoManager.add();\r
12846                         },\r
12847 \r
12848                         'Cut,Copy,Paste' : function(command) {\r
12849                                 var doc = editor.getDoc(), failed;\r
12850 \r
12851                                 // Try executing the native command\r
12852                                 try {\r
12853                                         execNativeCommand(command);\r
12854                                 } catch (ex) {\r
12855                                         // Command failed\r
12856                                         failed = TRUE;\r
12857                                 }\r
12858 \r
12859                                 // Present alert message about clipboard access not being available\r
12860                                 if (failed || !doc.queryCommandSupported(command)) {\r
12861                                         if (tinymce.isGecko) {\r
12862                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {\r
12863                                                         if (state)\r
12864                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');\r
12865                                                 });\r
12866                                         } else\r
12867                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));\r
12868                                 }\r
12869                         },\r
12870 \r
12871                         // Override unlink command\r
12872                         unlink : function(command) {\r
12873                                 if (selection.isCollapsed())\r
12874                                         selection.select(selection.getNode());\r
12875 \r
12876                                 execNativeCommand(command);\r
12877                                 selection.collapse(FALSE);\r
12878                         },\r
12879 \r
12880                         // Override justify commands to use the text formatter engine\r
12881                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
12882                                 var align = command.substring(7);\r
12883 \r
12884                                 // Remove all other alignments first\r
12885                                 each('left,center,right,full'.split(','), function(name) {\r
12886                                         if (align != name)\r
12887                                                 editor.formatter.remove('align' + name);\r
12888                                 });\r
12889 \r
12890                                 toggleFormat('align' + align);\r
12891                                 execCommand('mceRepaint');\r
12892                         },\r
12893 \r
12894                         // Override list commands to fix WebKit bug\r
12895                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
12896                                 var listElm, listParent;\r
12897 \r
12898                                 execNativeCommand(command);\r
12899 \r
12900                                 // WebKit produces lists within block elements so we need to split them\r
12901                                 // we will replace the native list creation logic to custom logic later on\r
12902                                 // TODO: Remove this when the list creation logic is removed\r
12903                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');\r
12904                                 if (listElm) {\r
12905                                         listParent = listElm.parentNode;\r
12906 \r
12907                                         // If list is within a text block then split that block\r
12908                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {\r
12909                                                 storeSelection();\r
12910                                                 dom.split(listParent, listElm);\r
12911                                                 restoreSelection();\r
12912                                         }\r
12913                                 }\r
12914                         },\r
12915 \r
12916                         // Override commands to use the text formatter engine\r
12917                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
12918                                 toggleFormat(command);\r
12919                         },\r
12920 \r
12921                         // Override commands to use the text formatter engine\r
12922                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {\r
12923                                 toggleFormat(command, value);\r
12924                         },\r
12925 \r
12926                         FontSize : function(command, ui, value) {\r
12927                                 var fontClasses, fontSizes;\r
12928 \r
12929                                 // Convert font size 1-7 to styles\r
12930                                 if (value >= 1 && value <= 7) {\r
12931                                         fontSizes = tinymce.explode(settings.font_size_style_values);\r
12932                                         fontClasses = tinymce.explode(settings.font_size_classes);\r
12933 \r
12934                                         if (fontClasses)\r
12935                                                 value = fontClasses[value - 1] || value;\r
12936                                         else\r
12937                                                 value = fontSizes[value - 1] || value;\r
12938                                 }\r
12939 \r
12940                                 toggleFormat(command, value);\r
12941                         },\r
12942 \r
12943                         RemoveFormat : function(command) {\r
12944                                 editor.formatter.remove(command);\r
12945                         },\r
12946 \r
12947                         mceBlockQuote : function(command) {\r
12948                                 toggleFormat('blockquote');\r
12949                         },\r
12950 \r
12951                         FormatBlock : function(command, ui, value) {\r
12952                                 return toggleFormat(value || 'p');\r
12953                         },\r
12954 \r
12955                         mceCleanup : function() {\r
12956                                 var bookmark = selection.getBookmark();\r
12957 \r
12958                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});\r
12959 \r
12960                                 selection.moveToBookmark(bookmark);\r
12961                         },\r
12962 \r
12963                         mceRemoveNode : function(command, ui, value) {\r
12964                                 var node = value || selection.getNode();\r
12965 \r
12966                                 // Make sure that the body node isn't removed\r
12967                                 if (node != editor.getBody()) {\r
12968                                         storeSelection();\r
12969                                         editor.dom.remove(node, TRUE);\r
12970                                         restoreSelection();\r
12971                                 }\r
12972                         },\r
12973 \r
12974                         mceSelectNodeDepth : function(command, ui, value) {\r
12975                                 var counter = 0;\r
12976 \r
12977                                 dom.getParent(selection.getNode(), function(node) {\r
12978                                         if (node.nodeType == 1 && counter++ == value) {\r
12979                                                 selection.select(node);\r
12980                                                 return FALSE;\r
12981                                         }\r
12982                                 }, editor.getBody());\r
12983                         },\r
12984 \r
12985                         mceSelectNode : function(command, ui, value) {\r
12986                                 selection.select(value);\r
12987                         },\r
12988 \r
12989                         mceInsertContent : function(command, ui, value) {\r
12990                                 var parser, serializer, parentNode, rootNode, fragment, args,\r
12991                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;\r
12992 \r
12993                                 // Setup parser and serializer\r
12994                                 parser = editor.parser;\r
12995                                 serializer = new tinymce.html.Serializer({}, editor.schema);\r
12996                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';\r
12997 \r
12998                                 // Run beforeSetContent handlers on the HTML to be inserted\r
12999                                 args = {content: value, format: 'html'};\r
13000                                 selection.onBeforeSetContent.dispatch(selection, args);\r
13001                                 value = args.content;\r
13002 \r
13003                                 // Add caret at end of contents if it's missing\r
13004                                 if (value.indexOf('{$caret}') == -1)\r
13005                                         value += '{$caret}';\r
13006 \r
13007                                 // Replace the caret marker with a span bookmark element\r
13008                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);\r
13009 \r
13010                                 // Insert node maker where we will insert the new HTML and get it's parent\r
13011                                 if (!selection.isCollapsed())\r
13012                                         editor.getDoc().execCommand('Delete', false, null);\r
13013 \r
13014                                 parentNode = selection.getNode();\r
13015 \r
13016                                 // Parse the fragment within the context of the parent node\r
13017                                 args = {context : parentNode.nodeName.toLowerCase()};\r
13018                                 fragment = parser.parse(value, args);\r
13019 \r
13020                                 // Move the caret to a more suitable location\r
13021                                 node = fragment.lastChild;\r
13022                                 if (node.attr('id') == 'mce_marker') {\r
13023                                         marker = node;\r
13024 \r
13025                                         for (node = node.prev; node; node = node.walk(true)) {\r
13026                                                 if (node.type == 3 || !dom.isBlock(node.name)) {\r
13027                                                         node.parent.insert(marker, node, node.name === 'br');\r
13028                                                         break;\r
13029                                                 }\r
13030                                         }\r
13031                                 }\r
13032 \r
13033                                 // If parser says valid we can insert the contents into that parent\r
13034                                 if (!args.invalid) {\r
13035                                         value = serializer.serialize(fragment);\r
13036 \r
13037                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent\r
13038                                         node = parentNode.firstChild;\r
13039                                         node2 = parentNode.lastChild;\r
13040                                         if (!node || (node === node2 && node.nodeName === 'BR'))\r
13041                                                 dom.setHTML(parentNode, value);\r
13042                                         else\r
13043                                                 selection.setContent(value);\r
13044                                 } else {\r
13045                                         // If the fragment was invalid within that context then we need\r
13046                                         // to parse and process the parent it's inserted into\r
13047 \r
13048                                         // Insert bookmark node and get the parent\r
13049                                         selection.setContent(bookmarkHtml);\r
13050                                         parentNode = editor.selection.getNode();\r
13051                                         rootNode = editor.getBody();\r
13052 \r
13053                                         // Opera will return the document node when selection is in root\r
13054                                         if (parentNode.nodeType == 9)\r
13055                                                 parentNode = node = rootNode;\r
13056                                         else\r
13057                                                 node = parentNode;\r
13058 \r
13059                                         // Find the ancestor just before the root element\r
13060                                         while (node !== rootNode) {\r
13061                                                 parentNode = node;\r
13062                                                 node = node.parentNode;\r
13063                                         }\r
13064 \r
13065                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that\r
13066                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);\r
13067                                         value = serializer.serialize(\r
13068                                                 parser.parse(\r
13069                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem\r
13070                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+<\/span>/i, function() {\r
13071                                                                 return serializer.serialize(fragment);\r
13072                                                         })\r
13073                                                 )\r
13074                                         );\r
13075 \r
13076                                         // Set the inner/outer HTML depending on if we are in the root or not\r
13077                                         if (parentNode == rootNode)\r
13078                                                 dom.setHTML(rootNode, value);\r
13079                                         else\r
13080                                                 dom.setOuterHTML(parentNode, value);\r
13081                                 }\r
13082 \r
13083                                 marker = dom.get('mce_marker');\r
13084 \r
13085                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well\r
13086                                 nodeRect = dom.getRect(marker);\r
13087                                 viewPortRect = dom.getViewPort(editor.getWin());\r
13088 \r
13089                                 // Check if node is out side the viewport if it is then scroll to it\r
13090                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||\r
13091                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {\r
13092                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();\r
13093                                         viewportBodyElement.scrollLeft = nodeRect.x;\r
13094                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;\r
13095                                 }\r
13096 \r
13097                                 // Move selection before marker and remove it\r
13098                                 rng = dom.createRng();\r
13099 \r
13100                                 // If previous sibling is a text node set the selection to the end of that node\r
13101                                 node = marker.previousSibling;\r
13102                                 if (node && node.nodeType == 3) {\r
13103                                         rng.setStart(node, node.nodeValue.length);\r
13104                                 } else {\r
13105                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node\r
13106                                         rng.setStartBefore(marker);\r
13107                                         rng.setEndBefore(marker);\r
13108                                 }\r
13109 \r
13110                                 // Remove the marker node and set the new range\r
13111                                 dom.remove(marker);\r
13112                                 selection.setRng(rng);\r
13113 \r
13114                                 // Dispatch after event and add any visual elements needed\r
13115                                 selection.onSetContent.dispatch(selection, args);\r
13116                                 editor.addVisual();\r
13117                         },\r
13118 \r
13119                         mceInsertRawHTML : function(command, ui, value) {\r
13120                                 selection.setContent('tiny_mce_marker');\r
13121                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));\r
13122                         },\r
13123 \r
13124                         mceSetContent : function(command, ui, value) {\r
13125                                 editor.setContent(value);\r
13126                         },\r
13127 \r
13128                         'Indent,Outdent' : function(command) {\r
13129                                 var intentValue, indentUnit, value;\r
13130 \r
13131                                 // Setup indent level\r
13132                                 intentValue = settings.indentation;\r
13133                                 indentUnit = /[a-z%]+$/i.exec(intentValue);\r
13134                                 intentValue = parseInt(intentValue);\r
13135 \r
13136                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {\r
13137                                         each(selection.getSelectedBlocks(), function(element) {\r
13138                                                 if (command == 'outdent') {\r
13139                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);\r
13140                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');\r
13141                                                 } else\r
13142                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);\r
13143                                         });\r
13144                                 } else\r
13145                                         execNativeCommand(command);\r
13146                         },\r
13147 \r
13148                         mceRepaint : function() {\r
13149                                 var bookmark;\r
13150 \r
13151                                 if (tinymce.isGecko) {\r
13152                                         try {\r
13153                                                 storeSelection(TRUE);\r
13154 \r
13155                                                 if (selection.getSel())\r
13156                                                         selection.getSel().selectAllChildren(editor.getBody());\r
13157 \r
13158                                                 selection.collapse(TRUE);\r
13159                                                 restoreSelection();\r
13160                                         } catch (ex) {\r
13161                                                 // Ignore\r
13162                                         }\r
13163                                 }\r
13164                         },\r
13165 \r
13166                         mceToggleFormat : function(command, ui, value) {\r
13167                                 editor.formatter.toggle(value);\r
13168                         },\r
13169 \r
13170                         InsertHorizontalRule : function() {\r
13171                                 editor.execCommand('mceInsertContent', false, '<hr />');\r
13172                         },\r
13173 \r
13174                         mceToggleVisualAid : function() {\r
13175                                 editor.hasVisual = !editor.hasVisual;\r
13176                                 editor.addVisual();\r
13177                         },\r
13178 \r
13179                         mceReplaceContent : function(command, ui, value) {\r
13180                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));\r
13181                         },\r
13182 \r
13183                         mceInsertLink : function(command, ui, value) {\r
13184                                 var link = dom.getParent(selection.getNode(), 'a'), img, style, cls;\r
13185 \r
13186                                 if (tinymce.is(value, 'string'))\r
13187                                         value = {href : value};\r
13188 \r
13189                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.\r
13190                                 value.href = value.href.replace(' ', '%20');\r
13191 \r
13192                                 if (!link) {\r
13193                                         // WebKit can't create links on floated images for some odd reason\r
13194                                         // So, just remove styles and restore it later\r
13195                                         if (tinymce.isWebKit) {\r
13196                                                 img = dom.getParent(selection.getNode(), 'img');\r
13197 \r
13198                                                 if (img) {\r
13199                                                         style = img.style.cssText;\r
13200                                                         cls = img.className;\r
13201                                                         img.style.cssText = null;\r
13202                                                         img.className = null;\r
13203                                                 }\r
13204                                         }\r
13205 \r
13206                                         execNativeCommand('CreateLink', FALSE, 'javascript:mctmp(0);');\r
13207 \r
13208                                         // Restore styles\r
13209                                         if (style)\r
13210                                                 img.style.cssText = style;\r
13211                                         if (cls)\r
13212                                                 img.className = cls;\r
13213 \r
13214                                         each(dom.select("a[href='javascript:mctmp(0);']"), function(link) {\r
13215                                                 dom.setAttribs(link, value);\r
13216                                         });\r
13217                                 } else {\r
13218                                         if (value.href)\r
13219                                                 dom.setAttribs(link, value);\r
13220                                         else\r
13221                                                 editor.dom.remove(link, TRUE);\r
13222                                 }\r
13223                         },\r
13224                         \r
13225                         selectAll : function() {\r
13226                                 var root = dom.getRoot(), rng = dom.createRng();\r
13227 \r
13228                                 rng.setStart(root, 0);\r
13229                                 rng.setEnd(root, root.childNodes.length);\r
13230 \r
13231                                 editor.selection.setRng(rng);\r
13232                         }\r
13233                 });\r
13234 \r
13235                 // Add queryCommandState overrides\r
13236                 addCommands({\r
13237                         // Override justify commands\r
13238                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
13239                                 return isFormatMatch('align' + command.substring(7));\r
13240                         },\r
13241 \r
13242                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
13243                                 return isFormatMatch(command);\r
13244                         },\r
13245 \r
13246                         mceBlockQuote : function() {\r
13247                                 return isFormatMatch('blockquote');\r
13248                         },\r
13249 \r
13250                         Outdent : function() {\r
13251                                 var node;\r
13252 \r
13253                                 if (settings.inline_styles) {\r
13254                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
13255                                                 return TRUE;\r
13256 \r
13257                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
13258                                                 return TRUE;\r
13259                                 }\r
13260 \r
13261                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));\r
13262                         },\r
13263 \r
13264                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
13265                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');\r
13266                         }\r
13267                 }, 'state');\r
13268 \r
13269                 // Add queryCommandValue overrides\r
13270                 addCommands({\r
13271                         'FontSize,FontName' : function(command) {\r
13272                                 var value = 0, parent;\r
13273 \r
13274                                 if (parent = dom.getParent(selection.getNode(), 'span')) {\r
13275                                         if (command == 'fontsize')\r
13276                                                 value = parent.style.fontSize;\r
13277                                         else\r
13278                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();\r
13279                                 }\r
13280 \r
13281                                 return value;\r
13282                         }\r
13283                 }, 'value');\r
13284 \r
13285                 // Add undo manager logic\r
13286                 if (settings.custom_undo_redo) {\r
13287                         addCommands({\r
13288                                 Undo : function() {\r
13289                                         editor.undoManager.undo();\r
13290                                 },\r
13291 \r
13292                                 Redo : function() {\r
13293                                         editor.undoManager.redo();\r
13294                                 }\r
13295                         });\r
13296                 }\r
13297         };\r
13298 })(tinymce);\r
13299 \r
13300 (function(tinymce) {\r
13301         var Dispatcher = tinymce.util.Dispatcher;\r
13302 \r
13303         tinymce.UndoManager = function(editor) {\r
13304                 var self, index = 0, data = [], beforeBookmark;\r
13305 \r
13306                 function getContent() {\r
13307                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));\r
13308                 };\r
13309 \r
13310                 return self = {\r
13311                         typing : false,\r
13312 \r
13313                         onAdd : new Dispatcher(self),\r
13314 \r
13315                         onUndo : new Dispatcher(self),\r
13316 \r
13317                         onRedo : new Dispatcher(self),\r
13318 \r
13319                         beforeChange : function() {\r
13320                                 beforeBookmark = editor.selection.getBookmark(2, true);\r
13321                         },\r
13322 \r
13323                         add : function(level) {\r
13324                                 var i, settings = editor.settings, lastLevel;\r
13325 \r
13326                                 level = level || {};\r
13327                                 level.content = getContent();\r
13328 \r
13329                                 // Add undo level if needed\r
13330                                 lastLevel = data[index];\r
13331                                 if (lastLevel && lastLevel.content == level.content)\r
13332                                         return null;\r
13333 \r
13334                                 // Set before bookmark on previous level\r
13335                                 if (data[index])\r
13336                                         data[index].beforeBookmark = beforeBookmark;\r
13337 \r
13338                                 // Time to compress\r
13339                                 if (settings.custom_undo_redo_levels) {\r
13340                                         if (data.length > settings.custom_undo_redo_levels) {\r
13341                                                 for (i = 0; i < data.length - 1; i++)\r
13342                                                         data[i] = data[i + 1];\r
13343 \r
13344                                                 data.length--;\r
13345                                                 index = data.length;\r
13346                                         }\r
13347                                 }\r
13348 \r
13349                                 // Get a non intrusive normalized bookmark\r
13350                                 level.bookmark = editor.selection.getBookmark(2, true);\r
13351 \r
13352                                 // Crop array if needed\r
13353                                 if (index < data.length - 1)\r
13354                                         data.length = index + 1;\r
13355 \r
13356                                 data.push(level);\r
13357                                 index = data.length - 1;\r
13358 \r
13359                                 self.onAdd.dispatch(self, level);\r
13360                                 editor.isNotDirty = 0;\r
13361 \r
13362                                 return level;\r
13363                         },\r
13364 \r
13365                         undo : function() {\r
13366                                 var level, i;\r
13367 \r
13368                                 if (self.typing) {\r
13369                                         self.add();\r
13370                                         self.typing = false;\r
13371                                 }\r
13372 \r
13373                                 if (index > 0) {\r
13374                                         level = data[--index];\r
13375 \r
13376                                         editor.setContent(level.content, {format : 'raw'});\r
13377                                         editor.selection.moveToBookmark(level.beforeBookmark);\r
13378 \r
13379                                         self.onUndo.dispatch(self, level);\r
13380                                 }\r
13381 \r
13382                                 return level;\r
13383                         },\r
13384 \r
13385                         redo : function() {\r
13386                                 var level;\r
13387 \r
13388                                 if (index < data.length - 1) {\r
13389                                         level = data[++index];\r
13390 \r
13391                                         editor.setContent(level.content, {format : 'raw'});\r
13392                                         editor.selection.moveToBookmark(level.bookmark);\r
13393 \r
13394                                         self.onRedo.dispatch(self, level);\r
13395                                 }\r
13396 \r
13397                                 return level;\r
13398                         },\r
13399 \r
13400                         clear : function() {\r
13401                                 data = [];\r
13402                                 index = 0;\r
13403                                 self.typing = false;\r
13404                         },\r
13405 \r
13406                         hasUndo : function() {\r
13407                                 return index > 0 || this.typing;\r
13408                         },\r
13409 \r
13410                         hasRedo : function() {\r
13411                                 return index < data.length - 1 && !this.typing;\r
13412                         }\r
13413                 };\r
13414         };\r
13415 })(tinymce);\r
13416 \r
13417 (function(tinymce) {\r
13418         // Shorten names\r
13419         var Event = tinymce.dom.Event,\r
13420                 isIE = tinymce.isIE,\r
13421                 isGecko = tinymce.isGecko,\r
13422                 isOpera = tinymce.isOpera,\r
13423                 each = tinymce.each,\r
13424                 extend = tinymce.extend,\r
13425                 TRUE = true,\r
13426                 FALSE = false;\r
13427 \r
13428         function cloneFormats(node) {\r
13429                 var clone, temp, inner;\r
13430 \r
13431                 do {\r
13432                         if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {\r
13433                                 if (clone) {\r
13434                                         temp = node.cloneNode(false);\r
13435                                         temp.appendChild(clone);\r
13436                                         clone = temp;\r
13437                                 } else {\r
13438                                         clone = inner = node.cloneNode(false);\r
13439                                 }\r
13440 \r
13441                                 clone.removeAttribute('id');\r
13442                         }\r
13443                 } while (node = node.parentNode);\r
13444 \r
13445                 if (clone)\r
13446                         return {wrapper : clone, inner : inner};\r
13447         };\r
13448 \r
13449         // Checks if the selection/caret is at the end of the specified block element\r
13450         function isAtEnd(rng, par) {\r
13451                 var rng2 = par.ownerDocument.createRange();\r
13452 \r
13453                 rng2.setStart(rng.endContainer, rng.endOffset);\r
13454                 rng2.setEndAfter(par);\r
13455 \r
13456                 // 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
13457                 return rng2.cloneContents().textContent.length == 0;\r
13458         };\r
13459 \r
13460         function splitList(selection, dom, li) {\r
13461                 var listBlock, block;\r
13462 \r
13463                 if (dom.isEmpty(li)) {\r
13464                         listBlock = dom.getParent(li, 'ul,ol');\r
13465 \r
13466                         if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {\r
13467                                 dom.split(listBlock, li);\r
13468                                 block = dom.create('p', 0, '<br data-mce-bogus="1" />');\r
13469                                 dom.replace(block, li);\r
13470                                 selection.select(block, 1);\r
13471                         }\r
13472 \r
13473                         return FALSE;\r
13474                 }\r
13475 \r
13476                 return TRUE;\r
13477         };\r
13478 \r
13479         tinymce.create('tinymce.ForceBlocks', {\r
13480                 ForceBlocks : function(ed) {\r
13481                         var t = this, s = ed.settings, elm;\r
13482 \r
13483                         t.editor = ed;\r
13484                         t.dom = ed.dom;\r
13485                         elm = (s.forced_root_block || 'p').toLowerCase();\r
13486                         s.element = elm.toUpperCase();\r
13487 \r
13488                         ed.onPreInit.add(t.setup, t);\r
13489                 },\r
13490 \r
13491                 setup : function() {\r
13492                         var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();\r
13493 \r
13494                         // Force root blocks\r
13495                         if (s.forced_root_block) {\r
13496                                 function addRootBlocks() {\r
13497                                         var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;\r
13498 \r
13499                                         if (!node || node.nodeType !== 1)\r
13500                                                 return;\r
13501 \r
13502                                         // Check if node is wrapped in block\r
13503                                         while (node != rootNode) {\r
13504                                                 if (blockElements[node.nodeName])\r
13505                                                         return;\r
13506 \r
13507                                                 node = node.parentNode;\r
13508                                         }\r
13509 \r
13510                                         // Get current selection\r
13511                                         rng = selection.getRng();\r
13512                                         if (rng.setStart) {\r
13513                                                 startContainer = rng.startContainer;\r
13514                                                 startOffset = rng.startOffset;\r
13515                                                 endContainer = rng.endContainer;\r
13516                                                 endOffset = rng.endOffset;\r
13517                                         } else {\r
13518                                                 // Force control range into text range\r
13519                                                 if (rng.item) {\r
13520                                                         rng = ed.getDoc().body.createTextRange();\r
13521                                                         rng.moveToElementText(rng.item(0));\r
13522                                                 }\r
13523 \r
13524                                                 tmpRng = rng.duplicate();\r
13525                                                 tmpRng.collapse(true);\r
13526                                                 startOffset = tmpRng.move('character', offset) * -1;\r
13527 \r
13528                                                 if (!tmpRng.collapsed) {\r
13529                                                         tmpRng = rng.duplicate();\r
13530                                                         tmpRng.collapse(false);\r
13531                                                         endOffset = (tmpRng.move('character', offset) * -1) - startOffset;\r
13532                                                 }\r
13533                                         }\r
13534 \r
13535                                         // Wrap non block elements and text nodes\r
13536                                         for (node = rootNode.firstChild; node; node) {\r
13537                                                 if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {\r
13538                                                         if (!rootBlockNode) {\r
13539                                                                 rootBlockNode = dom.create(s.forced_root_block);\r
13540                                                                 node.parentNode.insertBefore(rootBlockNode, node);\r
13541                                                         }\r
13542 \r
13543                                                         tempNode = node;\r
13544                                                         node = node.nextSibling;\r
13545                                                         rootBlockNode.appendChild(tempNode);\r
13546                                                 } else {\r
13547                                                         rootBlockNode = null;\r
13548                                                         node = node.nextSibling;\r
13549                                                 }\r
13550                                         }\r
13551 \r
13552                                         if (rng.setStart) {\r
13553                                                 rng.setStart(startContainer, startOffset);\r
13554                                                 rng.setEnd(endContainer, endOffset);\r
13555                                                 selection.setRng(rng);\r
13556                                         } else {\r
13557                                                 try {\r
13558                                                         rng = ed.getDoc().body.createTextRange();\r
13559                                                         rng.moveToElementText(rootNode);\r
13560                                                         rng.collapse(true);\r
13561                                                         rng.moveStart('character', startOffset);\r
13562 \r
13563                                                         if (endOffset > 0)\r
13564                                                                 rng.moveEnd('character', endOffset);\r
13565 \r
13566                                                         rng.select();\r
13567                                                 } catch (ex) {\r
13568                                                         // Ignore\r
13569                                                 }\r
13570                                         }\r
13571 \r
13572                                         ed.nodeChanged();\r
13573                                 };\r
13574 \r
13575                                 ed.onKeyUp.add(addRootBlocks);\r
13576                                 ed.onClick.add(addRootBlocks);\r
13577                         }\r
13578 \r
13579                         if (s.force_br_newlines) {\r
13580                                 // Force IE to produce BRs on enter\r
13581                                 if (isIE) {\r
13582                                         ed.onKeyPress.add(function(ed, e) {\r
13583                                                 var n;\r
13584 \r
13585                                                 if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {\r
13586                                                         selection.setContent('<br id="__" /> ', {format : 'raw'});\r
13587                                                         n = dom.get('__');\r
13588                                                         n.removeAttribute('id');\r
13589                                                         selection.select(n);\r
13590                                                         selection.collapse();\r
13591                                                         return Event.cancel(e);\r
13592                                                 }\r
13593                                         });\r
13594                                 }\r
13595                         }\r
13596 \r
13597                         if (s.force_p_newlines) {\r
13598                                 if (!isIE) {\r
13599                                         ed.onKeyPress.add(function(ed, e) {\r
13600                                                 if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))\r
13601                                                         Event.cancel(e);\r
13602                                         });\r
13603                                 } else {\r
13604                                         // Ungly hack to for IE to preserve the formatting when you press\r
13605                                         // enter at the end of a block element with formatted contents\r
13606                                         // This logic overrides the browsers default logic with\r
13607                                         // custom logic that enables us to control the output\r
13608                                         tinymce.addUnload(function() {\r
13609                                                 t._previousFormats = 0; // Fix IE leak\r
13610                                         });\r
13611 \r
13612                                         ed.onKeyPress.add(function(ed, e) {\r
13613                                                 t._previousFormats = 0;\r
13614 \r
13615                                                 // Clone the current formats, this will later be applied to the new block contents\r
13616                                                 if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)\r
13617                                                         t._previousFormats = cloneFormats(ed.selection.getStart());\r
13618                                         });\r
13619 \r
13620                                         ed.onKeyUp.add(function(ed, e) {\r
13621                                                 // Let IE break the element and the wrap the new caret location in the previous formats\r
13622                                                 if (e.keyCode == 13 && !e.shiftKey) {\r
13623                                                         var parent = ed.selection.getStart(), fmt = t._previousFormats;\r
13624 \r
13625                                                         // Parent is an empty block\r
13626                                                         if (!parent.hasChildNodes() && fmt) {\r
13627                                                                 parent = dom.getParent(parent, dom.isBlock);\r
13628 \r
13629                                                                 if (parent && parent.nodeName != 'LI') {\r
13630                                                                         parent.innerHTML = '';\r
13631 \r
13632                                                                         if (t._previousFormats) {\r
13633                                                                                 parent.appendChild(fmt.wrapper);\r
13634                                                                                 fmt.inner.innerHTML = '\uFEFF';\r
13635                                                                         } else\r
13636                                                                                 parent.innerHTML = '\uFEFF';\r
13637 \r
13638                                                                         selection.select(parent, 1);\r
13639                                                                         selection.collapse(true);\r
13640                                                                         ed.getDoc().execCommand('Delete', false, null);\r
13641                                                                         t._previousFormats = 0;\r
13642                                                                 }\r
13643                                                         }\r
13644                                                 }\r
13645                                         });\r
13646                                 }\r
13647 \r
13648                                 if (isGecko) {\r
13649                                         ed.onKeyDown.add(function(ed, e) {\r
13650                                                 if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)\r
13651                                                         t.backspaceDelete(e, e.keyCode == 8);\r
13652                                         });\r
13653                                 }\r
13654                         }\r
13655 \r
13656                         // Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973\r
13657                         if (tinymce.isWebKit) {\r
13658                                 function insertBr(ed) {\r
13659                                         var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;\r
13660 \r
13661                                         // Insert BR element\r
13662                                         rng.insertNode(br = dom.create('br'));\r
13663 \r
13664                                         // Place caret after BR\r
13665                                         rng.setStartAfter(br);\r
13666                                         rng.setEndAfter(br);\r
13667                                         selection.setRng(rng);\r
13668 \r
13669                                         // Could not place caret after BR then insert an nbsp entity and move the caret\r
13670                                         if (selection.getSel().focusNode == br.previousSibling) {\r
13671                                                 selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));\r
13672                                                 selection.collapse(TRUE);\r
13673                                         }\r
13674 \r
13675                                         // Create a temporary DIV after the BR and get the position as it\r
13676                                         // seems like getPos() returns 0 for text nodes and BR elements.\r
13677                                         dom.insertAfter(div, br);\r
13678                                         divYPos = dom.getPos(div).y;\r
13679                                         dom.remove(div);\r
13680 \r
13681                                         // Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117\r
13682                                         if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.\r
13683                                                 ed.getWin().scrollTo(0, divYPos);\r
13684                                 };\r
13685 \r
13686                                 ed.onKeyPress.add(function(ed, e) {\r
13687                                         if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {\r
13688                                                 insertBr(ed);\r
13689                                                 Event.cancel(e);\r
13690                                         }\r
13691                                 });\r
13692                         }\r
13693 \r
13694                         // IE specific fixes\r
13695                         if (isIE) {\r
13696                                 // Replaces IE:s auto generated paragraphs with the specified element name\r
13697                                 if (s.element != 'P') {\r
13698                                         ed.onKeyPress.add(function(ed, e) {\r
13699                                                 t.lastElm = selection.getNode().nodeName;\r
13700                                         });\r
13701 \r
13702                                         ed.onKeyUp.add(function(ed, e) {\r
13703                                                 var bl, n = selection.getNode(), b = ed.getBody();\r
13704 \r
13705                                                 if (b.childNodes.length === 1 && n.nodeName == 'P') {\r
13706                                                         n = dom.rename(n, s.element);\r
13707                                                         selection.select(n);\r
13708                                                         selection.collapse();\r
13709                                                         ed.nodeChanged();\r
13710                                                 } else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {\r
13711                                                         bl = dom.getParent(n, 'p');\r
13712 \r
13713                                                         if (bl) {\r
13714                                                                 dom.rename(bl, s.element);\r
13715                                                                 ed.nodeChanged();\r
13716                                                         }\r
13717                                                 }\r
13718                                         });\r
13719                                 }\r
13720                         }\r
13721                 },\r
13722 \r
13723                 getParentBlock : function(n) {\r
13724                         var d = this.dom;\r
13725 \r
13726                         return d.getParent(n, d.isBlock);\r
13727                 },\r
13728 \r
13729                 insertPara : function(e) {\r
13730                         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
13731                         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
13732 \r
13733                         ed.undoManager.beforeChange();\r
13734 \r
13735                         // If root blocks are forced then use Operas default behavior since it's really good\r
13736 // Removed due to bug: #1853816\r
13737 //                      if (se.forced_root_block && isOpera)\r
13738 //                              return TRUE;\r
13739 \r
13740                         // Setup before range\r
13741                         rb = d.createRange();\r
13742 \r
13743                         // If is before the first block element and in body, then move it into first block element\r
13744                         rb.setStart(s.anchorNode, s.anchorOffset);\r
13745                         rb.collapse(TRUE);\r
13746 \r
13747                         // Setup after range\r
13748                         ra = d.createRange();\r
13749 \r
13750                         // If is before the first block element and in body, then move it into first block element\r
13751                         ra.setStart(s.focusNode, s.focusOffset);\r
13752                         ra.collapse(TRUE);\r
13753 \r
13754                         // Setup start/end points\r
13755                         dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;\r
13756                         sn = dir ? s.anchorNode : s.focusNode;\r
13757                         so = dir ? s.anchorOffset : s.focusOffset;\r
13758                         en = dir ? s.focusNode : s.anchorNode;\r
13759                         eo = dir ? s.focusOffset : s.anchorOffset;\r
13760 \r
13761                         // If selection is in empty table cell\r
13762                         if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {\r
13763                                 if (sn.firstChild.nodeName == 'BR')\r
13764                                         dom.remove(sn.firstChild); // Remove BR\r
13765 \r
13766                                 // Create two new block elements\r
13767                                 if (sn.childNodes.length == 0) {\r
13768                                         ed.dom.add(sn, se.element, null, '<br />');\r
13769                                         aft = ed.dom.add(sn, se.element, null, '<br />');\r
13770                                 } else {\r
13771                                         n = sn.innerHTML;\r
13772                                         sn.innerHTML = '';\r
13773                                         ed.dom.add(sn, se.element, null, n);\r
13774                                         aft = ed.dom.add(sn, se.element, null, '<br />');\r
13775                                 }\r
13776 \r
13777                                 // Move caret into the last one\r
13778                                 r = d.createRange();\r
13779                                 r.selectNodeContents(aft);\r
13780                                 r.collapse(1);\r
13781                                 ed.selection.setRng(r);\r
13782 \r
13783                                 return FALSE;\r
13784                         }\r
13785 \r
13786                         // If the caret is in an invalid location in FF we need to move it into the first block\r
13787                         if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {\r
13788                                 sn = en = sn.firstChild;\r
13789                                 so = eo = 0;\r
13790                                 rb = d.createRange();\r
13791                                 rb.setStart(sn, 0);\r
13792                                 ra = d.createRange();\r
13793                                 ra.setStart(en, 0);\r
13794                         }\r
13795 \r
13796                         // Never use body as start or end node\r
13797                         sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
13798                         sn = sn.nodeName == "BODY" ? sn.firstChild : sn;\r
13799                         en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes\r
13800                         en = en.nodeName == "BODY" ? en.firstChild : en;\r
13801 \r
13802                         // Get start and end blocks\r
13803                         sb = t.getParentBlock(sn);\r
13804                         eb = t.getParentBlock(en);\r
13805                         bn = sb ? sb.nodeName : se.element; // Get block name to create\r
13806 \r
13807                         // Return inside list use default browser behavior\r
13808                         if (n = t.dom.getParent(sb, 'li,pre')) {\r
13809                                 if (n.nodeName == 'LI')\r
13810                                         return splitList(ed.selection, t.dom, n);\r
13811 \r
13812                                 return TRUE;\r
13813                         }\r
13814 \r
13815                         // If caption or absolute layers then always generate new blocks within\r
13816                         if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
13817                                 bn = se.element;\r
13818                                 sb = null;\r
13819                         }\r
13820 \r
13821                         // If caption or absolute layers then always generate new blocks within\r
13822                         if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {\r
13823                                 bn = se.element;\r
13824                                 eb = null;\r
13825                         }\r
13826 \r
13827                         // Use P instead\r
13828                         if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {\r
13829                                 bn = se.element;\r
13830                                 sb = eb = null;\r
13831                         }\r
13832 \r
13833                         // Setup new before and after blocks\r
13834                         bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);\r
13835                         aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);\r
13836 \r
13837                         // Remove id from after clone\r
13838                         aft.removeAttribute('id');\r
13839 \r
13840                         // Is header and cursor is at the end, then force paragraph under\r
13841                         if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))\r
13842                                 aft = ed.dom.create(se.element);\r
13843 \r
13844                         // Find start chop node\r
13845                         n = sc = sn;\r
13846                         do {\r
13847                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
13848                                         break;\r
13849 \r
13850                                 sc = n;\r
13851                         } while ((n = n.previousSibling ? n.previousSibling : n.parentNode));\r
13852 \r
13853                         // Find end chop node\r
13854                         n = ec = en;\r
13855                         do {\r
13856                                 if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))\r
13857                                         break;\r
13858 \r
13859                                 ec = n;\r
13860                         } while ((n = n.nextSibling ? n.nextSibling : n.parentNode));\r
13861 \r
13862                         // Place first chop part into before block element\r
13863                         if (sc.nodeName == bn)\r
13864                                 rb.setStart(sc, 0);\r
13865                         else\r
13866                                 rb.setStartBefore(sc);\r
13867 \r
13868                         rb.setEnd(sn, so);\r
13869                         bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
13870 \r
13871                         // Place secnd chop part within new block element\r
13872                         try {\r
13873                                 ra.setEndAfter(ec);\r
13874                         } catch(ex) {\r
13875                                 //console.debug(s.focusNode, s.focusOffset);\r
13876                         }\r
13877 \r
13878                         ra.setStart(en, eo);\r
13879                         aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari\r
13880 \r
13881                         // Create range around everything\r
13882                         r = d.createRange();\r
13883                         if (!sc.previousSibling && sc.parentNode.nodeName == bn) {\r
13884                                 r.setStartBefore(sc.parentNode);\r
13885                         } else {\r
13886                                 if (rb.startContainer.nodeName == bn && rb.startOffset == 0)\r
13887                                         r.setStartBefore(rb.startContainer);\r
13888                                 else\r
13889                                         r.setStart(rb.startContainer, rb.startOffset);\r
13890                         }\r
13891 \r
13892                         if (!ec.nextSibling && ec.parentNode.nodeName == bn)\r
13893                                 r.setEndAfter(ec.parentNode);\r
13894                         else\r
13895                                 r.setEnd(ra.endContainer, ra.endOffset);\r
13896 \r
13897                         // Delete and replace it with new block elements\r
13898                         r.deleteContents();\r
13899 \r
13900                         if (isOpera)\r
13901                                 ed.getWin().scrollTo(0, vp.y);\r
13902 \r
13903                         // Never wrap blocks in blocks\r
13904                         if (bef.firstChild && bef.firstChild.nodeName == bn)\r
13905                                 bef.innerHTML = bef.firstChild.innerHTML;\r
13906 \r
13907                         if (aft.firstChild && aft.firstChild.nodeName == bn)\r
13908                                 aft.innerHTML = aft.firstChild.innerHTML;\r
13909 \r
13910                         function appendStyles(e, en) {\r
13911                                 var nl = [], nn, n, i;\r
13912 \r
13913                                 e.innerHTML = '';\r
13914 \r
13915                                 // Make clones of style elements\r
13916                                 if (se.keep_styles) {\r
13917                                         n = en;\r
13918                                         do {\r
13919                                                 // We only want style specific elements\r
13920                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {\r
13921                                                         nn = n.cloneNode(FALSE);\r
13922                                                         dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique\r
13923                                                         nl.push(nn);\r
13924                                                 }\r
13925                                         } while (n = n.parentNode);\r
13926                                 }\r
13927 \r
13928                                 // Append style elements to aft\r
13929                                 if (nl.length > 0) {\r
13930                                         for (i = nl.length - 1, nn = e; i >= 0; i--)\r
13931                                                 nn = nn.appendChild(nl[i]);\r
13932 \r
13933                                         // Padd most inner style element\r
13934                                         nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
13935                                         return nl[0]; // Move caret to most inner element\r
13936                                 } else\r
13937                                         e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there\r
13938                         };\r
13939                                 \r
13940                         // Padd empty blocks\r
13941                         if (dom.isEmpty(bef))\r
13942                                 appendStyles(bef, sn);\r
13943 \r
13944                         // Fill empty afterblook with current style\r
13945                         if (dom.isEmpty(aft))\r
13946                                 car = appendStyles(aft, en);\r
13947 \r
13948                         // Opera needs this one backwards for older versions\r
13949                         if (isOpera && parseFloat(opera.version()) < 9.5) {\r
13950                                 r.insertNode(bef);\r
13951                                 r.insertNode(aft);\r
13952                         } else {\r
13953                                 r.insertNode(aft);\r
13954                                 r.insertNode(bef);\r
13955                         }\r
13956 \r
13957                         // Normalize\r
13958                         aft.normalize();\r
13959                         bef.normalize();\r
13960 \r
13961                         // Move cursor and scroll into view\r
13962                         ed.selection.select(aft, true);\r
13963                         ed.selection.collapse(true);\r
13964 \r
13965                         // 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
13966                         y = ed.dom.getPos(aft).y;\r
13967                         //ch = aft.clientHeight;\r
13968 \r
13969                         // Is element within viewport\r
13970                         if (y < vp.y || y + 25 > vp.y + vp.h) {\r
13971                                 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
13972 \r
13973                                 /*console.debug(\r
13974                                         'Element: y=' + y + ', h=' + ch + ', ' +\r
13975                                         'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)\r
13976                                 );*/\r
13977                         }\r
13978 \r
13979                         ed.undoManager.add();\r
13980 \r
13981                         return FALSE;\r
13982                 },\r
13983 \r
13984                 backspaceDelete : function(e, bs) {\r
13985                         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
13986 \r
13987                         // Delete when caret is behind a element doesn't work correctly on Gecko see #3011651\r
13988                         if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {\r
13989                                 walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);\r
13990 \r
13991                                 // Walk the dom backwards until we find a text node\r
13992                                 for (n = sc.lastChild; n; n = walker.prev()) {\r
13993                                         if (n.nodeType == 3) {\r
13994                                                 r.setStart(n, n.nodeValue.length);\r
13995                                                 r.collapse(true);\r
13996                                                 se.setRng(r);\r
13997                                                 return;\r
13998                                         }\r
13999                                 }\r
14000                         }\r
14001 \r
14002                         // The caret sometimes gets stuck in Gecko if you delete empty paragraphs\r
14003                         // This workaround removes the element by hand and moves the caret to the previous element\r
14004                         if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {\r
14005                                 if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {\r
14006                                         // Find previous block element\r
14007                                         n = sc;\r
14008                                         while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;\r
14009 \r
14010                                         if (n) {\r
14011                                                 if (sc != b.firstChild) {\r
14012                                                         // Find last text node\r
14013                                                         w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);\r
14014                                                         while (tn = w.nextNode())\r
14015                                                                 n = tn;\r
14016 \r
14017                                                         // Place caret at the end of last text node\r
14018                                                         r = ed.getDoc().createRange();\r
14019                                                         r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);\r
14020                                                         r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);\r
14021                                                         se.setRng(r);\r
14022 \r
14023                                                         // Remove the target container\r
14024                                                         ed.dom.remove(sc);\r
14025                                                 }\r
14026 \r
14027                                                 return Event.cancel(e);\r
14028                                         }\r
14029                                 }\r
14030                         }\r
14031                 }\r
14032         });\r
14033 })(tinymce);\r
14034 \r
14035 (function(tinymce) {\r
14036         // Shorten names\r
14037         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;\r
14038 \r
14039         tinymce.create('tinymce.ControlManager', {\r
14040                 ControlManager : function(ed, s) {\r
14041                         var t = this, i;\r
14042 \r
14043                         s = s || {};\r
14044                         t.editor = ed;\r
14045                         t.controls = {};\r
14046                         t.onAdd = new tinymce.util.Dispatcher(t);\r
14047                         t.onPostRender = new tinymce.util.Dispatcher(t);\r
14048                         t.prefix = s.prefix || ed.id + '_';\r
14049                         t._cls = {};\r
14050 \r
14051                         t.onPostRender.add(function() {\r
14052                                 each(t.controls, function(c) {\r
14053                                         c.postRender();\r
14054                                 });\r
14055                         });\r
14056                 },\r
14057 \r
14058                 get : function(id) {\r
14059                         return this.controls[this.prefix + id] || this.controls[id];\r
14060                 },\r
14061 \r
14062                 setActive : function(id, s) {\r
14063                         var c = null;\r
14064 \r
14065                         if (c = this.get(id))\r
14066                                 c.setActive(s);\r
14067 \r
14068                         return c;\r
14069                 },\r
14070 \r
14071                 setDisabled : function(id, s) {\r
14072                         var c = null;\r
14073 \r
14074                         if (c = this.get(id))\r
14075                                 c.setDisabled(s);\r
14076 \r
14077                         return c;\r
14078                 },\r
14079 \r
14080                 add : function(c) {\r
14081                         var t = this;\r
14082 \r
14083                         if (c) {\r
14084                                 t.controls[c.id] = c;\r
14085                                 t.onAdd.dispatch(c, t);\r
14086                         }\r
14087 \r
14088                         return c;\r
14089                 },\r
14090 \r
14091                 createControl : function(n) {\r
14092                         var c, t = this, ed = t.editor;\r
14093 \r
14094                         each(ed.plugins, function(p) {\r
14095                                 if (p.createControl) {\r
14096                                         c = p.createControl(n, t);\r
14097 \r
14098                                         if (c)\r
14099                                                 return false;\r
14100                                 }\r
14101                         });\r
14102 \r
14103                         switch (n) {\r
14104                                 case "|":\r
14105                                 case "separator":\r
14106                                         return t.createSeparator();\r
14107                         }\r
14108 \r
14109                         if (!c && ed.buttons && (c = ed.buttons[n]))\r
14110                                 return t.createButton(n, c);\r
14111 \r
14112                         return t.add(c);\r
14113                 },\r
14114 \r
14115                 createDropMenu : function(id, s, cc) {\r
14116                         var t = this, ed = t.editor, c, bm, v, cls;\r
14117 \r
14118                         s = extend({\r
14119                                 'class' : 'mceDropDown',\r
14120                                 constrain : ed.settings.constrain_menus\r
14121                         }, s);\r
14122 \r
14123                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';\r
14124                         if (v = ed.getParam('skin_variant'))\r
14125                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);\r
14126 \r
14127                         id = t.prefix + id;\r
14128                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;\r
14129                         c = t.controls[id] = new cls(id, s);\r
14130                         c.onAddItem.add(function(c, o) {\r
14131                                 var s = o.settings;\r
14132 \r
14133                                 s.title = ed.getLang(s.title, s.title);\r
14134 \r
14135                                 if (!s.onclick) {\r
14136                                         s.onclick = function(v) {\r
14137                                                 if (s.cmd)\r
14138                                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
14139                                         };\r
14140                                 }\r
14141                         });\r
14142 \r
14143                         ed.onRemove.add(function() {\r
14144                                 c.destroy();\r
14145                         });\r
14146 \r
14147                         // Fix for bug #1897785, #1898007\r
14148                         if (tinymce.isIE) {\r
14149                                 c.onShowMenu.add(function() {\r
14150                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
14151                                         ed.focus();\r
14152 \r
14153                                         bm = ed.selection.getBookmark(1);\r
14154                                 });\r
14155 \r
14156                                 c.onHideMenu.add(function() {\r
14157                                         if (bm) {\r
14158                                                 ed.selection.moveToBookmark(bm);\r
14159                                                 bm = 0;\r
14160                                         }\r
14161                                 });\r
14162                         }\r
14163 \r
14164                         return t.add(c);\r
14165                 },\r
14166 \r
14167                 createListBox : function(id, s, cc) {\r
14168                         var t = this, ed = t.editor, cmd, c, cls;\r
14169 \r
14170                         if (t.get(id))\r
14171                                 return null;\r
14172 \r
14173                         s.title = ed.translate(s.title);\r
14174                         s.scope = s.scope || ed;\r
14175 \r
14176                         if (!s.onselect) {\r
14177                                 s.onselect = function(v) {\r
14178                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14179                                 };\r
14180                         }\r
14181 \r
14182                         s = extend({\r
14183                                 title : s.title,\r
14184                                 'class' : 'mce_' + id,\r
14185                                 scope : s.scope,\r
14186                                 control_manager : t\r
14187                         }, s);\r
14188 \r
14189                         id = t.prefix + id;\r
14190 \r
14191                         if (ed.settings.use_native_selects)\r
14192                                 c = new tinymce.ui.NativeListBox(id, s);\r
14193                         else {\r
14194                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;\r
14195                                 c = new cls(id, s, ed);\r
14196                         }\r
14197 \r
14198                         t.controls[id] = c;\r
14199 \r
14200                         // Fix focus problem in Safari\r
14201                         if (tinymce.isWebKit) {\r
14202                                 c.onPostRender.add(function(c, n) {\r
14203                                         // Store bookmark on mousedown\r
14204                                         Event.add(n, 'mousedown', function() {\r
14205                                                 ed.bookmark = ed.selection.getBookmark(1);\r
14206                                         });\r
14207 \r
14208                                         // Restore on focus, since it might be lost\r
14209                                         Event.add(n, 'focus', function() {\r
14210                                                 ed.selection.moveToBookmark(ed.bookmark);\r
14211                                                 ed.bookmark = null;\r
14212                                         });\r
14213                                 });\r
14214                         }\r
14215 \r
14216                         if (c.hideMenu)\r
14217                                 ed.onMouseDown.add(c.hideMenu, c);\r
14218 \r
14219                         return t.add(c);\r
14220                 },\r
14221 \r
14222                 createButton : function(id, s, cc) {\r
14223                         var t = this, ed = t.editor, o, c, cls;\r
14224 \r
14225                         if (t.get(id))\r
14226                                 return null;\r
14227 \r
14228                         s.title = ed.translate(s.title);\r
14229                         s.label = ed.translate(s.label);\r
14230                         s.scope = s.scope || ed;\r
14231 \r
14232                         if (!s.onclick && !s.menu_button) {\r
14233                                 s.onclick = function() {\r
14234                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
14235                                 };\r
14236                         }\r
14237 \r
14238                         s = extend({\r
14239                                 title : s.title,\r
14240                                 'class' : 'mce_' + id,\r
14241                                 unavailable_prefix : ed.getLang('unavailable', ''),\r
14242                                 scope : s.scope,\r
14243                                 control_manager : t\r
14244                         }, s);\r
14245 \r
14246                         id = t.prefix + id;\r
14247 \r
14248                         if (s.menu_button) {\r
14249                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;\r
14250                                 c = new cls(id, s, ed);\r
14251                                 ed.onMouseDown.add(c.hideMenu, c);\r
14252                         } else {\r
14253                                 cls = t._cls.button || tinymce.ui.Button;\r
14254                                 c = new cls(id, s, ed);\r
14255                         }\r
14256 \r
14257                         return t.add(c);\r
14258                 },\r
14259 \r
14260                 createMenuButton : function(id, s, cc) {\r
14261                         s = s || {};\r
14262                         s.menu_button = 1;\r
14263 \r
14264                         return this.createButton(id, s, cc);\r
14265                 },\r
14266 \r
14267                 createSplitButton : function(id, s, cc) {\r
14268                         var t = this, ed = t.editor, cmd, c, cls;\r
14269 \r
14270                         if (t.get(id))\r
14271                                 return null;\r
14272 \r
14273                         s.title = ed.translate(s.title);\r
14274                         s.scope = s.scope || ed;\r
14275 \r
14276                         if (!s.onclick) {\r
14277                                 s.onclick = function(v) {\r
14278                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14279                                 };\r
14280                         }\r
14281 \r
14282                         if (!s.onselect) {\r
14283                                 s.onselect = function(v) {\r
14284                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14285                                 };\r
14286                         }\r
14287 \r
14288                         s = extend({\r
14289                                 title : s.title,\r
14290                                 'class' : 'mce_' + id,\r
14291                                 scope : s.scope,\r
14292                                 control_manager : t\r
14293                         }, s);\r
14294 \r
14295                         id = t.prefix + id;\r
14296                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;\r
14297                         c = t.add(new cls(id, s, ed));\r
14298                         ed.onMouseDown.add(c.hideMenu, c);\r
14299 \r
14300                         return c;\r
14301                 },\r
14302 \r
14303                 createColorSplitButton : function(id, s, cc) {\r
14304                         var t = this, ed = t.editor, cmd, c, cls, bm;\r
14305 \r
14306                         if (t.get(id))\r
14307                                 return null;\r
14308 \r
14309                         s.title = ed.translate(s.title);\r
14310                         s.scope = s.scope || ed;\r
14311 \r
14312                         if (!s.onclick) {\r
14313                                 s.onclick = function(v) {\r
14314                                         if (tinymce.isIE)\r
14315                                                 bm = ed.selection.getBookmark(1);\r
14316 \r
14317                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14318                                 };\r
14319                         }\r
14320 \r
14321                         if (!s.onselect) {\r
14322                                 s.onselect = function(v) {\r
14323                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
14324                                 };\r
14325                         }\r
14326 \r
14327                         s = extend({\r
14328                                 title : s.title,\r
14329                                 'class' : 'mce_' + id,\r
14330                                 'menu_class' : ed.getParam('skin') + 'Skin',\r
14331                                 scope : s.scope,\r
14332                                 more_colors_title : ed.getLang('more_colors')\r
14333                         }, s);\r
14334 \r
14335                         id = t.prefix + id;\r
14336                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;\r
14337                         c = new cls(id, s, ed);\r
14338                         ed.onMouseDown.add(c.hideMenu, c);\r
14339 \r
14340                         // Remove the menu element when the editor is removed\r
14341                         ed.onRemove.add(function() {\r
14342                                 c.destroy();\r
14343                         });\r
14344 \r
14345                         // Fix for bug #1897785, #1898007\r
14346                         if (tinymce.isIE) {\r
14347                                 c.onShowMenu.add(function() {\r
14348                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
14349                                         ed.focus();\r
14350                                         bm = ed.selection.getBookmark(1);\r
14351                                 });\r
14352 \r
14353                                 c.onHideMenu.add(function() {\r
14354                                         if (bm) {\r
14355                                                 ed.selection.moveToBookmark(bm);\r
14356                                                 bm = 0;\r
14357                                         }\r
14358                                 });\r
14359                         }\r
14360 \r
14361                         return t.add(c);\r
14362                 },\r
14363 \r
14364                 createToolbar : function(id, s, cc) {\r
14365                         var c, t = this, cls;\r
14366 \r
14367                         id = t.prefix + id;\r
14368                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;\r
14369                         c = new cls(id, s, t.editor);\r
14370 \r
14371                         if (t.get(id))\r
14372                                 return null;\r
14373 \r
14374                         return t.add(c);\r
14375                 },\r
14376                 \r
14377                 createToolbarGroup : function(id, s, cc) {\r
14378                         var c, t = this, cls;\r
14379                         id = t.prefix + id;\r
14380                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;\r
14381                         c = new cls(id, s, t.editor);\r
14382                         \r
14383                         if (t.get(id))\r
14384                                 return null;\r
14385                         \r
14386                         return t.add(c);\r
14387                 },\r
14388 \r
14389                 createSeparator : function(cc) {\r
14390                         var cls = cc || this._cls.separator || tinymce.ui.Separator;\r
14391 \r
14392                         return new cls();\r
14393                 },\r
14394 \r
14395                 setControlType : function(n, c) {\r
14396                         return this._cls[n.toLowerCase()] = c;\r
14397                 },\r
14398         \r
14399                 destroy : function() {\r
14400                         each(this.controls, function(c) {\r
14401                                 c.destroy();\r
14402                         });\r
14403 \r
14404                         this.controls = null;\r
14405                 }\r
14406         });\r
14407 })(tinymce);\r
14408 \r
14409 (function(tinymce) {\r
14410         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;\r
14411 \r
14412         tinymce.create('tinymce.WindowManager', {\r
14413                 WindowManager : function(ed) {\r
14414                         var t = this;\r
14415 \r
14416                         t.editor = ed;\r
14417                         t.onOpen = new Dispatcher(t);\r
14418                         t.onClose = new Dispatcher(t);\r
14419                         t.params = {};\r
14420                         t.features = {};\r
14421                 },\r
14422 \r
14423                 open : function(s, p) {\r
14424                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;\r
14425 \r
14426                         // Default some options\r
14427                         s = s || {};\r
14428                         p = p || {};\r
14429                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window\r
14430                         sh = isOpera ? vp.h : screen.height;\r
14431                         s.name = s.name || 'mc_' + new Date().getTime();\r
14432                         s.width = parseInt(s.width || 320);\r
14433                         s.height = parseInt(s.height || 240);\r
14434                         s.resizable = true;\r
14435                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);\r
14436                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);\r
14437                         p.inline = false;\r
14438                         p.mce_width = s.width;\r
14439                         p.mce_height = s.height;\r
14440                         p.mce_auto_focus = s.auto_focus;\r
14441 \r
14442                         if (mo) {\r
14443                                 if (isIE) {\r
14444                                         s.center = true;\r
14445                                         s.help = false;\r
14446                                         s.dialogWidth = s.width + 'px';\r
14447                                         s.dialogHeight = s.height + 'px';\r
14448                                         s.scroll = s.scrollbars || false;\r
14449                                 }\r
14450                         }\r
14451 \r
14452                         // Build features string\r
14453                         each(s, function(v, k) {\r
14454                                 if (tinymce.is(v, 'boolean'))\r
14455                                         v = v ? 'yes' : 'no';\r
14456 \r
14457                                 if (!/^(name|url)$/.test(k)) {\r
14458                                         if (isIE && mo)\r
14459                                                 f += (f ? ';' : '') + k + ':' + v;\r
14460                                         else\r
14461                                                 f += (f ? ',' : '') + k + '=' + v;\r
14462                                 }\r
14463                         });\r
14464 \r
14465                         t.features = s;\r
14466                         t.params = p;\r
14467                         t.onOpen.dispatch(t, s, p);\r
14468 \r
14469                         u = s.url || s.file;\r
14470                         u = tinymce._addVer(u);\r
14471 \r
14472                         try {\r
14473                                 if (isIE && mo) {\r
14474                                         w = 1;\r
14475                                         window.showModalDialog(u, window, f);\r
14476                                 } else\r
14477                                         w = window.open(u, s.name, f);\r
14478                         } catch (ex) {\r
14479                                 // Ignore\r
14480                         }\r
14481 \r
14482                         if (!w)\r
14483                                 alert(t.editor.getLang('popup_blocked'));\r
14484                 },\r
14485 \r
14486                 close : function(w) {\r
14487                         w.close();\r
14488                         this.onClose.dispatch(this);\r
14489                 },\r
14490 \r
14491                 createInstance : function(cl, a, b, c, d, e) {\r
14492                         var f = tinymce.resolve(cl);\r
14493 \r
14494                         return new f(a, b, c, d, e);\r
14495                 },\r
14496 \r
14497                 confirm : function(t, cb, s, w) {\r
14498                         w = w || window;\r
14499 \r
14500                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));\r
14501                 },\r
14502 \r
14503                 alert : function(tx, cb, s, w) {\r
14504                         var t = this;\r
14505 \r
14506                         w = w || window;\r
14507                         w.alert(t._decode(t.editor.getLang(tx, tx)));\r
14508 \r
14509                         if (cb)\r
14510                                 cb.call(s || t);\r
14511                 },\r
14512 \r
14513                 resizeBy : function(dw, dh, win) {\r
14514                         win.resizeBy(dw, dh);\r
14515                 },\r
14516 \r
14517                 // Internal functions\r
14518 \r
14519                 _decode : function(s) {\r
14520                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');\r
14521                 }\r
14522         });\r
14523 }(tinymce));\r
14524 (function(tinymce) {\r
14525         tinymce.Formatter = function(ed) {\r
14526                 var formats = {},\r
14527                         each = tinymce.each,\r
14528                         dom = ed.dom,\r
14529                         selection = ed.selection,\r
14530                         TreeWalker = tinymce.dom.TreeWalker,\r
14531                         rangeUtils = new tinymce.dom.RangeUtils(dom),\r
14532                         isValid = ed.schema.isValidChild,\r
14533                         isBlock = dom.isBlock,\r
14534                         forcedRootBlock = ed.settings.forced_root_block,\r
14535                         nodeIndex = dom.nodeIndex,\r
14536                         INVISIBLE_CHAR = '\uFEFF',\r
14537                         MCE_ATTR_RE = /^(src|href|style)$/,\r
14538                         FALSE = false,\r
14539                         TRUE = true,\r
14540                         undefined,\r
14541                         pendingFormats = {apply : [], remove : []};\r
14542 \r
14543                 function isArray(obj) {\r
14544                         return obj instanceof Array;\r
14545                 };\r
14546 \r
14547                 function getParents(node, selector) {\r
14548                         return dom.getParents(node, selector, dom.getRoot());\r
14549                 };\r
14550 \r
14551                 function isCaretNode(node) {\r
14552                         return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');\r
14553                 };\r
14554 \r
14555                 // Public functions\r
14556 \r
14557                 function get(name) {\r
14558                         return name ? formats[name] : formats;\r
14559                 };\r
14560 \r
14561                 function register(name, format) {\r
14562                         if (name) {\r
14563                                 if (typeof(name) !== 'string') {\r
14564                                         each(name, function(format, name) {\r
14565                                                 register(name, format);\r
14566                                         });\r
14567                                 } else {\r
14568                                         // Force format into array and add it to internal collection\r
14569                                         format = format.length ? format : [format];\r
14570 \r
14571                                         each(format, function(format) {\r
14572                                                 // Set deep to false by default on selector formats this to avoid removing\r
14573                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs\r
14574                                                 if (format.deep === undefined)\r
14575                                                         format.deep = !format.selector;\r
14576 \r
14577                                                 // Default to true\r
14578                                                 if (format.split === undefined)\r
14579                                                         format.split = !format.selector || format.inline;\r
14580 \r
14581                                                 // Default to true\r
14582                                                 if (format.remove === undefined && format.selector && !format.inline)\r
14583                                                         format.remove = 'none';\r
14584 \r
14585                                                 // Mark format as a mixed format inline + block level\r
14586                                                 if (format.selector && format.inline) {\r
14587                                                         format.mixed = true;\r
14588                                                         format.block_expand = true;\r
14589                                                 }\r
14590 \r
14591                                                 // Split classes if needed\r
14592                                                 if (typeof(format.classes) === 'string')\r
14593                                                         format.classes = format.classes.split(/\s+/);\r
14594                                         });\r
14595 \r
14596                                         formats[name] = format;\r
14597                                 }\r
14598                         }\r
14599                 };\r
14600 \r
14601                 var getTextDecoration = function(node) {\r
14602                         var decoration;\r
14603 \r
14604                         ed.dom.getParent(node, function(n) {\r
14605                                 decoration = ed.dom.getStyle(n, 'text-decoration');\r
14606                                 return decoration && decoration !== 'none';\r
14607                         });\r
14608 \r
14609                         return decoration;\r
14610                 };\r
14611 \r
14612                 var processUnderlineAndColor = function(node) {\r
14613                         var textDecoration;\r
14614                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {\r
14615                                 textDecoration = getTextDecoration(node.parentNode);\r
14616                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {\r
14617                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);\r
14618                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {\r
14619                                         ed.dom.setStyle(node, 'text-decoration', null);\r
14620                                 }\r
14621                         }\r
14622                 };\r
14623 \r
14624                 function apply(name, vars, node) {\r
14625                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();\r
14626 \r
14627                         function moveStart(rng) {\r
14628                                 var container = rng.startContainer,\r
14629                                         offset = rng.startOffset,\r
14630                                         walker, node;\r
14631 \r
14632                                 // Move startContainer/startOffset in to a suitable node\r
14633                                 if (container.nodeType == 1 || container.nodeValue === "") {\r
14634                                         container = container.nodeType == 1 ? container.childNodes[offset] : container;\r
14635 \r
14636                                         // Might fail if the offset is behind the last element in it's container\r
14637                                         if (container) {\r
14638                                                 walker = new TreeWalker(container, container.parentNode);\r
14639                                                 for (node = walker.current(); node; node = walker.next()) {\r
14640                                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
14641                                                                 rng.setStart(node, 0);\r
14642                                                                 break;\r
14643                                                         }\r
14644                                                 }\r
14645                                         }\r
14646                                 }\r
14647 \r
14648                                 return rng;\r
14649                         };\r
14650 \r
14651                         function setElementFormat(elm, fmt) {\r
14652                                 fmt = fmt || format;\r
14653 \r
14654                                 if (elm) {\r
14655                                         each(fmt.styles, function(value, name) {\r
14656                                                 dom.setStyle(elm, name, replaceVars(value, vars));\r
14657                                         });\r
14658 \r
14659                                         each(fmt.attributes, function(value, name) {\r
14660                                                 dom.setAttrib(elm, name, replaceVars(value, vars));\r
14661                                         });\r
14662 \r
14663                                         each(fmt.classes, function(value) {\r
14664                                                 value = replaceVars(value, vars);\r
14665 \r
14666                                                 if (!dom.hasClass(elm, value))\r
14667                                                         dom.addClass(elm, value);\r
14668                                         });\r
14669                                 }\r
14670                         };\r
14671 \r
14672                         function applyRngStyle(rng) {\r
14673                                 var newWrappers = [], wrapName, wrapElm;\r
14674 \r
14675                                 // Setup wrapper element\r
14676                                 wrapName = format.inline || format.block;\r
14677                                 wrapElm = dom.create(wrapName);\r
14678                                 setElementFormat(wrapElm);\r
14679 \r
14680                                 rangeUtils.walk(rng, function(nodes) {\r
14681                                         var currentWrapElm;\r
14682 \r
14683                                         function process(node) {\r
14684                                                 var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;\r
14685 \r
14686                                                 // Stop wrapping on br elements\r
14687                                                 if (isEq(nodeName, 'br')) {\r
14688                                                         currentWrapElm = 0;\r
14689 \r
14690                                                         // Remove any br elements when we wrap things\r
14691                                                         if (format.block)\r
14692                                                                 dom.remove(node);\r
14693 \r
14694                                                         return;\r
14695                                                 }\r
14696 \r
14697                                                 // If node is wrapper type\r
14698                                                 if (format.wrapper && matchNode(node, name, vars)) {\r
14699                                                         currentWrapElm = 0;\r
14700                                                         return;\r
14701                                                 }\r
14702 \r
14703                                                 // Can we rename the block\r
14704                                                 if (format.block && !format.wrapper && isTextBlock(nodeName)) {\r
14705                                                         node = dom.rename(node, wrapName);\r
14706                                                         setElementFormat(node);\r
14707                                                         newWrappers.push(node);\r
14708                                                         currentWrapElm = 0;\r
14709                                                         return;\r
14710                                                 }\r
14711 \r
14712                                                 // Handle selector patterns\r
14713                                                 if (format.selector) {\r
14714                                                         // Look for matching formats\r
14715                                                         each(formatList, function(format) {\r
14716                                                                 // Check collapsed state if it exists\r
14717                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {\r
14718                                                                         return;\r
14719                                                                 }\r
14720 \r
14721                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {\r
14722                                                                         setElementFormat(node, format);\r
14723                                                                         found = true;\r
14724                                                                 }\r
14725                                                         });\r
14726 \r
14727                                                         // Continue processing if a selector match wasn't found and a inline element is defined\r
14728                                                         if (!format.inline || found) {\r
14729                                                                 currentWrapElm = 0;\r
14730                                                                 return;\r
14731                                                         }\r
14732                                                 }\r
14733 \r
14734                                                 // Is it valid to wrap this item\r
14735                                                 if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
14736                                                                 !(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {\r
14737                                                         // Start wrapping\r
14738                                                         if (!currentWrapElm) {\r
14739                                                                 // Wrap the node\r
14740                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);\r
14741                                                                 node.parentNode.insertBefore(currentWrapElm, node);\r
14742                                                                 newWrappers.push(currentWrapElm);\r
14743                                                         }\r
14744 \r
14745                                                         currentWrapElm.appendChild(node);\r
14746                                                 } else if (nodeName == 'li') {\r
14747                                                         // Start wrapping\r
14748                                                         if (!currentWrapElm) {\r
14749                                                                 // Wrap the node\r
14750                                                                 liTextNode = node.ownerDocument.createTextNode('');\r
14751                                                                 each(tinymce.grep(node.childNodes), function(n) { if (n.nodeType == 3) { liTextNode.nodeValue += n.nodeValue; n.parentNode.removeChild(n); } });\r
14752                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);\r
14753                                                                 node.insertBefore(currentWrapElm, node.firstChild);\r
14754                                                                 newWrappers.push(currentWrapElm);\r
14755                                                         }\r
14756 \r
14757                                                         currentWrapElm.appendChild(liTextNode);\r
14758                                                         \r
14759                                                 } else {\r
14760                                                         // Start a new wrapper for possible children\r
14761                                                         currentWrapElm = 0;\r
14762 \r
14763                                                         each(tinymce.grep(node.childNodes), process);\r
14764 \r
14765                                                         // End the last wrapper\r
14766                                                         currentWrapElm = 0;\r
14767                                                 }\r
14768                                         };\r
14769 \r
14770                                         // Process siblings from range\r
14771                                         each(nodes, process);\r
14772                                 });\r
14773 \r
14774                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link\r
14775                                 if (format.wrap_links === false) {\r
14776                                         each(newWrappers, function(node) {\r
14777                                                 function process(node) {\r
14778                                                         var i, currentWrapElm, children;\r
14779 \r
14780                                                         if (node.nodeName === 'A') {\r
14781                                                                 currentWrapElm = wrapElm.cloneNode(FALSE);\r
14782                                                                 newWrappers.push(currentWrapElm);\r
14783 \r
14784                                                                 children = tinymce.grep(node.childNodes);\r
14785                                                                 for (i = 0; i < children.length; i++)\r
14786                                                                         currentWrapElm.appendChild(children[i]);\r
14787 \r
14788                                                                 node.appendChild(currentWrapElm);\r
14789                                                         }\r
14790 \r
14791                                                         each(tinymce.grep(node.childNodes), process);\r
14792                                                 };\r
14793 \r
14794                                                 process(node);\r
14795                                         });\r
14796                                 }\r
14797 \r
14798                                 // Cleanup\r
14799                                 each(newWrappers, function(node) {\r
14800                                         var childCount;\r
14801 \r
14802                                         function getChildCount(node) {\r
14803                                                 var count = 0;\r
14804 \r
14805                                                 each(node.childNodes, function(node) {\r
14806                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))\r
14807                                                                 count++;\r
14808                                                 });\r
14809 \r
14810                                                 return count;\r
14811                                         };\r
14812 \r
14813                                         function mergeStyles(node) {\r
14814                                                 var child, clone;\r
14815 \r
14816                                                 each(node.childNodes, function(node) {\r
14817                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {\r
14818                                                                 child = node;\r
14819                                                                 return FALSE; // break loop\r
14820                                                         }\r
14821                                                 });\r
14822 \r
14823                                                 // If child was found and of the same type as the current node\r
14824                                                 if (child && matchName(child, format)) {\r
14825                                                         clone = child.cloneNode(FALSE);\r
14826                                                         setElementFormat(clone);\r
14827 \r
14828                                                         dom.replace(clone, node, TRUE);\r
14829                                                         dom.remove(child, 1);\r
14830                                                 }\r
14831 \r
14832                                                 return clone || node;\r
14833                                         };\r
14834 \r
14835                                         childCount = getChildCount(node);\r
14836 \r
14837                                         // Remove empty nodes but only if there is multiple wrappers and they are not block\r
14838                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at\r
14839                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {\r
14840                                                 dom.remove(node, 1);\r
14841                                                 return;\r
14842                                         }\r
14843 \r
14844                                         if (format.inline || format.wrapper) {\r
14845                                                 // Merges the current node with it's children of similar type to reduce the number of elements\r
14846                                                 if (!format.exact && childCount === 1)\r
14847                                                         node = mergeStyles(node);\r
14848 \r
14849                                                 // Remove/merge children\r
14850                                                 each(formatList, function(format) {\r
14851                                                         // Merge all children of similar type will move styles from child to parent\r
14852                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>\r
14853                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>\r
14854                                                         each(dom.select(format.inline, node), function(child) {\r
14855                                                                 var parent;\r
14856 \r
14857                                                                 // When wrap_links is set to false we don't want\r
14858                                                                 // to remove the format on children within links\r
14859                                                                 if (format.wrap_links === false) {\r
14860                                                                         parent = child.parentNode;\r
14861 \r
14862                                                                         do {\r
14863                                                                                 if (parent.nodeName === 'A')\r
14864                                                                                         return;\r
14865                                                                         } while (parent = parent.parentNode);\r
14866                                                                 }\r
14867 \r
14868                                                                 removeFormat(format, vars, child, format.exact ? child : null);\r
14869                                                         });\r
14870                                                 });\r
14871 \r
14872                                                 // Remove child if direct parent is of same type\r
14873                                                 if (matchNode(node.parentNode, name, vars)) {\r
14874                                                         dom.remove(node, 1);\r
14875                                                         node = 0;\r
14876                                                         return TRUE;\r
14877                                                 }\r
14878 \r
14879                                                 // Look for parent with similar style format\r
14880                                                 if (format.merge_with_parents) {\r
14881                                                         dom.getParent(node.parentNode, function(parent) {\r
14882                                                                 if (matchNode(parent, name, vars)) {\r
14883                                                                         dom.remove(node, 1);\r
14884                                                                         node = 0;\r
14885                                                                         return TRUE;\r
14886                                                                 }\r
14887                                                         });\r
14888                                                 }\r
14889 \r
14890                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>\r
14891                                                 if (node) {\r
14892                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);\r
14893                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));\r
14894                                                 }\r
14895                                         }\r
14896                                 });\r
14897                         };\r
14898 \r
14899                         if (format) {\r
14900                                 if (node) {\r
14901                                         rng = dom.createRng();\r
14902 \r
14903                                         rng.setStartBefore(node);\r
14904                                         rng.setEndAfter(node);\r
14905 \r
14906                                         applyRngStyle(expandRng(rng, formatList));\r
14907                                 } else {\r
14908                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
14909                                                 // Obtain selection node before selection is unselected by applyRngStyle()\r
14910                                                 var curSelNode = ed.selection.getNode();\r
14911 \r
14912                                                 // Apply formatting to selection\r
14913                                                 bookmark = selection.getBookmark();\r
14914                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList));\r
14915 \r
14916                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.\r
14917                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {\r
14918                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');\r
14919                                                         processUnderlineAndColor(curSelNode);\r
14920                                                 }\r
14921 \r
14922                                                 selection.moveToBookmark(bookmark);\r
14923                                                 selection.setRng(moveStart(selection.getRng(TRUE)));\r
14924                                                 ed.nodeChanged();\r
14925                                         } else\r
14926                                                 performCaretAction('apply', name, vars);\r
14927                                 }\r
14928                         }\r
14929                 };\r
14930 \r
14931                 function remove(name, vars, node) {\r
14932                         var formatList = get(name), format = formatList[0], bookmark, i, rng;\r
14933 \r
14934                         function moveStart(rng) {\r
14935                                 var container = rng.startContainer,\r
14936                                         offset = rng.startOffset,\r
14937                                         walker, node, nodes, tmpNode;\r
14938 \r
14939                                 // Convert text node into index if possible\r
14940                                 if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {\r
14941                                         container = container.parentNode;\r
14942                                         offset = nodeIndex(container) + 1;\r
14943                                 }\r
14944 \r
14945                                 // Move startContainer/startOffset in to a suitable node\r
14946                                 if (container.nodeType == 1) {\r
14947                                         nodes = container.childNodes;\r
14948                                         container = nodes[Math.min(offset, nodes.length - 1)];\r
14949                                         walker = new TreeWalker(container);\r
14950 \r
14951                                         // If offset is at end of the parent node walk to the next one\r
14952                                         if (offset > nodes.length - 1)\r
14953                                                 walker.next();\r
14954 \r
14955                                         for (node = walker.current(); node; node = walker.next()) {\r
14956                                                 if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
14957                                                         // IE has a "neat" feature where it moves the start node into the closest element\r
14958                                                         // we can avoid this by inserting an element before it and then remove it after we set the selection\r
14959                                                         tmpNode = dom.create('a', null, INVISIBLE_CHAR);\r
14960                                                         node.parentNode.insertBefore(tmpNode, node);\r
14961 \r
14962                                                         // Set selection and remove tmpNode\r
14963                                                         rng.setStart(node, 0);\r
14964                                                         selection.setRng(rng);\r
14965                                                         dom.remove(tmpNode);\r
14966 \r
14967                                                         return;\r
14968                                                 }\r
14969                                         }\r
14970                                 }\r
14971                         };\r
14972 \r
14973                         // Merges the styles for each node\r
14974                         function process(node) {\r
14975                                 var children, i, l;\r
14976 \r
14977                                 // Grab the children first since the nodelist might be changed\r
14978                                 children = tinymce.grep(node.childNodes);\r
14979 \r
14980                                 // Process current node\r
14981                                 for (i = 0, l = formatList.length; i < l; i++) {\r
14982                                         if (removeFormat(formatList[i], vars, node, node))\r
14983                                                 break;\r
14984                                 }\r
14985 \r
14986                                 // Process the children\r
14987                                 if (format.deep) {\r
14988                                         for (i = 0, l = children.length; i < l; i++)\r
14989                                                 process(children[i]);\r
14990                                 }\r
14991                         };\r
14992 \r
14993                         function findFormatRoot(container) {\r
14994                                 var formatRoot;\r
14995 \r
14996                                 // Find format root\r
14997                                 each(getParents(container.parentNode).reverse(), function(parent) {\r
14998                                         var format;\r
14999 \r
15000                                         // Find format root element\r
15001                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {\r
15002                                                 // Is the node matching the format we are looking for\r
15003                                                 format = matchNode(parent, name, vars);\r
15004                                                 if (format && format.split !== false)\r
15005                                                         formatRoot = parent;\r
15006                                         }\r
15007                                 });\r
15008 \r
15009                                 return formatRoot;\r
15010                         };\r
15011 \r
15012                         function wrapAndSplit(format_root, container, target, split) {\r
15013                                 var parent, clone, lastClone, firstClone, i, formatRootParent;\r
15014 \r
15015                                 // Format root found then clone formats and split it\r
15016                                 if (format_root) {\r
15017                                         formatRootParent = format_root.parentNode;\r
15018 \r
15019                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {\r
15020                                                 clone = parent.cloneNode(FALSE);\r
15021 \r
15022                                                 for (i = 0; i < formatList.length; i++) {\r
15023                                                         if (removeFormat(formatList[i], vars, clone, clone)) {\r
15024                                                                 clone = 0;\r
15025                                                                 break;\r
15026                                                         }\r
15027                                                 }\r
15028 \r
15029                                                 // Build wrapper node\r
15030                                                 if (clone) {\r
15031                                                         if (lastClone)\r
15032                                                                 clone.appendChild(lastClone);\r
15033 \r
15034                                                         if (!firstClone)\r
15035                                                                 firstClone = clone;\r
15036 \r
15037                                                         lastClone = clone;\r
15038                                                 }\r
15039                                         }\r
15040 \r
15041                                         // Never split block elements if the format is mixed\r
15042                                         if (split && (!format.mixed || !isBlock(format_root)))\r
15043                                                 container = dom.split(format_root, container);\r
15044 \r
15045                                         // Wrap container in cloned formats\r
15046                                         if (lastClone) {\r
15047                                                 target.parentNode.insertBefore(lastClone, target);\r
15048                                                 firstClone.appendChild(target);\r
15049                                         }\r
15050                                 }\r
15051 \r
15052                                 return container;\r
15053                         };\r
15054 \r
15055                         function splitToFormatRoot(container) {\r
15056                                 return wrapAndSplit(findFormatRoot(container), container, container, true);\r
15057                         };\r
15058 \r
15059                         function unwrap(start) {\r
15060                                 var node = dom.get(start ? '_start' : '_end'),\r
15061                                         out = node[start ? 'firstChild' : 'lastChild'];\r
15062 \r
15063                                 // If the end is placed within the start the result will be removed\r
15064                                 // So this checks if the out node is a bookmark node if it is it\r
15065                                 // checks for another more suitable node\r
15066                                 if (isBookmarkNode(out))\r
15067                                         out = out[start ? 'firstChild' : 'lastChild'];\r
15068 \r
15069                                 dom.remove(node, true);\r
15070 \r
15071                                 return out;\r
15072                         };\r
15073 \r
15074                         function removeRngStyle(rng) {\r
15075                                 var startContainer, endContainer;\r
15076 \r
15077                                 rng = expandRng(rng, formatList, TRUE);\r
15078 \r
15079                                 if (format.split) {\r
15080                                         startContainer = getContainer(rng, TRUE);\r
15081                                         endContainer = getContainer(rng);\r
15082 \r
15083                                         if (startContainer != endContainer) {\r
15084                                                 // Wrap start/end nodes in span element since these might be cloned/moved\r
15085                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});\r
15086                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});\r
15087 \r
15088                                                 // Split start/end\r
15089                                                 splitToFormatRoot(startContainer);\r
15090                                                 splitToFormatRoot(endContainer);\r
15091 \r
15092                                                 // Unwrap start/end to get real elements again\r
15093                                                 startContainer = unwrap(TRUE);\r
15094                                                 endContainer = unwrap();\r
15095                                         } else\r
15096                                                 startContainer = endContainer = splitToFormatRoot(startContainer);\r
15097 \r
15098                                         // Update range positions since they might have changed after the split operations\r
15099                                         rng.startContainer = startContainer.parentNode;\r
15100                                         rng.startOffset = nodeIndex(startContainer);\r
15101                                         rng.endContainer = endContainer.parentNode;\r
15102                                         rng.endOffset = nodeIndex(endContainer) + 1;\r
15103                                 }\r
15104 \r
15105                                 // Remove items between start/end\r
15106                                 rangeUtils.walk(rng, function(nodes) {\r
15107                                         each(nodes, function(node) {\r
15108                                                 process(node);\r
15109 \r
15110                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.\r
15111                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {\r
15112                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);\r
15113                                                 }\r
15114                                         });\r
15115                                 });\r
15116                         };\r
15117 \r
15118                         // Handle node\r
15119                         if (node) {\r
15120                                 rng = dom.createRng();\r
15121                                 rng.setStartBefore(node);\r
15122                                 rng.setEndAfter(node);\r
15123                                 removeRngStyle(rng);\r
15124                                 return;\r
15125                         }\r
15126 \r
15127                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
15128                                 bookmark = selection.getBookmark();\r
15129                                 removeRngStyle(selection.getRng(TRUE));\r
15130                                 selection.moveToBookmark(bookmark);\r
15131 \r
15132                                 // 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
15133                                 if (match(name, vars, selection.getStart())) {\r
15134                                         moveStart(selection.getRng(true));\r
15135                                 }\r
15136 \r
15137                                 ed.nodeChanged();\r
15138                         } else\r
15139                                 performCaretAction('remove', name, vars);\r
15140                 };\r
15141 \r
15142                 function toggle(name, vars, node) {\r
15143                         var fmt = get(name);\r
15144 \r
15145                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))\r
15146                                 remove(name, vars, node);\r
15147                         else\r
15148                                 apply(name, vars, node);\r
15149                 };\r
15150 \r
15151                 function matchNode(node, name, vars, similar) {\r
15152                         var formatList = get(name), format, i, classes;\r
15153 \r
15154                         function matchItems(node, format, item_name) {\r
15155                                 var key, value, items = format[item_name], i;\r
15156 \r
15157                                 // Check all items\r
15158                                 if (items) {\r
15159                                         // Non indexed object\r
15160                                         if (items.length === undefined) {\r
15161                                                 for (key in items) {\r
15162                                                         if (items.hasOwnProperty(key)) {\r
15163                                                                 if (item_name === 'attributes')\r
15164                                                                         value = dom.getAttrib(node, key);\r
15165                                                                 else\r
15166                                                                         value = getStyle(node, key);\r
15167 \r
15168                                                                 if (similar && !value && !format.exact)\r
15169                                                                         return;\r
15170 \r
15171                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))\r
15172                                                                         return;\r
15173                                                         }\r
15174                                                 }\r
15175                                         } else {\r
15176                                                 // Only one match needed for indexed arrays\r
15177                                                 for (i = 0; i < items.length; i++) {\r
15178                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))\r
15179                                                                 return format;\r
15180                                                 }\r
15181                                         }\r
15182                                 }\r
15183 \r
15184                                 return format;\r
15185                         };\r
15186 \r
15187                         if (formatList && node) {\r
15188                                 // Check each format in list\r
15189                                 for (i = 0; i < formatList.length; i++) {\r
15190                                         format = formatList[i];\r
15191 \r
15192                                         // Name name, attributes, styles and classes\r
15193                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {\r
15194                                                 // Match classes\r
15195                                                 if (classes = format.classes) {\r
15196                                                         for (i = 0; i < classes.length; i++) {\r
15197                                                                 if (!dom.hasClass(node, classes[i]))\r
15198                                                                         return;\r
15199                                                         }\r
15200                                                 }\r
15201 \r
15202                                                 return format;\r
15203                                         }\r
15204                                 }\r
15205                         }\r
15206                 };\r
15207 \r
15208                 function match(name, vars, node) {\r
15209                         var startNode, i;\r
15210 \r
15211                         function matchParents(node) {\r
15212                                 // Find first node with similar format settings\r
15213                                 node = dom.getParent(node, function(node) {\r
15214                                         return !!matchNode(node, name, vars, true);\r
15215                                 });\r
15216 \r
15217                                 // Do an exact check on the similar format element\r
15218                                 return matchNode(node, name, vars);\r
15219                         };\r
15220 \r
15221                         // Check specified node\r
15222                         if (node)\r
15223                                 return matchParents(node);\r
15224 \r
15225                         // Check pending formats\r
15226                         if (selection.isCollapsed()) {\r
15227                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {\r
15228                                         if (pendingFormats.apply[i].name == name)\r
15229                                                 return true;\r
15230                                 }\r
15231 \r
15232                                 for (i = pendingFormats.remove.length - 1; i >= 0; i--) {\r
15233                                         if (pendingFormats.remove[i].name == name)\r
15234                                                 return false;\r
15235                                 }\r
15236 \r
15237                                 return matchParents(selection.getNode());\r
15238                         }\r
15239 \r
15240                         // Check selected node\r
15241                         node = selection.getNode();\r
15242                         if (matchParents(node))\r
15243                                 return TRUE;\r
15244 \r
15245                         // Check start node if it's different\r
15246                         startNode = selection.getStart();\r
15247                         if (startNode != node) {\r
15248                                 if (matchParents(startNode))\r
15249                                         return TRUE;\r
15250                         }\r
15251 \r
15252                         return FALSE;\r
15253                 };\r
15254 \r
15255                 function matchAll(names, vars) {\r
15256                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;\r
15257 \r
15258                         // If the selection is collapsed then check pending formats\r
15259                         if (selection.isCollapsed()) {\r
15260                                 for (ni = 0; ni < names.length; ni++) {\r
15261                                         // If the name is to be removed, then stop it from being added\r
15262                                         for (i = pendingFormats.remove.length - 1; i >= 0; i--) {\r
15263                                                 name = names[ni];\r
15264 \r
15265                                                 if (pendingFormats.remove[i].name == name) {\r
15266                                                         checkedMap[name] = true;\r
15267                                                         break;\r
15268                                                 }\r
15269                                         }\r
15270                                 }\r
15271 \r
15272                                 // If the format is to be applied\r
15273                                 for (i = pendingFormats.apply.length - 1; i >= 0; i--) {\r
15274                                         for (ni = 0; ni < names.length; ni++) {\r
15275                                                 name = names[ni];\r
15276 \r
15277                                                 if (!checkedMap[name] && pendingFormats.apply[i].name == name) {\r
15278                                                         checkedMap[name] = true;\r
15279                                                         matchedFormatNames.push(name);\r
15280                                                 }\r
15281                                         }\r
15282                                 }\r
15283                         }\r
15284 \r
15285                         // Check start of selection for formats\r
15286                         startElement = selection.getStart();\r
15287                         dom.getParent(startElement, function(node) {\r
15288                                 var i, name;\r
15289 \r
15290                                 for (i = 0; i < names.length; i++) {\r
15291                                         name = names[i];\r
15292 \r
15293                                         if (!checkedMap[name] && matchNode(node, name, vars)) {\r
15294                                                 checkedMap[name] = true;\r
15295                                                 matchedFormatNames.push(name);\r
15296                                         }\r
15297                                 }\r
15298                         });\r
15299 \r
15300                         return matchedFormatNames;\r
15301                 };\r
15302 \r
15303                 function canApply(name) {\r
15304                         var formatList = get(name), startNode, parents, i, x, selector;\r
15305 \r
15306                         if (formatList) {\r
15307                                 startNode = selection.getStart();\r
15308                                 parents = getParents(startNode);\r
15309 \r
15310                                 for (x = formatList.length - 1; x >= 0; x--) {\r
15311                                         selector = formatList[x].selector;\r
15312 \r
15313                                         // Format is not selector based, then always return TRUE\r
15314                                         if (!selector)\r
15315                                                 return TRUE;\r
15316 \r
15317                                         for (i = parents.length - 1; i >= 0; i--) {\r
15318                                                 if (dom.is(parents[i], selector))\r
15319                                                         return TRUE;\r
15320                                         }\r
15321                                 }\r
15322                         }\r
15323 \r
15324                         return FALSE;\r
15325                 };\r
15326 \r
15327                 // Expose to public\r
15328                 tinymce.extend(this, {\r
15329                         get : get,\r
15330                         register : register,\r
15331                         apply : apply,\r
15332                         remove : remove,\r
15333                         toggle : toggle,\r
15334                         match : match,\r
15335                         matchAll : matchAll,\r
15336                         matchNode : matchNode,\r
15337                         canApply : canApply\r
15338                 });\r
15339 \r
15340                 // Private functions\r
15341 \r
15342                 function matchName(node, format) {\r
15343                         // Check for inline match\r
15344                         if (isEq(node, format.inline))\r
15345                                 return TRUE;\r
15346 \r
15347                         // Check for block match\r
15348                         if (isEq(node, format.block))\r
15349                                 return TRUE;\r
15350 \r
15351                         // Check for selector match\r
15352                         if (format.selector)\r
15353                                 return dom.is(node, format.selector);\r
15354                 };\r
15355 \r
15356                 function isEq(str1, str2) {\r
15357                         str1 = str1 || '';\r
15358                         str2 = str2 || '';\r
15359 \r
15360                         str1 = '' + (str1.nodeName || str1);\r
15361                         str2 = '' + (str2.nodeName || str2);\r
15362 \r
15363                         return str1.toLowerCase() == str2.toLowerCase();\r
15364                 };\r
15365 \r
15366                 function getStyle(node, name) {\r
15367                         var styleVal = dom.getStyle(node, name);\r
15368 \r
15369                         // Force the format to hex\r
15370                         if (name == 'color' || name == 'backgroundColor')\r
15371                                 styleVal = dom.toHex(styleVal);\r
15372 \r
15373                         // Opera will return bold as 700\r
15374                         if (name == 'fontWeight' && styleVal == 700)\r
15375                                 styleVal = 'bold';\r
15376 \r
15377                         return '' + styleVal;\r
15378                 };\r
15379 \r
15380                 function replaceVars(value, vars) {\r
15381                         if (typeof(value) != "string")\r
15382                                 value = value(vars);\r
15383                         else if (vars) {\r
15384                                 value = value.replace(/%(\w+)/g, function(str, name) {\r
15385                                         return vars[name] || str;\r
15386                                 });\r
15387                         }\r
15388 \r
15389                         return value;\r
15390                 };\r
15391 \r
15392                 function isWhiteSpaceNode(node) {\r
15393                         return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);\r
15394                 };\r
15395 \r
15396                 function wrap(node, name, attrs) {\r
15397                         var wrapper = dom.create(name, attrs);\r
15398 \r
15399                         node.parentNode.insertBefore(wrapper, node);\r
15400                         wrapper.appendChild(node);\r
15401 \r
15402                         return wrapper;\r
15403                 };\r
15404 \r
15405                 function expandRng(rng, format, remove) {\r
15406                         var startContainer = rng.startContainer,\r
15407                                 startOffset = rng.startOffset,\r
15408                                 endContainer = rng.endContainer,\r
15409                                 endOffset = rng.endOffset, sibling, lastIdx, leaf;\r
15410 \r
15411                         // This function walks up the tree if there is no siblings before/after the node\r
15412                         function findParentContainer(container, child_name, sibling_name, root) {\r
15413                                 var parent, child;\r
15414 \r
15415                                 root = root || dom.getRoot();\r
15416 \r
15417                                 for (;;) {\r
15418                                         // Check if we can move up are we at root level or body level\r
15419                                         parent = container.parentNode;\r
15420 \r
15421                                         // Stop expanding on block elements or root depending on format\r
15422                                         if (parent == root || (!format[0].block_expand && isBlock(parent)))\r
15423                                                 return container;\r
15424 \r
15425                                         for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {\r
15426                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))\r
15427                                                         return container;\r
15428 \r
15429                                                 if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))\r
15430                                                         return container;\r
15431                                         }\r
15432 \r
15433                                         container = container.parentNode;\r
15434                                 }\r
15435 \r
15436                                 return container;\r
15437                         };\r
15438 \r
15439                         // This function walks down the tree to find the leaf at the selection.\r
15440                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.\r
15441                         function findLeaf(node, offset) {\r
15442                                 if (offset === undefined)\r
15443                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
15444                                 while (node && node.hasChildNodes()) {\r
15445                                         node = node.childNodes[offset];\r
15446                                         if (node)\r
15447                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
15448                                 }\r
15449                                 return { node: node, offset: offset };\r
15450                         }\r
15451 \r
15452                         // If index based start position then resolve it\r
15453                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {\r
15454                                 lastIdx = startContainer.childNodes.length - 1;\r
15455                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];\r
15456 \r
15457                                 if (startContainer.nodeType == 3)\r
15458                                         startOffset = 0;\r
15459                         }\r
15460 \r
15461                         // If index based end position then resolve it\r
15462                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {\r
15463                                 lastIdx = endContainer.childNodes.length - 1;\r
15464                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];\r
15465 \r
15466                                 if (endContainer.nodeType == 3)\r
15467                                         endOffset = endContainer.nodeValue.length;\r
15468                         }\r
15469 \r
15470                         // Exclude bookmark nodes if possible\r
15471                         if (isBookmarkNode(startContainer.parentNode))\r
15472                                 startContainer = startContainer.parentNode;\r
15473 \r
15474                         if (isBookmarkNode(startContainer))\r
15475                                 startContainer = startContainer.nextSibling || startContainer;\r
15476 \r
15477                         if (isBookmarkNode(endContainer.parentNode)) {\r
15478                                 endOffset = dom.nodeIndex(endContainer);\r
15479                                 endContainer = endContainer.parentNode;\r
15480                         }\r
15481 \r
15482                         if (isBookmarkNode(endContainer) && endContainer.previousSibling) {\r
15483                                 endContainer = endContainer.previousSibling;\r
15484                                 endOffset = endContainer.length;\r
15485                         }\r
15486 \r
15487                         if (format[0].inline) {\r
15488                                 // Avoid applying formatting to a trailing space.\r
15489                                 leaf = findLeaf(endContainer, endOffset);\r
15490                                 if (leaf.node) {\r
15491                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)\r
15492                                                 leaf = findLeaf(leaf.node.previousSibling);\r
15493 \r
15494                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&\r
15495                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {\r
15496 \r
15497                                                 if (leaf.offset > 1) {\r
15498                                                         endContainer = leaf.node;\r
15499                                                         endContainer.splitText(leaf.offset - 1);\r
15500                                                 } else if (leaf.node.previousSibling) {\r
15501                                                         endContainer = leaf.node.previousSibling;\r
15502                                                 }\r
15503                                         }\r
15504                                 }\r
15505                         }\r
15506                         \r
15507                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers\r
15508                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!\r
15509                         // This will reduce the number of wrapper elements that needs to be created\r
15510                         // Move start point up the tree\r
15511                         if (format[0].inline || format[0].block_expand) {\r
15512                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');\r
15513                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');\r
15514                         }\r
15515 \r
15516                         // Expand start/end container to matching selector\r
15517                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {\r
15518                                 function findSelectorEndPoint(container, sibling_name) {\r
15519                                         var parents, i, y, curFormat;\r
15520 \r
15521                                         if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])\r
15522                                                 container = container[sibling_name];\r
15523 \r
15524                                         parents = getParents(container);\r
15525                                         for (i = 0; i < parents.length; i++) {\r
15526                                                 for (y = 0; y < format.length; y++) {\r
15527                                                         curFormat = format[y];\r
15528 \r
15529                                                         // If collapsed state is set then skip formats that doesn't match that\r
15530                                                         if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)\r
15531                                                                 continue;\r
15532 \r
15533                                                         if (dom.is(parents[i], curFormat.selector))\r
15534                                                                 return parents[i];\r
15535                                                 }\r
15536                                         }\r
15537 \r
15538                                         return container;\r
15539                                 };\r
15540 \r
15541                                 // Find new startContainer/endContainer if there is better one\r
15542                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');\r
15543                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');\r
15544                         }\r
15545 \r
15546                         // Expand start/end container to matching block element or text node\r
15547                         if (format[0].block || format[0].selector) {\r
15548                                 function findBlockEndPoint(container, sibling_name, sibling_name2) {\r
15549                                         var node;\r
15550 \r
15551                                         // Expand to block of similar type\r
15552                                         if (!format[0].wrapper)\r
15553                                                 node = dom.getParent(container, format[0].block);\r
15554 \r
15555                                         // Expand to first wrappable block element or any block element\r
15556                                         if (!node)\r
15557                                                 node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);\r
15558 \r
15559                                         // Exclude inner lists from wrapping\r
15560                                         if (node && format[0].wrapper)\r
15561                                                 node = getParents(node, 'ul,ol').reverse()[0] || node;\r
15562 \r
15563                                         // Didn't find a block element look for first/last wrappable element\r
15564                                         if (!node) {\r
15565                                                 node = container;\r
15566 \r
15567                                                 while (node[sibling_name] && !isBlock(node[sibling_name])) {\r
15568                                                         node = node[sibling_name];\r
15569 \r
15570                                                         // Break on BR but include it will be removed later on\r
15571                                                         // we can't remove it now since we need to check if it can be wrapped\r
15572                                                         if (isEq(node, 'br'))\r
15573                                                                 break;\r
15574                                                 }\r
15575                                         }\r
15576 \r
15577                                         return node || container;\r
15578                                 };\r
15579 \r
15580                                 // Find new startContainer/endContainer if there is better one\r
15581                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');\r
15582                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');\r
15583 \r
15584                                 // Non block element then try to expand up the leaf\r
15585                                 if (format[0].block) {\r
15586                                         if (!isBlock(startContainer))\r
15587                                                 startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');\r
15588 \r
15589                                         if (!isBlock(endContainer))\r
15590                                                 endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');\r
15591                                 }\r
15592                         }\r
15593 \r
15594                         // Setup index for startContainer\r
15595                         if (startContainer.nodeType == 1) {\r
15596                                 startOffset = nodeIndex(startContainer);\r
15597                                 startContainer = startContainer.parentNode;\r
15598                         }\r
15599 \r
15600                         // Setup index for endContainer\r
15601                         if (endContainer.nodeType == 1) {\r
15602                                 endOffset = nodeIndex(endContainer) + 1;\r
15603                                 endContainer = endContainer.parentNode;\r
15604                         }\r
15605 \r
15606                         // Return new range like object\r
15607                         return {\r
15608                                 startContainer : startContainer,\r
15609                                 startOffset : startOffset,\r
15610                                 endContainer : endContainer,\r
15611                                 endOffset : endOffset\r
15612                         };\r
15613                 }\r
15614 \r
15615                 function removeFormat(format, vars, node, compare_node) {\r
15616                         var i, attrs, stylesModified;\r
15617 \r
15618                         // Check if node matches format\r
15619                         if (!matchName(node, format))\r
15620                                 return FALSE;\r
15621 \r
15622                         // Should we compare with format attribs and styles\r
15623                         if (format.remove != 'all') {\r
15624                                 // Remove styles\r
15625                                 each(format.styles, function(value, name) {\r
15626                                         value = replaceVars(value, vars);\r
15627 \r
15628                                         // Indexed array\r
15629                                         if (typeof(name) === 'number') {\r
15630                                                 name = value;\r
15631                                                 compare_node = 0;\r
15632                                         }\r
15633 \r
15634                                         if (!compare_node || isEq(getStyle(compare_node, name), value))\r
15635                                                 dom.setStyle(node, name, '');\r
15636 \r
15637                                         stylesModified = 1;\r
15638                                 });\r
15639 \r
15640                                 // Remove style attribute if it's empty\r
15641                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {\r
15642                                         node.removeAttribute('style');\r
15643                                         node.removeAttribute('data-mce-style');\r
15644                                 }\r
15645 \r
15646                                 // Remove attributes\r
15647                                 each(format.attributes, function(value, name) {\r
15648                                         var valueOut;\r
15649 \r
15650                                         value = replaceVars(value, vars);\r
15651 \r
15652                                         // Indexed array\r
15653                                         if (typeof(name) === 'number') {\r
15654                                                 name = value;\r
15655                                                 compare_node = 0;\r
15656                                         }\r
15657 \r
15658                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {\r
15659                                                 // Keep internal classes\r
15660                                                 if (name == 'class') {\r
15661                                                         value = dom.getAttrib(node, name);\r
15662                                                         if (value) {\r
15663                                                                 // Build new class value where everything is removed except the internal prefixed classes\r
15664                                                                 valueOut = '';\r
15665                                                                 each(value.split(/\s+/), function(cls) {\r
15666                                                                         if (/mce\w+/.test(cls))\r
15667                                                                                 valueOut += (valueOut ? ' ' : '') + cls;\r
15668                                                                 });\r
15669 \r
15670                                                                 // We got some internal classes left\r
15671                                                                 if (valueOut) {\r
15672                                                                         dom.setAttrib(node, name, valueOut);\r
15673                                                                         return;\r
15674                                                                 }\r
15675                                                         }\r
15676                                                 }\r
15677 \r
15678                                                 // IE6 has a bug where the attribute doesn't get removed correctly\r
15679                                                 if (name == "class")\r
15680                                                         node.removeAttribute('className');\r
15681 \r
15682                                                 // Remove mce prefixed attributes\r
15683                                                 if (MCE_ATTR_RE.test(name))\r
15684                                                         node.removeAttribute('data-mce-' + name);\r
15685 \r
15686                                                 node.removeAttribute(name);\r
15687                                         }\r
15688                                 });\r
15689 \r
15690                                 // Remove classes\r
15691                                 each(format.classes, function(value) {\r
15692                                         value = replaceVars(value, vars);\r
15693 \r
15694                                         if (!compare_node || dom.hasClass(compare_node, value))\r
15695                                                 dom.removeClass(node, value);\r
15696                                 });\r
15697 \r
15698                                 // Check for non internal attributes\r
15699                                 attrs = dom.getAttribs(node);\r
15700                                 for (i = 0; i < attrs.length; i++) {\r
15701                                         if (attrs[i].nodeName.indexOf('_') !== 0)\r
15702                                                 return FALSE;\r
15703                                 }\r
15704                         }\r
15705 \r
15706                         // Remove the inline child if it's empty for example <b> or <span>\r
15707                         if (format.remove != 'none') {\r
15708                                 removeNode(node, format);\r
15709                                 return TRUE;\r
15710                         }\r
15711                 };\r
15712 \r
15713                 function removeNode(node, format) {\r
15714                         var parentNode = node.parentNode, rootBlockElm;\r
15715 \r
15716                         if (format.block) {\r
15717                                 if (!forcedRootBlock) {\r
15718                                         function find(node, next, inc) {\r
15719                                                 node = getNonWhiteSpaceSibling(node, next, inc);\r
15720 \r
15721                                                 return !node || (node.nodeName == 'BR' || isBlock(node));\r
15722                                         };\r
15723 \r
15724                                         // Append BR elements if needed before we remove the block\r
15725                                         if (isBlock(node) && !isBlock(parentNode)) {\r
15726                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))\r
15727                                                         node.insertBefore(dom.create('br'), node.firstChild);\r
15728 \r
15729                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))\r
15730                                                         node.appendChild(dom.create('br'));\r
15731                                         }\r
15732                                 } else {\r
15733                                         // Wrap the block in a forcedRootBlock if we are at the root of document\r
15734                                         if (parentNode == dom.getRoot()) {\r
15735                                                 if (!format.list_block || !isEq(node, format.list_block)) {\r
15736                                                         each(tinymce.grep(node.childNodes), function(node) {\r
15737                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {\r
15738                                                                         if (!rootBlockElm)\r
15739                                                                                 rootBlockElm = wrap(node, forcedRootBlock);\r
15740                                                                         else\r
15741                                                                                 rootBlockElm.appendChild(node);\r
15742                                                                 } else\r
15743                                                                         rootBlockElm = 0;\r
15744                                                         });\r
15745                                                 }\r
15746                                         }\r
15747                                 }\r
15748                         }\r
15749 \r
15750                         // Never remove nodes that isn't the specified inline element if a selector is specified too\r
15751                         if (format.selector && format.inline && !isEq(format.inline, node))\r
15752                                 return;\r
15753 \r
15754                         dom.remove(node, 1);\r
15755                 };\r
15756 \r
15757                 function getNonWhiteSpaceSibling(node, next, inc) {\r
15758                         if (node) {\r
15759                                 next = next ? 'nextSibling' : 'previousSibling';\r
15760 \r
15761                                 for (node = inc ? node : node[next]; node; node = node[next]) {\r
15762                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))\r
15763                                                 return node;\r
15764                                 }\r
15765                         }\r
15766                 };\r
15767 \r
15768                 function isBookmarkNode(node) {\r
15769                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';\r
15770                 };\r
15771 \r
15772                 function mergeSiblings(prev, next) {\r
15773                         var marker, sibling, tmpSibling;\r
15774 \r
15775                         function compareElements(node1, node2) {\r
15776                                 // Not the same name\r
15777                                 if (node1.nodeName != node2.nodeName)\r
15778                                         return FALSE;\r
15779 \r
15780                                 function getAttribs(node) {\r
15781                                         var attribs = {};\r
15782 \r
15783                                         each(dom.getAttribs(node), function(attr) {\r
15784                                                 var name = attr.nodeName.toLowerCase();\r
15785 \r
15786                                                 // Don't compare internal attributes or style\r
15787                                                 if (name.indexOf('_') !== 0 && name !== 'style')\r
15788                                                         attribs[name] = dom.getAttrib(node, name);\r
15789                                         });\r
15790 \r
15791                                         return attribs;\r
15792                                 };\r
15793 \r
15794                                 function compareObjects(obj1, obj2) {\r
15795                                         var value, name;\r
15796 \r
15797                                         for (name in obj1) {\r
15798                                                 // Obj1 has item obj2 doesn't have\r
15799                                                 if (obj1.hasOwnProperty(name)) {\r
15800                                                         value = obj2[name];\r
15801 \r
15802                                                         // Obj2 doesn't have obj1 item\r
15803                                                         if (value === undefined)\r
15804                                                                 return FALSE;\r
15805 \r
15806                                                         // Obj2 item has a different value\r
15807                                                         if (obj1[name] != value)\r
15808                                                                 return FALSE;\r
15809 \r
15810                                                         // Delete similar value\r
15811                                                         delete obj2[name];\r
15812                                                 }\r
15813                                         }\r
15814 \r
15815                                         // Check if obj 2 has something obj 1 doesn't have\r
15816                                         for (name in obj2) {\r
15817                                                 // Obj2 has item obj1 doesn't have\r
15818                                                 if (obj2.hasOwnProperty(name))\r
15819                                                         return FALSE;\r
15820                                         }\r
15821 \r
15822                                         return TRUE;\r
15823                                 };\r
15824 \r
15825                                 // Attribs are not the same\r
15826                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))\r
15827                                         return FALSE;\r
15828 \r
15829                                 // Styles are not the same\r
15830                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))\r
15831                                         return FALSE;\r
15832 \r
15833                                 return TRUE;\r
15834                         };\r
15835 \r
15836                         // Check if next/prev exists and that they are elements\r
15837                         if (prev && next) {\r
15838                                 function findElementSibling(node, sibling_name) {\r
15839                                         for (sibling = node; sibling; sibling = sibling[sibling_name]) {\r
15840                                                 if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)\r
15841                                                         return node;\r
15842 \r
15843                                                 if (sibling.nodeType == 1 && !isBookmarkNode(sibling))\r
15844                                                         return sibling;\r
15845                                         }\r
15846 \r
15847                                         return node;\r
15848                                 };\r
15849 \r
15850                                 // If previous sibling is empty then jump over it\r
15851                                 prev = findElementSibling(prev, 'previousSibling');\r
15852                                 next = findElementSibling(next, 'nextSibling');\r
15853 \r
15854                                 // Compare next and previous nodes\r
15855                                 if (compareElements(prev, next)) {\r
15856                                         // Append nodes between\r
15857                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {\r
15858                                                 tmpSibling = sibling;\r
15859                                                 sibling = sibling.nextSibling;\r
15860                                                 prev.appendChild(tmpSibling);\r
15861                                         }\r
15862 \r
15863                                         // Remove next node\r
15864                                         dom.remove(next);\r
15865 \r
15866                                         // Move children into prev node\r
15867                                         each(tinymce.grep(next.childNodes), function(node) {\r
15868                                                 prev.appendChild(node);\r
15869                                         });\r
15870 \r
15871                                         return prev;\r
15872                                 }\r
15873                         }\r
15874 \r
15875                         return next;\r
15876                 };\r
15877 \r
15878                 function isTextBlock(name) {\r
15879                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);\r
15880                 };\r
15881 \r
15882                 function getContainer(rng, start) {\r
15883                         var container, offset, lastIdx;\r
15884 \r
15885                         container = rng[start ? 'startContainer' : 'endContainer'];\r
15886                         offset = rng[start ? 'startOffset' : 'endOffset'];\r
15887 \r
15888                         if (container.nodeType == 1) {\r
15889                                 lastIdx = container.childNodes.length - 1;\r
15890 \r
15891                                 if (!start && offset)\r
15892                                         offset--;\r
15893 \r
15894                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];\r
15895                         }\r
15896 \r
15897                         return container;\r
15898                 };\r
15899 \r
15900                 function performCaretAction(type, name, vars) {\r
15901                         var i, currentPendingFormats = pendingFormats[type],\r
15902                                 otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];\r
15903 \r
15904                         function hasPending() {\r
15905                                 return pendingFormats.apply.length || pendingFormats.remove.length;\r
15906                         };\r
15907 \r
15908                         function resetPending() {\r
15909                                 pendingFormats.apply = [];\r
15910                                 pendingFormats.remove = [];\r
15911                         };\r
15912 \r
15913                         function perform(caret_node) {\r
15914                                 // Apply pending formats\r
15915                                 each(pendingFormats.apply.reverse(), function(item) {\r
15916                                         apply(item.name, item.vars, caret_node);\r
15917 \r
15918                                         // Colored nodes should be underlined so that the color of the underline matches the text color.\r
15919                                         if (item.name === 'forecolor' && item.vars.value)\r
15920                                                 processUnderlineAndColor(caret_node.parentNode);\r
15921                                 });\r
15922 \r
15923                                 // Remove pending formats\r
15924                                 each(pendingFormats.remove.reverse(), function(item) {\r
15925                                         remove(item.name, item.vars, caret_node);\r
15926                                 });\r
15927 \r
15928                                 dom.remove(caret_node, 1);\r
15929                                 resetPending();\r
15930                         };\r
15931 \r
15932                         // Check if it already exists then ignore it\r
15933                         for (i = currentPendingFormats.length - 1; i >= 0; i--) {\r
15934                                 if (currentPendingFormats[i].name == name)\r
15935                                         return;\r
15936                         }\r
15937 \r
15938                         currentPendingFormats.push({name : name, vars : vars});\r
15939 \r
15940                         // Check if it's in the other type, then remove it\r
15941                         for (i = otherPendingFormats.length - 1; i >= 0; i--) {\r
15942                                 if (otherPendingFormats[i].name == name)\r
15943                                         otherPendingFormats.splice(i, 1);\r
15944                         }\r
15945 \r
15946                         // Pending apply or remove formats\r
15947                         if (hasPending()) {\r
15948                                 ed.getDoc().execCommand('FontName', false, 'mceinline');\r
15949                                 pendingFormats.lastRng = selection.getRng();\r
15950 \r
15951                                 // IE will convert the current word\r
15952                                 each(dom.select('font,span'), function(node) {\r
15953                                         var bookmark;\r
15954 \r
15955                                         if (isCaretNode(node)) {\r
15956                                                 bookmark = selection.getBookmark();\r
15957                                                 perform(node);\r
15958                                                 selection.moveToBookmark(bookmark);\r
15959                                                 ed.nodeChanged();\r
15960                                         }\r
15961                                 });\r
15962 \r
15963                                 // Only register listeners once if we need to\r
15964                                 if (!pendingFormats.isListening && hasPending()) {\r
15965                                         pendingFormats.isListening = true;\r
15966 \r
15967                                         each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {\r
15968                                                 ed[event].addToTop(function(ed, e) {\r
15969                                                         // Do we have pending formats and is the selection moved has moved\r
15970                                                         if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {\r
15971                                                                 each(dom.select('font,span'), function(node) {\r
15972                                                                         var textNode, rng;\r
15973 \r
15974                                                                         // Look for marker\r
15975                                                                         if (isCaretNode(node)) {\r
15976                                                                                 textNode = node.firstChild;\r
15977 \r
15978                                                                                 // Find the first text node within node\r
15979                                                                                 while (textNode && textNode.nodeType != 3)\r
15980                                                                                         textNode = textNode.firstChild;\r
15981 \r
15982                                                                                 if (textNode) {\r
15983                                                                                         perform(node);\r
15984 \r
15985                                                                                         rng = dom.createRng();\r
15986                                                                                         rng.setStart(textNode, textNode.nodeValue.length);\r
15987                                                                                         rng.setEnd(textNode, textNode.nodeValue.length);\r
15988                                                                                         selection.setRng(rng);\r
15989                                                                                         ed.nodeChanged();\r
15990                                                                                 } else\r
15991                                                                                         dom.remove(node);\r
15992                                                                         }\r
15993                                                                 });\r
15994 \r
15995                                                                 // Always unbind and clear pending styles on keyup\r
15996                                                                 if (e.type == 'keyup' || e.type == 'mouseup')\r
15997                                                                         resetPending();\r
15998                                                         }\r
15999                                                 });\r
16000                                         });\r
16001                                 }\r
16002                         }\r
16003                 };\r
16004         };\r
16005 })(tinymce);\r
16006 \r
16007 tinymce.onAddEditor.add(function(tinymce, ed) {\r
16008         var filters, fontSizes, dom, settings = ed.settings;\r
16009 \r
16010         if (settings.inline_styles) {\r
16011                 fontSizes = tinymce.explode(settings.font_size_style_values);\r
16012 \r
16013                 function replaceWithSpan(node, styles) {\r
16014                         tinymce.each(styles, function(value, name) {\r
16015                                 if (value)\r
16016                                         dom.setStyle(node, name, value);\r
16017                         });\r
16018 \r
16019                         dom.rename(node, 'span');\r
16020                 };\r
16021 \r
16022                 filters = {\r
16023                         font : function(dom, node) {\r
16024                                 replaceWithSpan(node, {\r
16025                                         backgroundColor : node.style.backgroundColor,\r
16026                                         color : node.color,\r
16027                                         fontFamily : node.face,\r
16028                                         fontSize : fontSizes[parseInt(node.size) - 1]\r
16029                                 });\r
16030                         },\r
16031 \r
16032                         u : function(dom, node) {\r
16033                                 replaceWithSpan(node, {\r
16034                                         textDecoration : 'underline'\r
16035                                 });\r
16036                         },\r
16037 \r
16038                         strike : function(dom, node) {\r
16039                                 replaceWithSpan(node, {\r
16040                                         textDecoration : 'line-through'\r
16041                                 });\r
16042                         }\r
16043                 };\r
16044 \r
16045                 function convert(editor, params) {\r
16046                         dom = editor.dom;\r
16047 \r
16048                         if (settings.convert_fonts_to_spans) {\r
16049                                 tinymce.each(dom.select('font,u,strike', params.node), function(node) {\r
16050                                         filters[node.nodeName.toLowerCase()](ed.dom, node);\r
16051                                 });\r
16052                         }\r
16053                 };\r
16054 \r
16055                 ed.onPreProcess.add(convert);\r
16056                 ed.onSetContent.add(convert);\r
16057 \r
16058                 ed.onInit.add(function() {\r
16059                         ed.selection.onSetContent.add(convert);\r
16060                 });\r
16061         }\r
16062 });\r
16063 \r