]> git.mxchange.org Git - friendica.git/blob - library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
a0e114bea0084dd7b94a90aad0fac8cdb0c5508c
[friendica.git] / library / tinymce / jscripts / tiny_mce / tiny_mce_src.js
1 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY\r
2 (function(win) {\r
3         var whiteSpaceRe = /^\s*|\s*$/g,\r
4                 undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';\r
5 \r
6         var tinymce = {\r
7                 majorVersion : '3',\r
8 \r
9                 minorVersion : '5.0.1',\r
10 \r
11                 releaseDate : '2012-05-10',\r
12 \r
13                 _init : function() {\r
14                         var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;\r
15 \r
16                         t.isOpera = win.opera && opera.buildNumber;\r
17 \r
18                         t.isWebKit = /WebKit/.test(ua);\r
19 \r
20                         t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);\r
21 \r
22                         t.isIE6 = t.isIE && /MSIE [56]/.test(ua);\r
23 \r
24                         t.isIE7 = t.isIE && /MSIE [7]/.test(ua);\r
25 \r
26                         t.isIE8 = t.isIE && /MSIE [8]/.test(ua);\r
27 \r
28                         t.isIE9 = t.isIE && /MSIE [9]/.test(ua);\r
29 \r
30                         t.isGecko = !t.isWebKit && /Gecko/.test(ua);\r
31 \r
32                         t.isMac = ua.indexOf('Mac') != -1;\r
33 \r
34                         t.isAir = /adobeair/i.test(ua);\r
35 \r
36                         t.isIDevice = /(iPad|iPhone)/.test(ua);\r
37                         \r
38                         t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;\r
39 \r
40                         // TinyMCE .NET webcontrol might be setting the values for TinyMCE\r
41                         if (win.tinyMCEPreInit) {\r
42                                 t.suffix = tinyMCEPreInit.suffix;\r
43                                 t.baseURL = tinyMCEPreInit.base;\r
44                                 t.query = tinyMCEPreInit.query;\r
45                                 return;\r
46                         }\r
47 \r
48                         // Get suffix and base\r
49                         t.suffix = '';\r
50 \r
51                         // If base element found, add that infront of baseURL\r
52                         nl = d.getElementsByTagName('base');\r
53                         for (i=0; i<nl.length; i++) {\r
54                                 v = nl[i].href;\r
55                                 if (v) {\r
56                                         // Host only value like http://site.com or http://site.com:8008\r
57                                         if (/^https?:\/\/[^\/]+$/.test(v))\r
58                                                 v += '/';\r
59 \r
60                                         base = v ? v.match(/.*\//)[0] : ''; // Get only directory\r
61                                 }\r
62                         }\r
63 \r
64                         function getBase(n) {\r
65                                 if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {\r
66                                         if (/_(src|dev)\.js/g.test(n.src))\r
67                                                 t.suffix = '_src';\r
68 \r
69                                         if ((p = n.src.indexOf('?')) != -1)\r
70                                                 t.query = n.src.substring(p + 1);\r
71 \r
72                                         t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));\r
73 \r
74                                         // If path to script is relative and a base href was found add that one infront\r
75                                         // the src property will always be an absolute one on non IE browsers and IE 8\r
76                                         // so this logic will basically only be executed on older IE versions\r
77                                         if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)\r
78                                                 t.baseURL = base + t.baseURL;\r
79 \r
80                                         return t.baseURL;\r
81                                 }\r
82 \r
83                                 return null;\r
84                         };\r
85 \r
86                         // Check document\r
87                         nl = d.getElementsByTagName('script');\r
88                         for (i=0; i<nl.length; i++) {\r
89                                 if (getBase(nl[i]))\r
90                                         return;\r
91                         }\r
92 \r
93                         // Check head\r
94                         n = d.getElementsByTagName('head')[0];\r
95                         if (n) {\r
96                                 nl = n.getElementsByTagName('script');\r
97                                 for (i=0; i<nl.length; i++) {\r
98                                         if (getBase(nl[i]))\r
99                                                 return;\r
100                                 }\r
101                         }\r
102 \r
103                         return;\r
104                 },\r
105 \r
106                 is : function(o, t) {\r
107                         if (!t)\r
108                                 return o !== undef;\r
109 \r
110                         if (t == 'array' && (o.hasOwnProperty && o instanceof Array))\r
111                                 return true;\r
112 \r
113                         return typeof(o) == t;\r
114                 },\r
115 \r
116                 makeMap : function(items, delim, map) {\r
117                         var i;\r
118 \r
119                         items = items || [];\r
120                         delim = delim || ',';\r
121 \r
122                         if (typeof(items) == "string")\r
123                                 items = items.split(delim);\r
124 \r
125                         map = map || {};\r
126 \r
127                         i = items.length;\r
128                         while (i--)\r
129                                 map[items[i]] = {};\r
130 \r
131                         return map;\r
132                 },\r
133 \r
134                 each : function(o, cb, s) {\r
135                         var n, l;\r
136 \r
137                         if (!o)\r
138                                 return 0;\r
139 \r
140                         s = s || o;\r
141 \r
142                         if (o.length !== undef) {\r
143                                 // Indexed arrays, needed for Safari\r
144                                 for (n=0, l = o.length; n < l; n++) {\r
145                                         if (cb.call(s, o[n], n, o) === false)\r
146                                                 return 0;\r
147                                 }\r
148                         } else {\r
149                                 // Hashtables\r
150                                 for (n in o) {\r
151                                         if (o.hasOwnProperty(n)) {\r
152                                                 if (cb.call(s, o[n], n, o) === false)\r
153                                                         return 0;\r
154                                         }\r
155                                 }\r
156                         }\r
157 \r
158                         return 1;\r
159                 },\r
160 \r
161 \r
162                 map : function(a, f) {\r
163                         var o = [];\r
164 \r
165                         tinymce.each(a, function(v) {\r
166                                 o.push(f(v));\r
167                         });\r
168 \r
169                         return o;\r
170                 },\r
171 \r
172                 grep : function(a, f) {\r
173                         var o = [];\r
174 \r
175                         tinymce.each(a, function(v) {\r
176                                 if (!f || f(v))\r
177                                         o.push(v);\r
178                         });\r
179 \r
180                         return o;\r
181                 },\r
182 \r
183                 inArray : function(a, v) {\r
184                         var i, l;\r
185 \r
186                         if (a) {\r
187                                 for (i = 0, l = a.length; i < l; i++) {\r
188                                         if (a[i] === v)\r
189                                                 return i;\r
190                                 }\r
191                         }\r
192 \r
193                         return -1;\r
194                 },\r
195 \r
196                 extend : function(obj, ext) {\r
197                         var i, l, name, args = arguments, value;\r
198 \r
199                         for (i = 1, l = args.length; i < l; i++) {\r
200                                 ext = args[i];\r
201                                 for (name in ext) {\r
202                                         if (ext.hasOwnProperty(name)) {\r
203                                                 value = ext[name];\r
204 \r
205                                                 if (value !== undef) {\r
206                                                         obj[name] = value;\r
207                                                 }\r
208                                         }\r
209                                 }\r
210                         }\r
211 \r
212                         return obj;\r
213                 },\r
214 \r
215 \r
216                 trim : function(s) {\r
217                         return (s ? '' + s : '').replace(whiteSpaceRe, '');\r
218                 },\r
219 \r
220                 create : function(s, p, root) {\r
221                         var t = this, sp, ns, cn, scn, c, de = 0;\r
222 \r
223                         // Parse : <prefix> <class>:<super class>\r
224                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);\r
225                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name\r
226 \r
227                         // Create namespace for new class\r
228                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);\r
229 \r
230                         // Class already exists\r
231                         if (ns[cn])\r
232                                 return;\r
233 \r
234                         // Make pure static class\r
235                         if (s[2] == 'static') {\r
236                                 ns[cn] = p;\r
237 \r
238                                 if (this.onCreate)\r
239                                         this.onCreate(s[2], s[3], ns[cn]);\r
240 \r
241                                 return;\r
242                         }\r
243 \r
244                         // Create default constructor\r
245                         if (!p[cn]) {\r
246                                 p[cn] = function() {};\r
247                                 de = 1;\r
248                         }\r
249 \r
250                         // Add constructor and methods\r
251                         ns[cn] = p[cn];\r
252                         t.extend(ns[cn].prototype, p);\r
253 \r
254                         // Extend\r
255                         if (s[5]) {\r
256                                 sp = t.resolve(s[5]).prototype;\r
257                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name\r
258 \r
259                                 // Extend constructor\r
260                                 c = ns[cn];\r
261                                 if (de) {\r
262                                         // Add passthrough constructor\r
263                                         ns[cn] = function() {\r
264                                                 return sp[scn].apply(this, arguments);\r
265                                         };\r
266                                 } else {\r
267                                         // Add inherit constructor\r
268                                         ns[cn] = function() {\r
269                                                 this.parent = sp[scn];\r
270                                                 return c.apply(this, arguments);\r
271                                         };\r
272                                 }\r
273                                 ns[cn].prototype[cn] = ns[cn];\r
274 \r
275                                 // Add super methods\r
276                                 t.each(sp, function(f, n) {\r
277                                         ns[cn].prototype[n] = sp[n];\r
278                                 });\r
279 \r
280                                 // Add overridden methods\r
281                                 t.each(p, function(f, n) {\r
282                                         // Extend methods if needed\r
283                                         if (sp[n]) {\r
284                                                 ns[cn].prototype[n] = function() {\r
285                                                         this.parent = sp[n];\r
286                                                         return f.apply(this, arguments);\r
287                                                 };\r
288                                         } else {\r
289                                                 if (n != cn)\r
290                                                         ns[cn].prototype[n] = f;\r
291                                         }\r
292                                 });\r
293                         }\r
294 \r
295                         // Add static methods\r
296                         t.each(p['static'], function(f, n) {\r
297                                 ns[cn][n] = f;\r
298                         });\r
299 \r
300                         if (this.onCreate)\r
301                                 this.onCreate(s[2], s[3], ns[cn].prototype);\r
302                 },\r
303 \r
304                 walk : function(o, f, n, s) {\r
305                         s = s || this;\r
306 \r
307                         if (o) {\r
308                                 if (n)\r
309                                         o = o[n];\r
310 \r
311                                 tinymce.each(o, function(o, i) {\r
312                                         if (f.call(s, o, i, n) === false)\r
313                                                 return false;\r
314 \r
315                                         tinymce.walk(o, f, n, s);\r
316                                 });\r
317                         }\r
318                 },\r
319 \r
320                 createNS : function(n, o) {\r
321                         var i, v;\r
322 \r
323                         o = o || win;\r
324 \r
325                         n = n.split('.');\r
326                         for (i=0; i<n.length; i++) {\r
327                                 v = n[i];\r
328 \r
329                                 if (!o[v])\r
330                                         o[v] = {};\r
331 \r
332                                 o = o[v];\r
333                         }\r
334 \r
335                         return o;\r
336                 },\r
337 \r
338                 resolve : function(n, o) {\r
339                         var i, l;\r
340 \r
341                         o = o || win;\r
342 \r
343                         n = n.split('.');\r
344                         for (i = 0, l = n.length; i < l; i++) {\r
345                                 o = o[n[i]];\r
346 \r
347                                 if (!o)\r
348                                         break;\r
349                         }\r
350 \r
351                         return o;\r
352                 },\r
353 \r
354                 addUnload : function(f, s) {\r
355                         var t = this, unload;\r
356 \r
357                         unload = function() {\r
358                                 var li = t.unloads, o, n;\r
359 \r
360                                 if (li) {\r
361                                         // Call unload handlers\r
362                                         for (n in li) {\r
363                                                 o = li[n];\r
364 \r
365                                                 if (o && o.func)\r
366                                                         o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy\r
367                                         }\r
368 \r
369                                         // Detach unload function\r
370                                         if (win.detachEvent) {\r
371                                                 win.detachEvent('onbeforeunload', fakeUnload);\r
372                                                 win.detachEvent('onunload', unload);\r
373                                         } else if (win.removeEventListener)\r
374                                                 win.removeEventListener('unload', unload, false);\r
375 \r
376                                         // Destroy references\r
377                                         t.unloads = o = li = w = unload = 0;\r
378 \r
379                                         // Run garbarge collector on IE\r
380                                         if (win.CollectGarbage)\r
381                                                 CollectGarbage();\r
382                                 }\r
383                         };\r
384 \r
385                         function fakeUnload() {\r
386                                 var d = document;\r
387 \r
388                                 function stop() {\r
389                                         // Prevent memory leak\r
390                                         d.detachEvent('onstop', stop);\r
391 \r
392                                         // Call unload handler\r
393                                         if (unload)\r
394                                                 unload();\r
395 \r
396                                         d = 0;\r
397                                 };\r
398 \r
399                                 // Is there things still loading, then do some magic\r
400                                 if (d.readyState == 'interactive') {\r
401                                         // Fire unload when the currently loading page is stopped\r
402                                         if (d)\r
403                                                 d.attachEvent('onstop', stop);\r
404 \r
405                                         // Remove onstop listener after a while to prevent the unload function\r
406                                         // to execute if the user presses cancel in an onbeforeunload\r
407                                         // confirm dialog and then presses the browser stop button\r
408                                         win.setTimeout(function() {\r
409                                                 if (d)\r
410                                                         d.detachEvent('onstop', stop);\r
411                                         }, 0);\r
412                                 }\r
413                         };\r
414 \r
415                         f = {func : f, scope : s || this};\r
416 \r
417                         if (!t.unloads) {\r
418                                 // Attach unload handler\r
419                                 if (win.attachEvent) {\r
420                                         win.attachEvent('onunload', unload);\r
421                                         win.attachEvent('onbeforeunload', fakeUnload);\r
422                                 } else if (win.addEventListener)\r
423                                         win.addEventListener('unload', unload, false);\r
424 \r
425                                 // Setup initial unload handler array\r
426                                 t.unloads = [f];\r
427                         } else\r
428                                 t.unloads.push(f);\r
429 \r
430                         return f;\r
431                 },\r
432 \r
433                 removeUnload : function(f) {\r
434                         var u = this.unloads, r = null;\r
435 \r
436                         tinymce.each(u, function(o, i) {\r
437                                 if (o && o.func == f) {\r
438                                         u.splice(i, 1);\r
439                                         r = f;\r
440                                         return false;\r
441                                 }\r
442                         });\r
443 \r
444                         return r;\r
445                 },\r
446 \r
447                 explode : function(s, d) {\r
448                         if (!s || tinymce.is(s, 'array')) {\r
449                                 return s;\r
450                         }\r
451 \r
452                         return tinymce.map(s.split(d || ','), tinymce.trim);\r
453                 },\r
454 \r
455                 _addVer : function(u) {\r
456                         var v;\r
457 \r
458                         if (!this.query)\r
459                                 return u;\r
460 \r
461                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;\r
462 \r
463                         if (u.indexOf('#') == -1)\r
464                                 return u + v;\r
465 \r
466                         return u.replace('#', v + '#');\r
467                 },\r
468 \r
469                 // Fix function for IE 9 where regexps isn't working correctly\r
470                 // Todo: remove me once MS fixes the bug\r
471                 _replace : function(find, replace, str) {\r
472                         // On IE9 we have to fake $x replacement\r
473                         if (isRegExpBroken) {\r
474                                 return str.replace(find, function() {\r
475                                         var val = replace, args = arguments, i;\r
476 \r
477                                         for (i = 0; i < args.length - 2; i++) {\r
478                                                 if (args[i] === undef) {\r
479                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');\r
480                                                 } else {\r
481                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);\r
482                                                 }\r
483                                         }\r
484 \r
485                                         return val;\r
486                                 });\r
487                         }\r
488 \r
489                         return str.replace(find, replace);\r
490                 }\r
491 \r
492                 };\r
493 \r
494         // Initialize the API\r
495         tinymce._init();\r
496 \r
497         // Expose tinymce namespace to the global namespace (window)\r
498         win.tinymce = win.tinyMCE = tinymce;\r
499 \r
500         // Describe the different namespaces\r
501 \r
502         })(window);\r
503 \r
504 \r
505 \r
506 tinymce.create('tinymce.util.Dispatcher', {\r
507         scope : null,\r
508         listeners : null,\r
509         inDispatch: false,\r
510 \r
511         Dispatcher : function(scope) {\r
512                 this.scope = scope || this;\r
513                 this.listeners = [];\r
514         },\r
515 \r
516         add : function(callback, scope) {\r
517                 this.listeners.push({cb : callback, scope : scope || this.scope});\r
518 \r
519                 return callback;\r
520         },\r
521 \r
522         addToTop : function(callback, scope) {\r
523                 var self = this, listener = {cb : callback, scope : scope || self.scope};\r
524 \r
525                 // Create new listeners if addToTop is executed in a dispatch loop\r
526                 if (self.inDispatch) {\r
527                         self.listeners = [listener].concat(self.listeners);\r
528                 } else {\r
529                         self.listeners.unshift(listener);\r
530                 }\r
531 \r
532                 return callback;\r
533         },\r
534 \r
535         remove : function(callback) {\r
536                 var listeners = this.listeners, output = null;\r
537 \r
538                 tinymce.each(listeners, function(listener, i) {\r
539                         if (callback == listener.cb) {\r
540                                 output = listener;\r
541                                 listeners.splice(i, 1);\r
542                                 return false;\r
543                         }\r
544                 });\r
545 \r
546                 return output;\r
547         },\r
548 \r
549         dispatch : function() {\r
550                 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;\r
551 \r
552                 self.inDispatch = true;\r
553                 \r
554                 // Needs to be a real loop since the listener count might change while looping\r
555                 // And this is also more efficient\r
556                 for (i = 0; i < listeners.length; i++) {\r
557                         listener = listeners[i];\r
558                         returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);\r
559 \r
560                         if (returnValue === false)\r
561                                 break;\r
562                 }\r
563 \r
564                 self.inDispatch = false;\r
565 \r
566                 return returnValue;\r
567         }\r
568 \r
569         });\r
570 \r
571 (function() {\r
572         var each = tinymce.each;\r
573 \r
574         tinymce.create('tinymce.util.URI', {\r
575                 URI : function(u, s) {\r
576                         var t = this, o, a, b, base_url;\r
577 \r
578                         // Trim whitespace\r
579                         u = tinymce.trim(u);\r
580 \r
581                         // Default settings\r
582                         s = t.settings = s || {};\r
583 \r
584                         // Strange app protocol that isn't http/https or local anchor\r
585                         // For example: mailto,skype,tel etc.\r
586                         if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {\r
587                                 t.source = u;\r
588                                 return;\r
589                         }\r
590 \r
591                         // Absolute path with no host, fake host and protocol\r
592                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)\r
593                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;\r
594 \r
595                         // Relative path http:// or protocol relative //path\r
596                         if (!/^[\w\-]*:?\/\//.test(u)) {\r
597                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;\r
598                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);\r
599                         }\r
600 \r
601                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)\r
602                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something\r
603                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);\r
604                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {\r
605                                 var s = u[i];\r
606 \r
607                                 // Zope 3 workaround, they use @@something\r
608                                 if (s)\r
609                                         s = s.replace(/\(mce_at\)/g, '@@');\r
610 \r
611                                 t[v] = s;\r
612                         });\r
613 \r
614                         b = s.base_uri;\r
615                         if (b) {\r
616                                 if (!t.protocol)\r
617                                         t.protocol = b.protocol;\r
618 \r
619                                 if (!t.userInfo)\r
620                                         t.userInfo = b.userInfo;\r
621 \r
622                                 if (!t.port && t.host === 'mce_host')\r
623                                         t.port = b.port;\r
624 \r
625                                 if (!t.host || t.host === 'mce_host')\r
626                                         t.host = b.host;\r
627 \r
628                                 t.source = '';\r
629                         }\r
630 \r
631                         //t.path = t.path || '/';\r
632                 },\r
633 \r
634                 setPath : function(p) {\r
635                         var t = this;\r
636 \r
637                         p = /^(.*?)\/?(\w+)?$/.exec(p);\r
638 \r
639                         // Update path parts\r
640                         t.path = p[0];\r
641                         t.directory = p[1];\r
642                         t.file = p[2];\r
643 \r
644                         // Rebuild source\r
645                         t.source = '';\r
646                         t.getURI();\r
647                 },\r
648 \r
649                 toRelative : function(u) {\r
650                         var t = this, o;\r
651 \r
652                         if (u === "./")\r
653                                 return u;\r
654 \r
655                         u = new tinymce.util.URI(u, {base_uri : t});\r
656 \r
657                         // Not on same domain/port or protocol\r
658                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)\r
659                                 return u.getURI();\r
660 \r
661                         var tu = t.getURI(), uu = u.getURI();\r
662                         \r
663                         // Allow usage of the base_uri when relative_urls = true\r
664                         if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))\r
665                                 return tu;\r
666 \r
667                         o = t.toRelPath(t.path, u.path);\r
668 \r
669                         // Add query\r
670                         if (u.query)\r
671                                 o += '?' + u.query;\r
672 \r
673                         // Add anchor\r
674                         if (u.anchor)\r
675                                 o += '#' + u.anchor;\r
676 \r
677                         return o;\r
678                 },\r
679         \r
680                 toAbsolute : function(u, nh) {\r
681                         u = new tinymce.util.URI(u, {base_uri : this});\r
682 \r
683                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);\r
684                 },\r
685 \r
686                 toRelPath : function(base, path) {\r
687                         var items, bp = 0, out = '', i, l;\r
688 \r
689                         // Split the paths\r
690                         base = base.substring(0, base.lastIndexOf('/'));\r
691                         base = base.split('/');\r
692                         items = path.split('/');\r
693 \r
694                         if (base.length >= items.length) {\r
695                                 for (i = 0, l = base.length; i < l; i++) {\r
696                                         if (i >= items.length || base[i] != items[i]) {\r
697                                                 bp = i + 1;\r
698                                                 break;\r
699                                         }\r
700                                 }\r
701                         }\r
702 \r
703                         if (base.length < items.length) {\r
704                                 for (i = 0, l = items.length; i < l; i++) {\r
705                                         if (i >= base.length || base[i] != items[i]) {\r
706                                                 bp = i + 1;\r
707                                                 break;\r
708                                         }\r
709                                 }\r
710                         }\r
711 \r
712                         if (bp === 1)\r
713                                 return path;\r
714 \r
715                         for (i = 0, l = base.length - (bp - 1); i < l; i++)\r
716                                 out += "../";\r
717 \r
718                         for (i = bp - 1, l = items.length; i < l; i++) {\r
719                                 if (i != bp - 1)\r
720                                         out += "/" + items[i];\r
721                                 else\r
722                                         out += items[i];\r
723                         }\r
724 \r
725                         return out;\r
726                 },\r
727 \r
728                 toAbsPath : function(base, path) {\r
729                         var i, nb = 0, o = [], tr, outPath;\r
730 \r
731                         // Split paths\r
732                         tr = /\/$/.test(path) ? '/' : '';\r
733                         base = base.split('/');\r
734                         path = path.split('/');\r
735 \r
736                         // Remove empty chunks\r
737                         each(base, function(k) {\r
738                                 if (k)\r
739                                         o.push(k);\r
740                         });\r
741 \r
742                         base = o;\r
743 \r
744                         // Merge relURLParts chunks\r
745                         for (i = path.length - 1, o = []; i >= 0; i--) {\r
746                                 // Ignore empty or .\r
747                                 if (path[i].length === 0 || path[i] === ".")\r
748                                         continue;\r
749 \r
750                                 // Is parent\r
751                                 if (path[i] === '..') {\r
752                                         nb++;\r
753                                         continue;\r
754                                 }\r
755 \r
756                                 // Move up\r
757                                 if (nb > 0) {\r
758                                         nb--;\r
759                                         continue;\r
760                                 }\r
761 \r
762                                 o.push(path[i]);\r
763                         }\r
764 \r
765                         i = base.length - nb;\r
766 \r
767                         // If /a/b/c or /\r
768                         if (i <= 0)\r
769                                 outPath = o.reverse().join('/');\r
770                         else\r
771                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');\r
772 \r
773                         // Add front / if it's needed\r
774                         if (outPath.indexOf('/') !== 0)\r
775                                 outPath = '/' + outPath;\r
776 \r
777                         // Add traling / if it's needed\r
778                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)\r
779                                 outPath += tr;\r
780 \r
781                         return outPath;\r
782                 },\r
783 \r
784                 getURI : function(nh) {\r
785                         var s, t = this;\r
786 \r
787                         // Rebuild source\r
788                         if (!t.source || nh) {\r
789                                 s = '';\r
790 \r
791                                 if (!nh) {\r
792                                         if (t.protocol)\r
793                                                 s += t.protocol + '://';\r
794 \r
795                                         if (t.userInfo)\r
796                                                 s += t.userInfo + '@';\r
797 \r
798                                         if (t.host)\r
799                                                 s += t.host;\r
800 \r
801                                         if (t.port)\r
802                                                 s += ':' + t.port;\r
803                                 }\r
804 \r
805                                 if (t.path)\r
806                                         s += t.path;\r
807 \r
808                                 if (t.query)\r
809                                         s += '?' + t.query;\r
810 \r
811                                 if (t.anchor)\r
812                                         s += '#' + t.anchor;\r
813 \r
814                                 t.source = s;\r
815                         }\r
816 \r
817                         return t.source;\r
818                 }\r
819         });\r
820 })();\r
821 \r
822 (function() {\r
823         var each = tinymce.each;\r
824 \r
825         tinymce.create('static tinymce.util.Cookie', {\r
826                 getHash : function(n) {\r
827                         var v = this.get(n), h;\r
828 \r
829                         if (v) {\r
830                                 each(v.split('&'), function(v) {\r
831                                         v = v.split('=');\r
832                                         h = h || {};\r
833                                         h[unescape(v[0])] = unescape(v[1]);\r
834                                 });\r
835                         }\r
836 \r
837                         return h;\r
838                 },\r
839 \r
840                 setHash : function(n, v, e, p, d, s) {\r
841                         var o = '';\r
842 \r
843                         each(v, function(v, k) {\r
844                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);\r
845                         });\r
846 \r
847                         this.set(n, o, e, p, d, s);\r
848                 },\r
849 \r
850                 get : function(n) {\r
851                         var c = document.cookie, e, p = n + "=", b;\r
852 \r
853                         // Strict mode\r
854                         if (!c)\r
855                                 return;\r
856 \r
857                         b = c.indexOf("; " + p);\r
858 \r
859                         if (b == -1) {\r
860                                 b = c.indexOf(p);\r
861 \r
862                                 if (b !== 0)\r
863                                         return null;\r
864                         } else\r
865                                 b += 2;\r
866 \r
867                         e = c.indexOf(";", b);\r
868 \r
869                         if (e == -1)\r
870                                 e = c.length;\r
871 \r
872                         return unescape(c.substring(b + p.length, e));\r
873                 },\r
874 \r
875                 set : function(n, v, e, p, d, s) {\r
876                         document.cookie = n + "=" + escape(v) +\r
877                                 ((e) ? "; expires=" + e.toGMTString() : "") +\r
878                                 ((p) ? "; path=" + escape(p) : "") +\r
879                                 ((d) ? "; domain=" + d : "") +\r
880                                 ((s) ? "; secure" : "");\r
881                 },\r
882 \r
883                 remove : function(n, p) {\r
884                         var d = new Date();\r
885 \r
886                         d.setTime(d.getTime() - 1000);\r
887 \r
888                         this.set(n, '', d, p, d);\r
889                 }\r
890         });\r
891 })();\r
892 \r
893 (function() {\r
894         function serialize(o, quote) {\r
895                 var i, v, t, name;\r
896 \r
897                 quote = quote || '"';\r
898 \r
899                 if (o == null)\r
900                         return 'null';\r
901 \r
902                 t = typeof o;\r
903 \r
904                 if (t == 'string') {\r
905                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';\r
906 \r
907                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {\r
908                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility\r
909                                 if (quote === '"' && a === "'")\r
910                                         return a;\r
911 \r
912                                 i = v.indexOf(b);\r
913 \r
914                                 if (i + 1)\r
915                                         return '\\' + v.charAt(i + 1);\r
916 \r
917                                 a = b.charCodeAt().toString(16);\r
918 \r
919                                 return '\\u' + '0000'.substring(a.length) + a;\r
920                         }) + quote;\r
921                 }\r
922 \r
923                 if (t == 'object') {\r
924                         if (o.hasOwnProperty && o instanceof Array) {\r
925                                         for (i=0, v = '['; i<o.length; i++)\r
926                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);\r
927 \r
928                                         return v + ']';\r
929                                 }\r
930 \r
931                                 v = '{';\r
932 \r
933                                 for (name in o) {\r
934                                         if (o.hasOwnProperty(name)) {\r
935                                                 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';\r
936                                         }\r
937                                 }\r
938 \r
939                                 return v + '}';\r
940                 }\r
941 \r
942                 return '' + o;\r
943         };\r
944 \r
945         tinymce.util.JSON = {\r
946                 serialize: serialize,\r
947 \r
948                 parse: function(s) {\r
949                         try {\r
950                                 return eval('(' + s + ')');\r
951                         } catch (ex) {\r
952                                 // Ignore\r
953                         }\r
954                 }\r
955 \r
956                 };\r
957 })();\r
958 \r
959 tinymce.create('static tinymce.util.XHR', {\r
960         send : function(o) {\r
961                 var x, t, w = window, c = 0;\r
962 \r
963                 function ready() {\r
964                         if (!o.async || x.readyState == 4 || c++ > 10000) {\r
965                                 if (o.success && c < 10000 && x.status == 200)\r
966                                         o.success.call(o.success_scope, '' + x.responseText, x, o);\r
967                                 else if (o.error)\r
968                                         o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);\r
969 \r
970                                 x = null;\r
971                         } else\r
972                                 w.setTimeout(ready, 10);\r
973                 };\r
974 \r
975                 // Default settings\r
976                 o.scope = o.scope || this;\r
977                 o.success_scope = o.success_scope || o.scope;\r
978                 o.error_scope = o.error_scope || o.scope;\r
979                 o.async = o.async === false ? false : true;\r
980                 o.data = o.data || '';\r
981 \r
982                 function get(s) {\r
983                         x = 0;\r
984 \r
985                         try {\r
986                                 x = new ActiveXObject(s);\r
987                         } catch (ex) {\r
988                         }\r
989 \r
990                         return x;\r
991                 };\r
992 \r
993                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');\r
994 \r
995                 if (x) {\r
996                         if (x.overrideMimeType)\r
997                                 x.overrideMimeType(o.content_type);\r
998 \r
999                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);\r
1000 \r
1001                         if (o.content_type)\r
1002                                 x.setRequestHeader('Content-Type', o.content_type);\r
1003 \r
1004                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r
1005 \r
1006                         x.send(o.data);\r
1007 \r
1008                         // Syncronous request\r
1009                         if (!o.async)\r
1010                                 return ready();\r
1011 \r
1012                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE\r
1013                         t = w.setTimeout(ready, 10);\r
1014                 }\r
1015         }\r
1016 });\r
1017 \r
1018 (function() {\r
1019         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;\r
1020 \r
1021         tinymce.create('tinymce.util.JSONRequest', {\r
1022                 JSONRequest : function(s) {\r
1023                         this.settings = extend({\r
1024                         }, s);\r
1025                         this.count = 0;\r
1026                 },\r
1027 \r
1028                 send : function(o) {\r
1029                         var ecb = o.error, scb = o.success;\r
1030 \r
1031                         o = extend(this.settings, o);\r
1032 \r
1033                         o.success = function(c, x) {\r
1034                                 c = JSON.parse(c);\r
1035 \r
1036                                 if (typeof(c) == 'undefined') {\r
1037                                         c = {\r
1038                                                 error : 'JSON Parse error.'\r
1039                                         };\r
1040                                 }\r
1041 \r
1042                                 if (c.error)\r
1043                                         ecb.call(o.error_scope || o.scope, c.error, x);\r
1044                                 else\r
1045                                         scb.call(o.success_scope || o.scope, c.result);\r
1046                         };\r
1047 \r
1048                         o.error = function(ty, x) {\r
1049                                 if (ecb)\r
1050                                         ecb.call(o.error_scope || o.scope, ty, x);\r
1051                         };\r
1052 \r
1053                         o.data = JSON.serialize({\r
1054                                 id : o.id || 'c' + (this.count++),\r
1055                                 method : o.method,\r
1056                                 params : o.params\r
1057                         });\r
1058 \r
1059                         // JSON content type for Ruby on rails. Bug: #1883287\r
1060                         o.content_type = 'application/json';\r
1061 \r
1062                         XHR.send(o);\r
1063                 },\r
1064 \r
1065                 'static' : {\r
1066                         sendRPC : function(o) {\r
1067                                 return new tinymce.util.JSONRequest().send(o);\r
1068                         }\r
1069                 }\r
1070         });\r
1071 }());\r
1072 (function(tinymce){\r
1073         tinymce.VK = {\r
1074                 BACKSPACE: 8,\r
1075                 DELETE: 46,\r
1076                 DOWN: 40,\r
1077                 ENTER: 13,\r
1078                 LEFT: 37,\r
1079                 RIGHT: 39,\r
1080                 SPACEBAR: 32,\r
1081                 TAB: 9,\r
1082                 UP: 38,\r
1083 \r
1084                 modifierPressed: function (e) {\r
1085                         return e.shiftKey || e.ctrlKey || e.altKey;\r
1086                 }\r
1087         };\r
1088 })(tinymce);\r
1089 \r
1090 tinymce.util.Quirks = function(editor) {\r
1091         var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;\r
1092 \r
1093         function setEditorCommandState(cmd, state) {\r
1094                 try {\r
1095                         editor.getDoc().execCommand(cmd, false, state);\r
1096                 } catch (ex) {\r
1097                         // Ignore\r
1098                 }\r
1099         }\r
1100 \r
1101         function cleanupStylesWhenDeleting() {\r
1102                 function removeMergedFormatSpans(isDelete) {\r
1103                         var rng, blockElm, node, clonedSpan;\r
1104 \r
1105                         rng = selection.getRng();\r
1106 \r
1107                         // Find root block\r
1108                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1109 \r
1110                         // On delete clone the root span of the next block element\r
1111                         if (isDelete)\r
1112                                 blockElm = dom.getNext(blockElm, dom.isBlock);\r
1113 \r
1114                         // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace\r
1115                         if (blockElm) {\r
1116                                 node = blockElm.firstChild;\r
1117 \r
1118                                 // Ignore empty text nodes\r
1119                                 while (node && node.nodeType == 3 && node.nodeValue.length === 0)\r
1120                                         node = node.nextSibling;\r
1121 \r
1122                                 if (node && node.nodeName === 'SPAN') {\r
1123                                         clonedSpan = node.cloneNode(false);\r
1124                                 }\r
1125                         }\r
1126 \r
1127                         // Do the backspace/delete action\r
1128                         editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
1129 \r
1130                         // Find all odd apple-style-spans\r
1131                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1132                         tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {\r
1133                                 var bm = selection.getBookmark();\r
1134 \r
1135                                 if (clonedSpan) {\r
1136                                         dom.replace(clonedSpan.cloneNode(false), span, true);\r
1137                                 } else {\r
1138                                         dom.remove(span, true);\r
1139                                 }\r
1140 \r
1141                                 // Restore the selection\r
1142                                 selection.moveToBookmark(bm);\r
1143                         });\r
1144                 };\r
1145 \r
1146                 editor.onKeyDown.add(function(editor, e) {\r
1147                         var isDelete;\r
1148 \r
1149                         isDelete = e.keyCode == DELETE;\r
1150                         if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {\r
1151                                 e.preventDefault();\r
1152                                 removeMergedFormatSpans(isDelete);\r
1153                         }\r
1154                 });\r
1155 \r
1156                 editor.addCommand('Delete', function() {removeMergedFormatSpans();});\r
1157         };\r
1158         \r
1159         function emptyEditorWhenDeleting() {\r
1160                 function getEndPointNode(rng, start) {\r
1161                         var container, offset, prefix = start ? 'start' : 'end';\r
1162 \r
1163                         container = rng[prefix + 'Container'];\r
1164                         offset = rng[prefix + 'Offset'];\r
1165 \r
1166                         // Resolve indexed container\r
1167                         if (container.nodeType == 1 && container.hasChildNodes()) {\r
1168                                 container = container.childNodes[Math.min(start ? offset : (offset > 0 ? offset - 1 : 0), container.childNodes.length - 1)]\r
1169                         }\r
1170 \r
1171                         return container;\r
1172                 };\r
1173 \r
1174                 function isAtStartEndOfBody(rng, start) {\r
1175                         var container, offset, root, childNode, prefix = start ? 'start' : 'end', isAfter;\r
1176 \r
1177                         container = rng[prefix + 'Container'];\r
1178                         offset = rng[prefix + 'Offset'];\r
1179                         root = dom.getRoot();\r
1180 \r
1181                         // Resolve indexed container\r
1182                         if (container.nodeType == 1) {\r
1183                                 isAfter = offset >= container.childNodes.length;\r
1184                                 container = getEndPointNode(rng, start);\r
1185 \r
1186                                 if (container.nodeType == 3) {\r
1187                                         offset = start && !isAfter ? 0 : container.nodeValue.length;\r
1188                                 }\r
1189                         }\r
1190 \r
1191                         // Check if start/end is in the middle of text\r
1192                         if (container.nodeType == 3 && ((start && offset > 0) || (!start && offset < container.nodeValue.length))) {\r
1193                                 return false;\r
1194                         }\r
1195 \r
1196                         // Walk up the DOM tree to see if the endpoint is at the beginning/end of body\r
1197                         while (container !== root) {\r
1198                                 childNode = container.parentNode[start ? 'firstChild' : 'lastChild'];\r
1199 \r
1200                                 // If first/last element is a BR then jump to it's sibling in case: <p>x<br></p>\r
1201                                 if (childNode.nodeName == "BR") {\r
1202                                         childNode = childNode[start ? 'nextSibling' : 'previousSibling'] || childNode;\r
1203                                 }\r
1204 \r
1205                                 // If the childNode isn't the container node then break in case <p><span>A</span>[X]</p>\r
1206                                 if (childNode !== container) {\r
1207                                         return false;\r
1208                                 }\r
1209 \r
1210                                 container = container.parentNode;\r
1211                         }\r
1212 \r
1213                         return true;\r
1214                 };\r
1215 \r
1216                 editor.onKeyDown.addToTop(function(editor, e) {\r
1217                         var rng, keyCode = e.keyCode;\r
1218 \r
1219                         if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {\r
1220                                 rng = selection.getRng(true);\r
1221 \r
1222                                 if (isAtStartEndOfBody(rng, true) && isAtStartEndOfBody(rng, false) &&\r
1223                                         (rng.collapsed || dom.findCommonAncestor(getEndPointNode(rng, true), getEndPointNode(rng)) === dom.getRoot())) {\r
1224                                         editor.setContent('');\r
1225                                         editor.nodeChanged();\r
1226                                         e.preventDefault();\r
1227                                 }\r
1228                         }\r
1229                 });\r
1230         };\r
1231 \r
1232         function inputMethodFocus() {\r
1233                 if (!editor.settings.content_editable) {\r
1234                         // Case 1 IME doesn't initialize if you focus the document\r
1235                         dom.bind(editor.getDoc(), 'focusin', function(e) {\r
1236                                 selection.setRng(selection.getRng());\r
1237                         });\r
1238 \r
1239                         // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event\r
1240                         dom.bind(editor.getDoc(), 'mousedown', function(e) {\r
1241                                 if (e.target == editor.getDoc().documentElement) {\r
1242                                         editor.getWin().focus();\r
1243                                         selection.setRng(selection.getRng());\r
1244                                 }\r
1245                         });\r
1246                 }\r
1247         };\r
1248 \r
1249         function removeHrOnBackspace() {\r
1250                 editor.onKeyDown.add(function(editor, e) {\r
1251                         if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {\r
1252                                 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {\r
1253                                         var node = selection.getNode();\r
1254                                         var previousSibling = node.previousSibling;\r
1255 \r
1256                                         if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {\r
1257                                                 dom.remove(previousSibling);\r
1258                                                 tinymce.dom.Event.cancel(e);\r
1259                                         }\r
1260                                 }\r
1261                         }\r
1262                 })\r
1263         }\r
1264 \r
1265         function focusBody() {\r
1266                 // Fix for a focus bug in FF 3.x where the body element\r
1267                 // wouldn't get proper focus if the user clicked on the HTML element\r
1268                 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4\r
1269                         editor.onMouseDown.add(function(editor, e) {\r
1270                                 if (e.target.nodeName === "HTML") {\r
1271                                         var body = editor.getBody();\r
1272 \r
1273                                         // Blur the body it's focused but not correctly focused\r
1274                                         body.blur();\r
1275 \r
1276                                         // Refocus the body after a little while\r
1277                                         setTimeout(function() {\r
1278                                                 body.focus();\r
1279                                         }, 0);\r
1280                                 }\r
1281                         });\r
1282                 }\r
1283         };\r
1284 \r
1285         function selectControlElements() {\r
1286                 editor.onClick.add(function(editor, e) {\r
1287                         e = e.target;\r
1288 \r
1289                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250\r
1290                         // WebKit can't even do simple things like selecting an image\r
1291                         // Needs tobe the setBaseAndExtend or it will fail to select floated images\r
1292                         if (/^(IMG|HR)$/.test(e.nodeName)) {\r
1293                                 selection.getSel().setBaseAndExtent(e, 0, e, 1);\r
1294                         }\r
1295 \r
1296                         if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {\r
1297                                 selection.select(e);\r
1298                         }\r
1299 \r
1300                         editor.nodeChanged();\r
1301                 });\r
1302         };\r
1303 \r
1304         function removeStylesWhenDeletingAccrossBlockElements() {\r
1305                 function getAttributeApplyFunction() {\r
1306                         var template = dom.getAttribs(selection.getStart().cloneNode(false));\r
1307 \r
1308                         return function() {\r
1309                                 var target = selection.getStart();\r
1310 \r
1311                                 if (target !== editor.getBody()) {\r
1312                                         dom.setAttrib(target, "style", null);\r
1313 \r
1314                                         tinymce.each(template, function(attr) {\r
1315                                                 target.setAttributeNode(attr.cloneNode(true));\r
1316                                         });\r
1317                                 }\r
1318                         };\r
1319                 }\r
1320 \r
1321                 function isSelectionAcrossElements() {\r
1322                         return !selection.isCollapsed() && selection.getStart() != selection.getEnd();\r
1323                 }\r
1324 \r
1325                 function blockEvent(editor, e) {\r
1326                         e.preventDefault();\r
1327                         return false;\r
1328                 }\r
1329 \r
1330                 editor.onKeyPress.add(function(editor, e) {\r
1331                         var applyAttributes;\r
1332 \r
1333                         if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {\r
1334                                 applyAttributes = getAttributeApplyFunction();\r
1335                                 editor.getDoc().execCommand('delete', false, null);\r
1336                                 applyAttributes();\r
1337                                 e.preventDefault();\r
1338                                 return false;\r
1339                         }\r
1340                 });\r
1341 \r
1342                 dom.bind(editor.getDoc(), 'cut', function(e) {\r
1343                         var applyAttributes;\r
1344 \r
1345                         if (isSelectionAcrossElements()) {\r
1346                                 applyAttributes = getAttributeApplyFunction();\r
1347                                 editor.onKeyUp.addToTop(blockEvent);\r
1348 \r
1349                                 setTimeout(function() {\r
1350                                         applyAttributes();\r
1351                                         editor.onKeyUp.remove(blockEvent);\r
1352                                 }, 0);\r
1353                         }\r
1354                 });\r
1355         }\r
1356 \r
1357         function selectionChangeNodeChanged() {\r
1358                 var lastRng, selectionTimer;\r
1359 \r
1360                 dom.bind(editor.getDoc(), 'selectionchange', function() {\r
1361                         if (selectionTimer) {\r
1362                                 clearTimeout(selectionTimer);\r
1363                                 selectionTimer = 0;\r
1364                         }\r
1365 \r
1366                         selectionTimer = window.setTimeout(function() {\r
1367                                 var rng = selection.getRng();\r
1368 \r
1369                                 // Compare the ranges to see if it was a real change or not\r
1370                                 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {\r
1371                                         editor.nodeChanged();\r
1372                                         lastRng = rng;\r
1373                                 }\r
1374                         }, 50);\r
1375                 });\r
1376         }\r
1377 \r
1378         function ensureBodyHasRoleApplication() {\r
1379                 document.body.setAttribute("role", "application");\r
1380         }\r
1381 \r
1382         function disableBackspaceIntoATable() {\r
1383                 editor.onKeyDown.add(function(editor, e) {\r
1384                         if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {\r
1385                                 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {\r
1386                                         var previousSibling = selection.getNode().previousSibling;\r
1387                                         if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {\r
1388                                                 return tinymce.dom.Event.cancel(e);\r
1389                                         }\r
1390                                 }\r
1391                         }\r
1392                 })\r
1393         }\r
1394 \r
1395         function addNewLinesBeforeBrInPre() {\r
1396                 var documentMode = editor.getDoc().documentMode;\r
1397 \r
1398                 // IE8+ rendering mode does the right thing with BR in PRE\r
1399                 if (documentMode && documentMode > 7) {\r
1400                         return;\r
1401                 }\r
1402 \r
1403                  // Enable display: none in area and add a specific class that hides all BR elements in PRE to\r
1404                  // avoid the caret from getting stuck at the BR elements while pressing the right arrow key\r
1405                 setEditorCommandState('RespectVisibilityInDesign', true);\r
1406                 dom.addClass(editor.getBody(), 'mceHideBrInPre');\r
1407 \r
1408                 // Adds a \n before all BR elements in PRE to get them visual\r
1409                 editor.parser.addNodeFilter('pre', function(nodes, name) {\r
1410                         var i = nodes.length, brNodes, j, brElm, sibling;\r
1411 \r
1412                         while (i--) {\r
1413                                 brNodes = nodes[i].getAll('br');\r
1414                                 j = brNodes.length;\r
1415                                 while (j--) {\r
1416                                         brElm = brNodes[j];\r
1417 \r
1418                                         // Add \n before BR in PRE elements on older IE:s so the new lines get rendered\r
1419                                         sibling = brElm.prev;\r
1420                                         if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {\r
1421                                                 sibling.value += '\n';\r
1422                                         } else {\r
1423                                                 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';\r
1424                                         }\r
1425                                 }\r
1426                         }\r
1427                 });\r
1428 \r
1429                 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible\r
1430                 editor.serializer.addNodeFilter('pre', function(nodes, name) {\r
1431                         var i = nodes.length, brNodes, j, brElm, sibling;\r
1432 \r
1433                         while (i--) {\r
1434                                 brNodes = nodes[i].getAll('br');\r
1435                                 j = brNodes.length;\r
1436                                 while (j--) {\r
1437                                         brElm = brNodes[j];\r
1438                                         sibling = brElm.prev;\r
1439                                         if (sibling && sibling.type == 3) {\r
1440                                                 sibling.value = sibling.value.replace(/\r?\n$/, '');\r
1441                                         }\r
1442                                 }\r
1443                         }\r
1444                 });\r
1445         }\r
1446 \r
1447         function removePreSerializedStylesWhenSelectingControls() {\r
1448                 dom.bind(editor.getBody(), 'mouseup', function(e) {\r
1449                         var value, node = selection.getNode();\r
1450 \r
1451                         // Moved styles to attributes on IMG eements\r
1452                         if (node.nodeName == 'IMG') {\r
1453                                 // Convert style width to width attribute\r
1454                                 if (value = dom.getStyle(node, 'width')) {\r
1455                                         dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));\r
1456                                         dom.setStyle(node, 'width', '');\r
1457                                 }\r
1458 \r
1459                                 // Convert style height to height attribute\r
1460                                 if (value = dom.getStyle(node, 'height')) {\r
1461                                         dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));\r
1462                                         dom.setStyle(node, 'height', '');\r
1463                                 }\r
1464                         }\r
1465                 });\r
1466         }\r
1467 \r
1468         function keepInlineElementOnDeleteBackspace() {\r
1469                 editor.onKeyDown.add(function(editor, e) {\r
1470                         var isDelete, rng, container, offset, brElm, sibling, collapsed;\r
1471 \r
1472                         isDelete = e.keyCode == DELETE;\r
1473                         if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {\r
1474                                 rng = selection.getRng();\r
1475                                 container = rng.startContainer;\r
1476                                 offset = rng.startOffset;\r
1477                                 collapsed = rng.collapsed;\r
1478 \r
1479                                 // Override delete if the start container is a text node and is at the beginning of text or\r
1480                                 // just before/after the last character to be deleted in collapsed mode\r
1481                                 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {\r
1482                                         nonEmptyElements = editor.schema.getNonEmptyElements();\r
1483 \r
1484                                         // Prevent default logic since it's broken\r
1485                                         e.preventDefault();\r
1486 \r
1487                                         // Insert a BR before the text node this will prevent the containing element from being deleted/converted\r
1488                                         brElm = dom.create('br', {id: '__tmp'});\r
1489                                         container.parentNode.insertBefore(brElm, container);\r
1490 \r
1491                                         // Do the browser delete\r
1492                                         editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
1493 \r
1494                                         // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>\r
1495                                         container = selection.getRng().startContainer;\r
1496                                         sibling = container.previousSibling;\r
1497                                         if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {\r
1498                                                 dom.remove(sibling);\r
1499                                         }\r
1500 \r
1501                                         // Remove the temp element we inserted\r
1502                                         dom.remove('__tmp');\r
1503                                 }\r
1504                         }\r
1505                 });\r
1506         }\r
1507 \r
1508         function removeBlockQuoteOnBackSpace() {\r
1509                 // Add block quote deletion handler\r
1510                 editor.onKeyDown.add(function(editor, e) {\r
1511                         var rng, container, offset, root, parent;\r
1512 \r
1513                         if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {\r
1514                                 return;\r
1515                         }\r
1516 \r
1517                         rng = selection.getRng();\r
1518                         container = rng.startContainer;\r
1519                         offset = rng.startOffset;\r
1520                         root = dom.getRoot();\r
1521                         parent = container;\r
1522 \r
1523                         if (!rng.collapsed || offset !== 0) {\r
1524                                 return;\r
1525                         }\r
1526 \r
1527                         while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {\r
1528                                 parent = parent.parentNode;\r
1529                         }\r
1530 \r
1531                         // Is the cursor at the beginning of a blockquote?\r
1532                         if (parent.tagName === 'BLOCKQUOTE') {\r
1533                                 // Remove the blockquote\r
1534                                 editor.formatter.toggle('blockquote', null, parent);\r
1535 \r
1536                                 // Move the caret to the beginning of container\r
1537                                 rng.setStart(container, 0);\r
1538                                 rng.setEnd(container, 0);\r
1539                                 selection.setRng(rng);\r
1540                                 selection.collapse(false);\r
1541                         }\r
1542                 });\r
1543         };\r
1544 \r
1545         function setGeckoEditingOptions() {\r
1546                 function setOpts() {\r
1547                         editor._refreshContentEditable();\r
1548 \r
1549                         setEditorCommandState("StyleWithCSS", false);\r
1550                         setEditorCommandState("enableInlineTableEditing", false);\r
1551 \r
1552                         if (!settings.object_resizing) {\r
1553                                 setEditorCommandState("enableObjectResizing", false);\r
1554                         }\r
1555                 };\r
1556 \r
1557                 if (!settings.readonly) {\r
1558                         editor.onBeforeExecCommand.add(setOpts);\r
1559                         editor.onMouseDown.add(setOpts);\r
1560                 }\r
1561         };\r
1562 \r
1563         function addBrAfterLastLinks() {\r
1564                 function fixLinks(editor, o) {\r
1565                         tinymce.each(dom.select('a'), function(node) {\r
1566                                 var parentNode = node.parentNode, root = dom.getRoot();\r
1567 \r
1568                                 if (parentNode.lastChild === node) {\r
1569                                         while (parentNode && !dom.isBlock(parentNode)) {\r
1570                                                 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {\r
1571                                                         return;\r
1572                                                 }\r
1573 \r
1574                                                 parentNode = parentNode.parentNode;\r
1575                                         }\r
1576 \r
1577                                         dom.add(parentNode, 'br', {'data-mce-bogus' : 1});\r
1578                                 }\r
1579                         });\r
1580                 };\r
1581 \r
1582                 editor.onExecCommand.add(function(editor, cmd) {\r
1583                         if (cmd === 'CreateLink') {\r
1584                                 fixLinks(editor);\r
1585                         }\r
1586                 });\r
1587 \r
1588                 editor.onSetContent.add(selection.onSetContent.add(fixLinks));\r
1589         };\r
1590 \r
1591         function removeGhostSelection() {\r
1592                 function repaint(sender, args) {\r
1593                         if (!sender || !args.initial) {\r
1594                                 editor.execCommand('mceRepaint');\r
1595                         }\r
1596                 };\r
1597 \r
1598                 editor.onUndo.add(repaint);\r
1599                 editor.onRedo.add(repaint);\r
1600                 editor.onSetContent.add(repaint);\r
1601         };\r
1602 \r
1603         function deleteImageOnBackSpace() {\r
1604                 editor.onKeyDown.add(function(editor, e) {\r
1605                         if (!e.isDefaultPrevented() && e.keyCode == 8 && selection.getNode().nodeName == 'IMG') {\r
1606                                 e.preventDefault();\r
1607                                 editor.undoManager.beforeChange();\r
1608                                 dom.remove(selection.getNode());\r
1609                                 editor.undoManager.add();\r
1610                         }\r
1611                 });\r
1612         };\r
1613 \r
1614         // All browsers\r
1615         disableBackspaceIntoATable();\r
1616         removeBlockQuoteOnBackSpace();\r
1617         emptyEditorWhenDeleting();\r
1618 \r
1619         // WebKit\r
1620         if (tinymce.isWebKit) {\r
1621                 keepInlineElementOnDeleteBackspace();\r
1622                 cleanupStylesWhenDeleting();\r
1623                 inputMethodFocus();\r
1624                 selectControlElements();\r
1625 \r
1626                 // iOS\r
1627                 if (tinymce.isIDevice) {\r
1628                         selectionChangeNodeChanged();\r
1629                 }\r
1630         }\r
1631 \r
1632         // IE\r
1633         if (tinymce.isIE) {\r
1634                 removeHrOnBackspace();\r
1635                 ensureBodyHasRoleApplication();\r
1636                 addNewLinesBeforeBrInPre();\r
1637                 removePreSerializedStylesWhenSelectingControls();\r
1638                 deleteImageOnBackSpace();\r
1639         }\r
1640 \r
1641         // Gecko\r
1642         if (tinymce.isGecko) {\r
1643                 removeHrOnBackspace();\r
1644                 focusBody();\r
1645                 removeStylesWhenDeletingAccrossBlockElements();\r
1646                 setGeckoEditingOptions();\r
1647                 addBrAfterLastLinks();\r
1648                 removeGhostSelection();\r
1649         }\r
1650 };\r
1651 (function(tinymce) {\r
1652         var namedEntities, baseEntities, reverseEntities,\r
1653                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1654                 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1655                 rawCharsRegExp = /[<>&\"\']/g,\r
1656                 entityRegExp = /&(#x|#)?([\w]+);/g,\r
1657                 asciiMap = {\r
1658                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",\r
1659                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",\r
1660                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",\r
1661                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",\r
1662                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"\r
1663                 };\r
1664 \r
1665         // Raw entities\r
1666         baseEntities = {\r
1667                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code\r
1668                 "'" : '&#39;',\r
1669                 '<' : '&lt;',\r
1670                 '>' : '&gt;',\r
1671                 '&' : '&amp;'\r
1672         };\r
1673 \r
1674         // Reverse lookup table for raw entities\r
1675         reverseEntities = {\r
1676                 '&lt;' : '<',\r
1677                 '&gt;' : '>',\r
1678                 '&amp;' : '&',\r
1679                 '&quot;' : '"',\r
1680                 '&apos;' : "'"\r
1681         };\r
1682 \r
1683         // Decodes text by using the browser\r
1684         function nativeDecode(text) {\r
1685                 var elm;\r
1686 \r
1687                 elm = document.createElement("div");\r
1688                 elm.innerHTML = text;\r
1689 \r
1690                 return elm.textContent || elm.innerText || text;\r
1691         };\r
1692 \r
1693         // Build a two way lookup table for the entities\r
1694         function buildEntitiesLookup(items, radix) {\r
1695                 var i, chr, entity, lookup = {};\r
1696 \r
1697                 if (items) {\r
1698                         items = items.split(',');\r
1699                         radix = radix || 10;\r
1700 \r
1701                         // Build entities lookup table\r
1702                         for (i = 0; i < items.length; i += 2) {\r
1703                                 chr = String.fromCharCode(parseInt(items[i], radix));\r
1704 \r
1705                                 // Only add non base entities\r
1706                                 if (!baseEntities[chr]) {\r
1707                                         entity = '&' + items[i + 1] + ';';\r
1708                                         lookup[chr] = entity;\r
1709                                         lookup[entity] = chr;\r
1710                                 }\r
1711                         }\r
1712 \r
1713                         return lookup;\r
1714                 }\r
1715         };\r
1716 \r
1717         // Unpack entities lookup where the numbers are in radix 32 to reduce the size\r
1718         namedEntities = buildEntitiesLookup(\r
1719                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +\r
1720                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +\r
1721                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +\r
1722                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +\r
1723                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +\r
1724                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +\r
1725                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +\r
1726                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +\r
1727                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +\r
1728                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +\r
1729                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +\r
1730                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +\r
1731                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +\r
1732                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +\r
1733                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +\r
1734                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +\r
1735                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +\r
1736                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +\r
1737                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +\r
1738                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +\r
1739                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +\r
1740                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +\r
1741                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +\r
1742                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +\r
1743                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);\r
1744 \r
1745         tinymce.html = tinymce.html || {};\r
1746 \r
1747         tinymce.html.Entities = {\r
1748                 encodeRaw : function(text, attr) {\r
1749                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1750                                 return baseEntities[chr] || chr;\r
1751                         });\r
1752                 },\r
1753 \r
1754                 encodeAllRaw : function(text) {\r
1755                         return ('' + text).replace(rawCharsRegExp, function(chr) {\r
1756                                 return baseEntities[chr] || chr;\r
1757                         });\r
1758                 },\r
1759 \r
1760                 encodeNumeric : function(text, attr) {\r
1761                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1762                                 // Multi byte sequence convert it to a single entity\r
1763                                 if (chr.length > 1)\r
1764                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';\r
1765 \r
1766                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';\r
1767                         });\r
1768                 },\r
1769 \r
1770                 encodeNamed : function(text, attr, entities) {\r
1771                         entities = entities || namedEntities;\r
1772 \r
1773                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1774                                 return baseEntities[chr] || entities[chr] || chr;\r
1775                         });\r
1776                 },\r
1777 \r
1778                 getEncodeFunc : function(name, entities) {\r
1779                         var Entities = tinymce.html.Entities;\r
1780 \r
1781                         entities = buildEntitiesLookup(entities) || namedEntities;\r
1782 \r
1783                         function encodeNamedAndNumeric(text, attr) {\r
1784                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
1785                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;\r
1786                                 });\r
1787                         };\r
1788 \r
1789                         function encodeCustomNamed(text, attr) {\r
1790                                 return Entities.encodeNamed(text, attr, entities);\r
1791                         };\r
1792 \r
1793                         // Replace + with , to be compatible with previous TinyMCE versions\r
1794                         name = tinymce.makeMap(name.replace(/\+/g, ','));\r
1795 \r
1796                         // Named and numeric encoder\r
1797                         if (name.named && name.numeric)\r
1798                                 return encodeNamedAndNumeric;\r
1799 \r
1800                         // Named encoder\r
1801                         if (name.named) {\r
1802                                 // Custom names\r
1803                                 if (entities)\r
1804                                         return encodeCustomNamed;\r
1805 \r
1806                                 return Entities.encodeNamed;\r
1807                         }\r
1808 \r
1809                         // Numeric\r
1810                         if (name.numeric)\r
1811                                 return Entities.encodeNumeric;\r
1812 \r
1813                         // Raw encoder\r
1814                         return Entities.encodeRaw;\r
1815                 },\r
1816 \r
1817                 decode : function(text) {\r
1818                         return text.replace(entityRegExp, function(all, numeric, value) {\r
1819                                 if (numeric) {\r
1820                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);\r
1821 \r
1822                                         // Support upper UTF\r
1823                                         if (value > 0xFFFF) {\r
1824                                                 value -= 0x10000;\r
1825 \r
1826                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));\r
1827                                         } else\r
1828                                                 return asciiMap[value] || String.fromCharCode(value);\r
1829                                 }\r
1830 \r
1831                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);\r
1832                         });\r
1833                 }\r
1834         };\r
1835 })(tinymce);\r
1836 \r
1837 tinymce.html.Styles = function(settings, schema) {\r
1838         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,\r
1839                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,\r
1840                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,\r
1841                 trimRightRegExp = /\s+$/,\r
1842                 urlColorRegExp = /rgb/,\r
1843                 undef, i, encodingLookup = {}, encodingItems;\r
1844 \r
1845         settings = settings || {};\r
1846 \r
1847         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');\r
1848         for (i = 0; i < encodingItems.length; i++) {\r
1849                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;\r
1850                 encodingLookup['\uFEFF' + i] = encodingItems[i];\r
1851         }\r
1852 \r
1853         function toHex(match, r, g, b) {\r
1854                 function hex(val) {\r
1855                         val = parseInt(val).toString(16);\r
1856 \r
1857                         return val.length > 1 ? val : '0' + val; // 0 -> 00\r
1858                 };\r
1859 \r
1860                 return '#' + hex(r) + hex(g) + hex(b);\r
1861         };\r
1862 \r
1863         return {\r
1864                 toHex : function(color) {\r
1865                         return color.replace(rgbRegExp, toHex);\r
1866                 },\r
1867 \r
1868                 parse : function(css) {\r
1869                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;\r
1870 \r
1871                         function compress(prefix, suffix) {\r
1872                                 var top, right, bottom, left;\r
1873 \r
1874                                 // Get values and check it it needs compressing\r
1875                                 top = styles[prefix + '-top' + suffix];\r
1876                                 if (!top)\r
1877                                         return;\r
1878 \r
1879                                 right = styles[prefix + '-right' + suffix];\r
1880                                 if (top != right)\r
1881                                         return;\r
1882 \r
1883                                 bottom = styles[prefix + '-bottom' + suffix];\r
1884                                 if (right != bottom)\r
1885                                         return;\r
1886 \r
1887                                 left = styles[prefix + '-left' + suffix];\r
1888                                 if (bottom != left)\r
1889                                         return;\r
1890 \r
1891                                 // Compress\r
1892                                 styles[prefix + suffix] = left;\r
1893                                 delete styles[prefix + '-top' + suffix];\r
1894                                 delete styles[prefix + '-right' + suffix];\r
1895                                 delete styles[prefix + '-bottom' + suffix];\r
1896                                 delete styles[prefix + '-left' + suffix];\r
1897                         };\r
1898 \r
1899                         function canCompress(key) {\r
1900                                 var value = styles[key], i;\r
1901 \r
1902                                 if (!value || value.indexOf(' ') < 0)\r
1903                                         return;\r
1904 \r
1905                                 value = value.split(' ');\r
1906                                 i = value.length;\r
1907                                 while (i--) {\r
1908                                         if (value[i] !== value[0])\r
1909                                                 return false;\r
1910                                 }\r
1911 \r
1912                                 styles[key] = value[0];\r
1913 \r
1914                                 return true;\r
1915                         };\r
1916 \r
1917                         function compress2(target, a, b, c) {\r
1918                                 if (!canCompress(a))\r
1919                                         return;\r
1920 \r
1921                                 if (!canCompress(b))\r
1922                                         return;\r
1923 \r
1924                                 if (!canCompress(c))\r
1925                                         return;\r
1926 \r
1927                                 // Compress\r
1928                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];\r
1929                                 delete styles[a];\r
1930                                 delete styles[b];\r
1931                                 delete styles[c];\r
1932                         };\r
1933 \r
1934                         // Encodes the specified string by replacing all \" \' ; : with _<num>\r
1935                         function encode(str) {\r
1936                                 isEncoded = true;\r
1937 \r
1938                                 return encodingLookup[str];\r
1939                         };\r
1940 \r
1941                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc\r
1942                         // It will also decode the \" \' if keep_slashes is set to fale or omitted\r
1943                         function decode(str, keep_slashes) {\r
1944                                 if (isEncoded) {\r
1945                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {\r
1946                                                 return encodingLookup[str];\r
1947                                         });\r
1948                                 }\r
1949 \r
1950                                 if (!keep_slashes)\r
1951                                         str = str.replace(/\\([\'\";:])/g, "$1");\r
1952 \r
1953                                 return str;\r
1954                         };\r
1955 \r
1956                         function processUrl(match, url, url2, url3, str, str2) {\r
1957                                 str = str || str2;\r
1958 \r
1959                                 if (str) {\r
1960                                         str = decode(str);\r
1961 \r
1962                                         // Force strings into single quote format\r
1963                                         return "'" + str.replace(/\'/g, "\\'") + "'";\r
1964                                 }\r
1965 \r
1966                                 url = decode(url || url2 || url3);\r
1967 \r
1968                                 // Convert the URL to relative/absolute depending on config\r
1969                                 if (urlConverter)\r
1970                                         url = urlConverter.call(urlConverterScope, url, 'style');\r
1971 \r
1972                                 // Output new URL format\r
1973                                 return "url('" + url.replace(/\'/g, "\\'") + "')";\r
1974                         };\r
1975 \r
1976                         if (css) {\r
1977                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing\r
1978                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {\r
1979                                         return str.replace(/[;:]/g, encode);\r
1980                                 });\r
1981 \r
1982                                 // Parse styles\r
1983                                 while (matches = styleRegExp.exec(css)) {\r
1984                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();\r
1985                                         value = matches[2].replace(trimRightRegExp, '');\r
1986 \r
1987                                         if (name && value.length > 0) {\r
1988                                                 // Opera will produce 700 instead of bold in their style values\r
1989                                                 if (name === 'font-weight' && value === '700')\r
1990                                                         value = 'bold';\r
1991                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED\r
1992                                                         value = value.toLowerCase();            \r
1993 \r
1994                                                 // Convert RGB colors to HEX\r
1995                                                 value = value.replace(rgbRegExp, toHex);\r
1996 \r
1997                                                 // Convert URLs and force them into url('value') format\r
1998                                                 value = value.replace(urlOrStrRegExp, processUrl);\r
1999                                                 styles[name] = isEncoded ? decode(value, true) : value;\r
2000                                         }\r
2001 \r
2002                                         styleRegExp.lastIndex = matches.index + matches[0].length;\r
2003                                 }\r
2004 \r
2005                                 // Compress the styles to reduce it's size for example IE will expand styles\r
2006                                 compress("border", "");\r
2007                                 compress("border", "-width");\r
2008                                 compress("border", "-color");\r
2009                                 compress("border", "-style");\r
2010                                 compress("padding", "");\r
2011                                 compress("margin", "");\r
2012                                 compress2('border', 'border-width', 'border-style', 'border-color');\r
2013 \r
2014                                 // Remove pointless border, IE produces these\r
2015                                 if (styles.border === 'medium none')\r
2016                                         delete styles.border;\r
2017                         }\r
2018 \r
2019                         return styles;\r
2020                 },\r
2021 \r
2022                 serialize : function(styles, element_name) {\r
2023                         var css = '', name, value;\r
2024 \r
2025                         function serializeStyles(name) {\r
2026                                 var styleList, i, l, value;\r
2027 \r
2028                                 styleList = schema.styles[name];\r
2029                                 if (styleList) {\r
2030                                         for (i = 0, l = styleList.length; i < l; i++) {\r
2031                                                 name = styleList[i];\r
2032                                                 value = styles[name];\r
2033 \r
2034                                                 if (value !== undef && value.length > 0)\r
2035                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
2036                                         }\r
2037                                 }\r
2038                         };\r
2039 \r
2040                         // Serialize styles according to schema\r
2041                         if (element_name && schema && schema.styles) {\r
2042                                 // Serialize global styles and element specific styles\r
2043                                 serializeStyles('*');\r
2044                                 serializeStyles(element_name);\r
2045                         } else {\r
2046                                 // Output the styles in the order they are inside the object\r
2047                                 for (name in styles) {\r
2048                                         value = styles[name];\r
2049 \r
2050                                         if (value !== undef && value.length > 0)\r
2051                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
2052                                 }\r
2053                         }\r
2054 \r
2055                         return css;\r
2056                 }\r
2057         };\r
2058 };\r
2059 \r
2060 (function(tinymce) {\r
2061         var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;\r
2062 \r
2063         function split(str, delim) {\r
2064                 return str.split(delim || ',');\r
2065         };\r
2066 \r
2067         function unpack(lookup, data) {\r
2068                 var key, elements = {};\r
2069 \r
2070                 function replace(value) {\r
2071                         return value.replace(/[A-Z]+/g, function(key) {\r
2072                                 return replace(lookup[key]);\r
2073                         });\r
2074                 };\r
2075 \r
2076                 // Unpack lookup\r
2077                 for (key in lookup) {\r
2078                         if (lookup.hasOwnProperty(key))\r
2079                                 lookup[key] = replace(lookup[key]);\r
2080                 }\r
2081 \r
2082                 // Unpack and parse data into object map\r
2083                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {\r
2084                         attributes = split(attributes, '|');\r
2085 \r
2086                         elements[name] = {\r
2087                                 attributes : makeMap(attributes),\r
2088                                 attributesOrder : attributes,\r
2089                                 children : makeMap(children, '|', {'#comment' : {}})\r
2090                         }\r
2091                 });\r
2092 \r
2093                 return elements;\r
2094         };\r
2095 \r
2096         function getHTML5() {\r
2097                 var html5 = mapCache.html5;\r
2098 \r
2099                 if (!html5) {\r
2100                         html5 = mapCache.html5 = unpack({\r
2101                                         A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title',\r
2102                                         B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video',\r
2103                                         C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'\r
2104                                 }, 'html[A|manifest][body|head]' +\r
2105                                         'head[A][base|command|link|meta|noscript|script|style|title]' +\r
2106                                         'title[A][#]' +\r
2107                                         'base[A|href|target][]' +\r
2108                                         'link[A|href|rel|media|type|sizes][]' +\r
2109                                         'meta[A|http-equiv|name|content|charset][]' +\r
2110                                         'style[A|type|media|scoped][#]' +\r
2111                                         'script[A|charset|type|src|defer|async][#]' +\r
2112                                         'noscript[A][C]' +\r
2113                                         'body[A][C]' +\r
2114                                         'section[A][C]' +\r
2115                                         'nav[A][C]' +\r
2116                                         'article[A][C]' +\r
2117                                         'aside[A][C]' +\r
2118                                         'h1[A][B]' +\r
2119                                         'h2[A][B]' +\r
2120                                         'h3[A][B]' +\r
2121                                         'h4[A][B]' +\r
2122                                         'h5[A][B]' +\r
2123                                         'h6[A][B]' +\r
2124                                         'hgroup[A][h1|h2|h3|h4|h5|h6]' +\r
2125                                         'header[A][C]' +\r
2126                                         'footer[A][C]' +\r
2127                                         'address[A][C]' +\r
2128                                         'p[A][B]' +\r
2129                                         'br[A][]' +\r
2130                                         'pre[A][B]' +\r
2131                                         'dialog[A][dd|dt]' +\r
2132                                         'blockquote[A|cite][C]' +\r
2133                                         'ol[A|start|reversed][li]' +\r
2134                                         'ul[A][li]' +\r
2135                                         'li[A|value][C]' +\r
2136                                         'dl[A][dd|dt]' +\r
2137                                         'dt[A][B]' +\r
2138                                         'dd[A][C]' +\r
2139                                         'a[A|href|target|ping|rel|media|type][C]' +\r
2140                                         'em[A][B]' +\r
2141                                         'strong[A][B]' +\r
2142                                         'small[A][B]' +\r
2143                                         'cite[A][B]' +\r
2144                                         'q[A|cite][B]' +\r
2145                                         'dfn[A][B]' +\r
2146                                         'abbr[A][B]' +\r
2147                                         'code[A][B]' +\r
2148                                         'var[A][B]' +\r
2149                                         'samp[A][B]' +\r
2150                                         'kbd[A][B]' +\r
2151                                         'sub[A][B]' +\r
2152                                         'sup[A][B]' +\r
2153                                         'i[A][B]' +\r
2154                                         'b[A][B]' +\r
2155                                         'mark[A][B]' +\r
2156                                         'progress[A|value|max][B]' +\r
2157                                         'meter[A|value|min|max|low|high|optimum][B]' +\r
2158                                         'time[A|datetime][B]' +\r
2159                                         'ruby[A][B|rt|rp]' +\r
2160                                         'rt[A][B]' +\r
2161                                         'rp[A][B]' +\r
2162                                         'bdo[A][B]' +\r
2163                                         'span[A][B]' +\r
2164                                         'ins[A|cite|datetime][B]' +\r
2165                                         'del[A|cite|datetime][B]' +\r
2166                                         'figure[A][C|legend|figcaption]' +\r
2167                                         'figcaption[A][C]' +\r
2168                                         'img[A|alt|src|height|width|usemap|ismap][]' +\r
2169                                         'iframe[A|name|src|height|width|sandbox|seamless][]' +\r
2170                                         'embed[A|src|height|width|type][]' +\r
2171                                         'object[A|data|type|height|width|usemap|name|form|classid][param]' +\r
2172                                         'param[A|name|value][]' +\r
2173                                         'details[A|open][C|legend]' +\r
2174                                         'command[A|type|label|icon|disabled|checked|radiogroup][]' +\r
2175                                         'menu[A|type|label][C|li]' +\r
2176                                         'legend[A][C|B]' +\r
2177                                         'div[A][C]' +\r
2178                                         'source[A|src|type|media][]' +\r
2179                                         'audio[A|src|autobuffer|autoplay|loop|controls][source]' +\r
2180                                         'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +\r
2181                                         'hr[A][]' +\r
2182                                         'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +\r
2183                                         'fieldset[A|disabled|form|name][C|legend]' +\r
2184                                         'label[A|form|for][B]' +\r
2185                                         'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value][]' +\r
2186                                         'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +\r
2187                                         'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +\r
2188                                         'datalist[A][B|option]' +\r
2189                                         'optgroup[A|disabled|label][option]' +\r
2190                                         'option[A|disabled|selected|label|value][]' +\r
2191                                         'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +\r
2192                                         'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +\r
2193                                         'output[A|for|form|name][B]' +\r
2194                                         'canvas[A|width|height][]' +\r
2195                                         'map[A|name][B|C]' +\r
2196                                         'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +\r
2197                                         'mathml[A][]' +\r
2198                                         'svg[A][]' +\r
2199                                         'table[A|summary][caption|colgroup|thead|tfoot|tbody|tr]' +\r
2200                                         'caption[A][C]' +\r
2201                                         'colgroup[A|span][col]' +\r
2202                                         'col[A|span][]' +\r
2203                                         'thead[A][tr]' +\r
2204                                         'tfoot[A][tr]' +\r
2205                                         'tbody[A][tr]' +\r
2206                                         'tr[A][th|td]' +\r
2207                                         'th[A|headers|rowspan|colspan|scope][B]' +\r
2208                                         'td[A|headers|rowspan|colspan][C]'\r
2209                         );\r
2210                 }\r
2211 \r
2212                 return html5;\r
2213         };\r
2214 \r
2215         function getHTML4() {\r
2216                 var html4 = mapCache.html4;\r
2217 \r
2218                 if (!html4) {\r
2219                         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size\r
2220                         html4 = mapCache.html4 = unpack({\r
2221                                 Z : 'H|K|N|O|P',\r
2222                                 Y : 'X|form|R|Q',\r
2223                                 ZG : 'E|span|width|align|char|charoff|valign',\r
2224                                 X : 'p|T|div|U|W|isindex|fieldset|table',\r
2225                                 ZF : 'E|align|char|charoff|valign',\r
2226                                 W : 'pre|hr|blockquote|address|center|noframes',\r
2227                                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',\r
2228                                 ZD : '[E][S]',\r
2229                                 U : 'ul|ol|dl|menu|dir',\r
2230                                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',\r
2231                                 T : 'h1|h2|h3|h4|h5|h6',\r
2232                                 ZB : 'X|S|Q',\r
2233                                 S : 'R|P',\r
2234                                 ZA : 'a|G|J|M|O|P',\r
2235                                 R : 'a|H|K|N|O',\r
2236                                 Q : 'noscript|P',\r
2237                                 P : 'ins|del|script',\r
2238                                 O : 'input|select|textarea|label|button',\r
2239                                 N : 'M|L',\r
2240                                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',\r
2241                                 L : 'sub|sup',\r
2242                                 K : 'J|I',\r
2243                                 J : 'tt|i|b|u|s|strike',\r
2244                                 I : 'big|small|font|basefont',\r
2245                                 H : 'G|F',\r
2246                                 G : 'br|span|bdo',\r
2247                                 F : 'object|applet|img|map|iframe',\r
2248                                 E : 'A|B|C',\r
2249                                 D : 'accesskey|tabindex|onfocus|onblur',\r
2250                                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',\r
2251                                 B : 'lang|xml:lang|dir',\r
2252                                 A : 'id|class|style|title'\r
2253                         }, 'script[id|charset|type|language|src|defer|xml:space][]' + \r
2254                                 'style[B|id|type|media|title|xml:space][]' + \r
2255                                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + \r
2256                                 'param[id|name|value|valuetype|type][]' + \r
2257                                 'p[E|align][#|S]' + \r
2258                                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + \r
2259                                 'br[A|clear][]' + \r
2260                                 'span[E][#|S]' + \r
2261                                 'bdo[A|C|B][#|S]' + \r
2262                                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + \r
2263                                 'h1[E|align][#|S]' + \r
2264                                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + \r
2265                                 'map[B|C|A|name][X|form|Q|area]' + \r
2266                                 'h2[E|align][#|S]' + \r
2267                                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + \r
2268                                 'h3[E|align][#|S]' + \r
2269                                 'tt[E][#|S]' + \r
2270                                 'i[E][#|S]' + \r
2271                                 'b[E][#|S]' + \r
2272                                 'u[E][#|S]' + \r
2273                                 's[E][#|S]' + \r
2274                                 'strike[E][#|S]' + \r
2275                                 'big[E][#|S]' + \r
2276                                 'small[E][#|S]' + \r
2277                                 'font[A|B|size|color|face][#|S]' + \r
2278                                 'basefont[id|size|color|face][]' + \r
2279                                 'em[E][#|S]' + \r
2280                                 'strong[E][#|S]' + \r
2281                                 'dfn[E][#|S]' + \r
2282                                 'code[E][#|S]' + \r
2283                                 'q[E|cite][#|S]' + \r
2284                                 'samp[E][#|S]' + \r
2285                                 'kbd[E][#|S]' + \r
2286                                 'var[E][#|S]' + \r
2287                                 'cite[E][#|S]' + \r
2288                                 'abbr[E][#|S]' + \r
2289                                 'acronym[E][#|S]' + \r
2290                                 'sub[E][#|S]' + \r
2291                                 'sup[E][#|S]' + \r
2292                                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + \r
2293                                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + \r
2294                                 'optgroup[E|disabled|label][option]' + \r
2295                                 'option[E|selected|disabled|label|value][]' + \r
2296                                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + \r
2297                                 'label[E|for|accesskey|onfocus|onblur][#|S]' + \r
2298                                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + \r
2299                                 'h4[E|align][#|S]' + \r
2300                                 'ins[E|cite|datetime][#|Y]' + \r
2301                                 'h5[E|align][#|S]' + \r
2302                                 'del[E|cite|datetime][#|Y]' + \r
2303                                 'h6[E|align][#|S]' + \r
2304                                 'div[E|align][#|Y]' + \r
2305                                 'ul[E|type|compact][li]' + \r
2306                                 'li[E|type|value][#|Y]' + \r
2307                                 'ol[E|type|compact|start][li]' + \r
2308                                 'dl[E|compact][dt|dd]' + \r
2309                                 'dt[E][#|S]' + \r
2310                                 'dd[E][#|Y]' + \r
2311                                 'menu[E|compact][li]' + \r
2312                                 'dir[E|compact][li]' + \r
2313                                 'pre[E|width|xml:space][#|ZA]' + \r
2314                                 'hr[E|align|noshade|size|width][]' + \r
2315                                 'blockquote[E|cite][#|Y]' + \r
2316                                 'address[E][#|S|p]' + \r
2317                                 'center[E][#|Y]' + \r
2318                                 'noframes[E][#|Y]' + \r
2319                                 'isindex[A|B|prompt][]' + \r
2320                                 'fieldset[E][#|legend|Y]' + \r
2321                                 'legend[E|accesskey|align][#|S]' + \r
2322                                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + \r
2323                                 'caption[E|align][#|S]' + \r
2324                                 'col[ZG][]' + \r
2325                                 'colgroup[ZG][col]' + \r
2326                                 'thead[ZF][tr]' + \r
2327                                 'tr[ZF|bgcolor][th|td]' + \r
2328                                 'th[E|ZE][#|Y]' + \r
2329                                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + \r
2330                                 'noscript[E][#|Y]' + \r
2331                                 'td[E|ZE][#|Y]' + \r
2332                                 'tfoot[ZF][tr]' + \r
2333                                 'tbody[ZF][tr]' + \r
2334                                 'area[E|D|shape|coords|href|nohref|alt|target][]' + \r
2335                                 'base[id|href|target][]' + \r
2336                                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'\r
2337                         );\r
2338                 }\r
2339 \r
2340                 return html4;\r
2341         };\r
2342 \r
2343         tinymce.html.Schema = function(settings) {\r
2344                 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;\r
2345                 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};\r
2346 \r
2347                 // Creates an lookup table map object for the specified option or the default value\r
2348                 function createLookupTable(option, default_value, extend) {\r
2349                         var value = settings[option];\r
2350 \r
2351                         if (!value) {\r
2352                                 // Get cached default map or make it if needed\r
2353                                 value = mapCache[option];\r
2354 \r
2355                                 if (!value) {\r
2356                                         value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));\r
2357                                         value = tinymce.extend(value, extend);\r
2358 \r
2359                                         mapCache[option] = value;\r
2360                                 }\r
2361                         } else {\r
2362                                 // Create custom map\r
2363                                 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));\r
2364                         }\r
2365 \r
2366                         return value;\r
2367                 };\r
2368 \r
2369                 settings = settings || {};\r
2370                 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();\r
2371 \r
2372                 // Allow all elements and attributes if verify_html is set to false\r
2373                 if (settings.verify_html === false)\r
2374                         settings.valid_elements = '*[*]';\r
2375 \r
2376                 // Build styles list\r
2377                 if (settings.valid_styles) {\r
2378                         validStyles = {};\r
2379 \r
2380                         // Convert styles into a rule list\r
2381                         each(settings.valid_styles, function(value, key) {\r
2382                                 validStyles[key] = tinymce.explode(value);\r
2383                         });\r
2384                 }\r
2385 \r
2386                 // Setup map objects\r
2387                 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');\r
2388                 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li options p td tfoot th thead tr');\r
2389                 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source');\r
2390                 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');\r
2391                 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);\r
2392                 blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + \r
2393                                                 'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + \r
2394                                                 'noscript menu isindex samp header footer article section hgroup aside nav figure');\r
2395 \r
2396                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.\r
2397                 function patternToRegExp(str) {\r
2398                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');\r
2399                 };\r
2400 \r
2401                 // Parses the specified valid_elements string and adds to the current rules\r
2402                 // This function is a bit hard to read since it's heavily optimized for speed\r
2403                 function addValidElements(valid_elements) {\r
2404                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,\r
2405                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,\r
2406                                 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,\r
2407                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,\r
2408                                 hasPatternsRegExp = /[*?+]/;\r
2409 \r
2410                         if (valid_elements) {\r
2411                                 // Split valid elements into an array with rules\r
2412                                 valid_elements = split(valid_elements);\r
2413 \r
2414                                 if (elements['@']) {\r
2415                                         globalAttributes = elements['@'].attributes;\r
2416                                         globalAttributesOrder = elements['@'].attributesOrder;\r
2417                                 }\r
2418 \r
2419                                 // Loop all rules\r
2420                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {\r
2421                                         // Parse element rule\r
2422                                         matches = elementRuleRegExp.exec(valid_elements[ei]);\r
2423                                         if (matches) {\r
2424                                                 // Setup local names for matches\r
2425                                                 prefix = matches[1];\r
2426                                                 elementName = matches[2];\r
2427                                                 outputName = matches[3];\r
2428                                                 attrData = matches[4];\r
2429 \r
2430                                                 // Create new attributes and attributesOrder\r
2431                                                 attributes = {};\r
2432                                                 attributesOrder = [];\r
2433 \r
2434                                                 // Create the new element\r
2435                                                 element = {\r
2436                                                         attributes : attributes,\r
2437                                                         attributesOrder : attributesOrder\r
2438                                                 };\r
2439 \r
2440                                                 // Padd empty elements prefix\r
2441                                                 if (prefix === '#')\r
2442                                                         element.paddEmpty = true;\r
2443 \r
2444                                                 // Remove empty elements prefix\r
2445                                                 if (prefix === '-')\r
2446                                                         element.removeEmpty = true;\r
2447 \r
2448                                                 // Copy attributes from global rule into current rule\r
2449                                                 if (globalAttributes) {\r
2450                                                         for (key in globalAttributes)\r
2451                                                                 attributes[key] = globalAttributes[key];\r
2452 \r
2453                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);\r
2454                                                 }\r
2455 \r
2456                                                 // Attributes defined\r
2457                                                 if (attrData) {\r
2458                                                         attrData = split(attrData, '|');\r
2459                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {\r
2460                                                                 matches = attrRuleRegExp.exec(attrData[ai]);\r
2461                                                                 if (matches) {\r
2462                                                                         attr = {};\r
2463                                                                         attrType = matches[1];\r
2464                                                                         attrName = matches[2].replace(/::/g, ':');\r
2465                                                                         prefix = matches[3];\r
2466                                                                         value = matches[4];\r
2467 \r
2468                                                                         // Required\r
2469                                                                         if (attrType === '!') {\r
2470                                                                                 element.attributesRequired = element.attributesRequired || [];\r
2471                                                                                 element.attributesRequired.push(attrName);\r
2472                                                                                 attr.required = true;\r
2473                                                                         }\r
2474 \r
2475                                                                         // Denied from global\r
2476                                                                         if (attrType === '-') {\r
2477                                                                                 delete attributes[attrName];\r
2478                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);\r
2479                                                                                 continue;\r
2480                                                                         }\r
2481 \r
2482                                                                         // Default value\r
2483                                                                         if (prefix) {\r
2484                                                                                 // Default value\r
2485                                                                                 if (prefix === '=') {\r
2486                                                                                         element.attributesDefault = element.attributesDefault || [];\r
2487                                                                                         element.attributesDefault.push({name: attrName, value: value});\r
2488                                                                                         attr.defaultValue = value;\r
2489                                                                                 }\r
2490 \r
2491                                                                                 // Forced value\r
2492                                                                                 if (prefix === ':') {\r
2493                                                                                         element.attributesForced = element.attributesForced || [];\r
2494                                                                                         element.attributesForced.push({name: attrName, value: value});\r
2495                                                                                         attr.forcedValue = value;\r
2496                                                                                 }\r
2497 \r
2498                                                                                 // Required values\r
2499                                                                                 if (prefix === '<')\r
2500                                                                                         attr.validValues = makeMap(value, '?');\r
2501                                                                         }\r
2502 \r
2503                                                                         // Check for attribute patterns\r
2504                                                                         if (hasPatternsRegExp.test(attrName)) {\r
2505                                                                                 element.attributePatterns = element.attributePatterns || [];\r
2506                                                                                 attr.pattern = patternToRegExp(attrName);\r
2507                                                                                 element.attributePatterns.push(attr);\r
2508                                                                         } else {\r
2509                                                                                 // Add attribute to order list if it doesn't already exist\r
2510                                                                                 if (!attributes[attrName])\r
2511                                                                                         attributesOrder.push(attrName);\r
2512 \r
2513                                                                                 attributes[attrName] = attr;\r
2514                                                                         }\r
2515                                                                 }\r
2516                                                         }\r
2517                                                 }\r
2518 \r
2519                                                 // Global rule, store away these for later usage\r
2520                                                 if (!globalAttributes && elementName == '@') {\r
2521                                                         globalAttributes = attributes;\r
2522                                                         globalAttributesOrder = attributesOrder;\r
2523                                                 }\r
2524 \r
2525                                                 // Handle substitute elements such as b/strong\r
2526                                                 if (outputName) {\r
2527                                                         element.outputName = elementName;\r
2528                                                         elements[outputName] = element;\r
2529                                                 }\r
2530 \r
2531                                                 // Add pattern or exact element\r
2532                                                 if (hasPatternsRegExp.test(elementName)) {\r
2533                                                         element.pattern = patternToRegExp(elementName);\r
2534                                                         patternElements.push(element);\r
2535                                                 } else\r
2536                                                         elements[elementName] = element;\r
2537                                         }\r
2538                                 }\r
2539                         }\r
2540                 };\r
2541 \r
2542                 function setValidElements(valid_elements) {\r
2543                         elements = {};\r
2544                         patternElements = [];\r
2545 \r
2546                         addValidElements(valid_elements);\r
2547 \r
2548                         each(schemaItems, function(element, name) {\r
2549                                 children[name] = element.children;\r
2550                         });\r
2551                 };\r
2552 \r
2553                 // Adds custom non HTML elements to the schema\r
2554                 function addCustomElements(custom_elements) {\r
2555                         var customElementRegExp = /^(~)?(.+)$/;\r
2556 \r
2557                         if (custom_elements) {\r
2558                                 each(split(custom_elements), function(rule) {\r
2559                                         var matches = customElementRegExp.exec(rule),\r
2560                                                 inline = matches[1] === '~',\r
2561                                                 cloneName = inline ? 'span' : 'div',\r
2562                                                 name = matches[2];\r
2563 \r
2564                                         children[name] = children[cloneName];\r
2565                                         customElementsMap[name] = cloneName;\r
2566 \r
2567                                         // If it's not marked as inline then add it to valid block elements\r
2568                                         if (!inline)\r
2569                                                 blockElementsMap[name] = {};\r
2570 \r
2571                                         // Add custom elements at span/div positions\r
2572                                         each(children, function(element, child) {\r
2573                                                 if (element[cloneName])\r
2574                                                         element[name] = element[cloneName];\r
2575                                         });\r
2576                                 });\r
2577                         }\r
2578                 };\r
2579 \r
2580                 // Adds valid children to the schema object\r
2581                 function addValidChildren(valid_children) {\r
2582                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;\r
2583 \r
2584                         if (valid_children) {\r
2585                                 each(split(valid_children), function(rule) {\r
2586                                         var matches = childRuleRegExp.exec(rule), parent, prefix;\r
2587 \r
2588                                         if (matches) {\r
2589                                                 prefix = matches[1];\r
2590 \r
2591                                                 // Add/remove items from default\r
2592                                                 if (prefix)\r
2593                                                         parent = children[matches[2]];\r
2594                                                 else\r
2595                                                         parent = children[matches[2]] = {'#comment' : {}};\r
2596 \r
2597                                                 parent = children[matches[2]];\r
2598 \r
2599                                                 each(split(matches[3], '|'), function(child) {\r
2600                                                         if (prefix === '-')\r
2601                                                                 delete parent[child];\r
2602                                                         else\r
2603                                                                 parent[child] = {};\r
2604                                                 });\r
2605                                         }\r
2606                                 });\r
2607                         }\r
2608                 };\r
2609 \r
2610                 function getElementRule(name) {\r
2611                         var element = elements[name], i;\r
2612 \r
2613                         // Exact match found\r
2614                         if (element)\r
2615                                 return element;\r
2616 \r
2617                         // No exact match then try the patterns\r
2618                         i = patternElements.length;\r
2619                         while (i--) {\r
2620                                 element = patternElements[i];\r
2621 \r
2622                                 if (element.pattern.test(name))\r
2623                                         return element;\r
2624                         }\r
2625                 };\r
2626 \r
2627                 if (!settings.valid_elements) {\r
2628                         // No valid elements defined then clone the elements from the schema spec\r
2629                         each(schemaItems, function(element, name) {\r
2630                                 elements[name] = {\r
2631                                         attributes : element.attributes,\r
2632                                         attributesOrder : element.attributesOrder\r
2633                                 };\r
2634 \r
2635                                 children[name] = element.children;\r
2636                         });\r
2637 \r
2638                         // Switch these on HTML4\r
2639                         if (settings.schema != "html5") {\r
2640                                 each(split('strong/b,em/i'), function(item) {\r
2641                                         item = split(item, '/');\r
2642                                         elements[item[1]].outputName = item[0];\r
2643                                 });\r
2644                         }\r
2645 \r
2646                         // Add default alt attribute for images\r
2647                         elements.img.attributesDefault = [{name: 'alt', value: ''}];\r
2648 \r
2649                         // Remove these if they are empty by default\r
2650                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {\r
2651                                 if (elements[name]) {\r
2652                                         elements[name].removeEmpty = true;\r
2653                                 }\r
2654                         });\r
2655 \r
2656                         // Padd these by default\r
2657                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {\r
2658                                 elements[name].paddEmpty = true;\r
2659                         });\r
2660                 } else\r
2661                         setValidElements(settings.valid_elements);\r
2662 \r
2663                 addCustomElements(settings.custom_elements);\r
2664                 addValidChildren(settings.valid_children);\r
2665                 addValidElements(settings.extended_valid_elements);\r
2666 \r
2667                 // Todo: Remove this when we fix list handling to be valid\r
2668                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');\r
2669 \r
2670                 // Delete invalid elements\r
2671                 if (settings.invalid_elements) {\r
2672                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {\r
2673                                 if (elements[item])\r
2674                                         delete elements[item];\r
2675                         });\r
2676                 }\r
2677 \r
2678                 // If the user didn't allow span only allow internal spans\r
2679                 if (!getElementRule('span'))\r
2680                         addValidElements('span[!data-mce-type|*]');\r
2681 \r
2682                 self.children = children;\r
2683 \r
2684                 self.styles = validStyles;\r
2685 \r
2686                 self.getBoolAttrs = function() {\r
2687                         return boolAttrMap;\r
2688                 };\r
2689 \r
2690                 self.getBlockElements = function() {\r
2691                         return blockElementsMap;\r
2692                 };\r
2693 \r
2694                 self.getShortEndedElements = function() {\r
2695                         return shortEndedElementsMap;\r
2696                 };\r
2697 \r
2698                 self.getSelfClosingElements = function() {\r
2699                         return selfClosingElementsMap;\r
2700                 };\r
2701 \r
2702                 self.getNonEmptyElements = function() {\r
2703                         return nonEmptyElementsMap;\r
2704                 };\r
2705 \r
2706                 self.getWhiteSpaceElements = function() {\r
2707                         return whiteSpaceElementsMap;\r
2708                 };\r
2709 \r
2710                 self.isValidChild = function(name, child) {\r
2711                         var parent = children[name];\r
2712 \r
2713                         return !!(parent && parent[child]);\r
2714                 };\r
2715 \r
2716                 self.getElementRule = getElementRule;\r
2717 \r
2718                 self.getCustomElements = function() {\r
2719                         return customElementsMap;\r
2720                 };\r
2721 \r
2722                 self.addValidElements = addValidElements;\r
2723 \r
2724                 self.setValidElements = setValidElements;\r
2725 \r
2726                 self.addCustomElements = addCustomElements;\r
2727 \r
2728                 self.addValidChildren = addValidChildren;\r
2729         };\r
2730 })(tinymce);\r
2731 \r
2732 (function(tinymce) {\r
2733         tinymce.html.SaxParser = function(settings, schema) {\r
2734                 var self = this, noop = function() {};\r
2735 \r
2736                 settings = settings || {};\r
2737                 self.schema = schema = schema || new tinymce.html.Schema();\r
2738 \r
2739                 if (settings.fix_self_closing !== false)\r
2740                         settings.fix_self_closing = true;\r
2741 \r
2742                 // Add handler functions from settings and setup default handlers\r
2743                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {\r
2744                         if (name)\r
2745                                 self[name] = settings[name] || noop;\r
2746                 });\r
2747 \r
2748                 self.parse = function(html) {\r
2749                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,\r
2750                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,\r
2751                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,\r
2752                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;\r
2753 \r
2754                         function processEndTag(name) {\r
2755                                 var pos, i;\r
2756 \r
2757                                 // Find position of parent of the same type\r
2758                                 pos = stack.length;\r
2759                                 while (pos--) {\r
2760                                         if (stack[pos].name === name)\r
2761                                                 break;                                          \r
2762                                 }\r
2763 \r
2764                                 // Found parent\r
2765                                 if (pos >= 0) {\r
2766                                         // Close all the open elements\r
2767                                         for (i = stack.length - 1; i >= pos; i--) {\r
2768                                                 name = stack[i];\r
2769 \r
2770                                                 if (name.valid)\r
2771                                                         self.end(name.name);\r
2772                                         }\r
2773 \r
2774                                         // Remove the open elements from the stack\r
2775                                         stack.length = pos;\r
2776                                 }\r
2777                         };\r
2778 \r
2779                         function parseAttribute(match, name, value, val2, val3) {\r
2780                                 var attrRule, i;\r
2781 \r
2782                                 name = name.toLowerCase();\r
2783                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute\r
2784 \r
2785                                 // Validate name and value\r
2786                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {\r
2787                                         attrRule = validAttributesMap[name];\r
2788 \r
2789                                         // Find rule by pattern matching\r
2790                                         if (!attrRule && validAttributePatterns) {\r
2791                                                 i = validAttributePatterns.length;\r
2792                                                 while (i--) {\r
2793                                                         attrRule = validAttributePatterns[i];\r
2794                                                         if (attrRule.pattern.test(name))\r
2795                                                                 break;\r
2796                                                 }\r
2797 \r
2798                                                 // No rule matched\r
2799                                                 if (i === -1)\r
2800                                                         attrRule = null;\r
2801                                         }\r
2802 \r
2803                                         // No attribute rule found\r
2804                                         if (!attrRule)\r
2805                                                 return;\r
2806 \r
2807                                         // Validate value\r
2808                                         if (attrRule.validValues && !(value in attrRule.validValues))\r
2809                                                 return;\r
2810                                 }\r
2811 \r
2812                                 // Add attribute to list and map\r
2813                                 attrList.map[name] = value;\r
2814                                 attrList.push({\r
2815                                         name: name,\r
2816                                         value: value\r
2817                                 });\r
2818                         };\r
2819 \r
2820                         // Precompile RegExps and map objects\r
2821                         tokenRegExp = new RegExp('<(?:' +\r
2822                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment\r
2823                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA\r
2824                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE\r
2825                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI\r
2826                                 '(?:\\/([^>]+)>)|' + // End element\r
2827                                 '(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element\r
2828                         ')', 'g');\r
2829 \r
2830                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;\r
2831                         specialElements = {\r
2832                                 'script' : /<\/script[^>]*>/gi,\r
2833                                 'style' : /<\/style[^>]*>/gi,\r
2834                                 'noscript' : /<\/noscript[^>]*>/gi\r
2835                         };\r
2836 \r
2837                         // Setup lookup tables for empty elements and boolean attributes\r
2838                         shortEndedElements = schema.getShortEndedElements();\r
2839                         selfClosing = schema.getSelfClosingElements();\r
2840                         fillAttrsMap = schema.getBoolAttrs();\r
2841                         validate = settings.validate;\r
2842                         removeInternalElements = settings.remove_internals;\r
2843                         fixSelfClosing = settings.fix_self_closing;\r
2844                         isIE = tinymce.isIE;\r
2845                         invalidPrefixRegExp = /^:/;\r
2846 \r
2847                         while (matches = tokenRegExp.exec(html)) {\r
2848                                 // Text\r
2849                                 if (index < matches.index)\r
2850                                         self.text(decode(html.substr(index, matches.index - index)));\r
2851 \r
2852                                 if (value = matches[6]) { // End element\r
2853                                         value = value.toLowerCase();\r
2854 \r
2855                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
2856                                         if (isIE && invalidPrefixRegExp.test(value))\r
2857                                                 value = value.substr(1);\r
2858 \r
2859                                         processEndTag(value);\r
2860                                 } else if (value = matches[7]) { // Start element\r
2861                                         value = value.toLowerCase();\r
2862 \r
2863                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
2864                                         if (isIE && invalidPrefixRegExp.test(value))\r
2865                                                 value = value.substr(1);\r
2866 \r
2867                                         isShortEnded = value in shortEndedElements;\r
2868 \r
2869                                         // Is self closing tag for example an <li> after an open <li>\r
2870                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)\r
2871                                                 processEndTag(value);\r
2872 \r
2873                                         // Validate element\r
2874                                         if (!validate || (elementRule = schema.getElementRule(value))) {\r
2875                                                 isValidElement = true;\r
2876 \r
2877                                                 // Grab attributes map and patters when validation is enabled\r
2878                                                 if (validate) {\r
2879                                                         validAttributesMap = elementRule.attributes;\r
2880                                                         validAttributePatterns = elementRule.attributePatterns;\r
2881                                                 }\r
2882 \r
2883                                                 // Parse attributes\r
2884                                                 if (attribsValue = matches[8]) {\r
2885                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element\r
2886 \r
2887                                                         // If the element has internal attributes then remove it if we are told to do so\r
2888                                                         if (isInternalElement && removeInternalElements)\r
2889                                                                 isValidElement = false;\r
2890 \r
2891                                                         attrList = [];\r
2892                                                         attrList.map = {};\r
2893 \r
2894                                                         attribsValue.replace(attrRegExp, parseAttribute);\r
2895                                                 } else {\r
2896                                                         attrList = [];\r
2897                                                         attrList.map = {};\r
2898                                                 }\r
2899 \r
2900                                                 // Process attributes if validation is enabled\r
2901                                                 if (validate && !isInternalElement) {\r
2902                                                         attributesRequired = elementRule.attributesRequired;\r
2903                                                         attributesDefault = elementRule.attributesDefault;\r
2904                                                         attributesForced = elementRule.attributesForced;\r
2905 \r
2906                                                         // Handle forced attributes\r
2907                                                         if (attributesForced) {\r
2908                                                                 i = attributesForced.length;\r
2909                                                                 while (i--) {\r
2910                                                                         attr = attributesForced[i];\r
2911                                                                         name = attr.name;\r
2912                                                                         attrValue = attr.value;\r
2913 \r
2914                                                                         if (attrValue === '{$uid}')\r
2915                                                                                 attrValue = 'mce_' + idCount++;\r
2916 \r
2917                                                                         attrList.map[name] = attrValue;\r
2918                                                                         attrList.push({name: name, value: attrValue});\r
2919                                                                 }\r
2920                                                         }\r
2921 \r
2922                                                         // Handle default attributes\r
2923                                                         if (attributesDefault) {\r
2924                                                                 i = attributesDefault.length;\r
2925                                                                 while (i--) {\r
2926                                                                         attr = attributesDefault[i];\r
2927                                                                         name = attr.name;\r
2928 \r
2929                                                                         if (!(name in attrList.map)) {\r
2930                                                                                 attrValue = attr.value;\r
2931 \r
2932                                                                                 if (attrValue === '{$uid}')\r
2933                                                                                         attrValue = 'mce_' + idCount++;\r
2934 \r
2935                                                                                 attrList.map[name] = attrValue;\r
2936                                                                                 attrList.push({name: name, value: attrValue});\r
2937                                                                         }\r
2938                                                                 }\r
2939                                                         }\r
2940 \r
2941                                                         // Handle required attributes\r
2942                                                         if (attributesRequired) {\r
2943                                                                 i = attributesRequired.length;\r
2944                                                                 while (i--) {\r
2945                                                                         if (attributesRequired[i] in attrList.map)\r
2946                                                                                 break;\r
2947                                                                 }\r
2948 \r
2949                                                                 // None of the required attributes where found\r
2950                                                                 if (i === -1)\r
2951                                                                         isValidElement = false;\r
2952                                                         }\r
2953 \r
2954                                                         // Invalidate element if it's marked as bogus\r
2955                                                         if (attrList.map['data-mce-bogus'])\r
2956                                                                 isValidElement = false;\r
2957                                                 }\r
2958 \r
2959                                                 if (isValidElement)\r
2960                                                         self.start(value, attrList, isShortEnded);\r
2961                                         } else\r
2962                                                 isValidElement = false;\r
2963 \r
2964                                         // Treat script, noscript and style a bit different since they may include code that looks like elements\r
2965                                         if (endRegExp = specialElements[value]) {\r
2966                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;\r
2967 \r
2968                                                 if (matches = endRegExp.exec(html)) {\r
2969                                                         if (isValidElement)\r
2970                                                                 text = html.substr(index, matches.index - index);\r
2971 \r
2972                                                         index = matches.index + matches[0].length;\r
2973                                                 } else {\r
2974                                                         text = html.substr(index);\r
2975                                                         index = html.length;\r
2976                                                 }\r
2977 \r
2978                                                 if (isValidElement && text.length > 0)\r
2979                                                         self.text(text, true);\r
2980 \r
2981                                                 if (isValidElement)\r
2982                                                         self.end(value);\r
2983 \r
2984                                                 tokenRegExp.lastIndex = index;\r
2985                                                 continue;\r
2986                                         }\r
2987 \r
2988                                         // Push value on to stack\r
2989                                         if (!isShortEnded) {\r
2990                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)\r
2991                                                         stack.push({name: value, valid: isValidElement});\r
2992                                                 else if (isValidElement)\r
2993                                                         self.end(value);\r
2994                                         }\r
2995                                 } else if (value = matches[1]) { // Comment\r
2996                                         self.comment(value);\r
2997                                 } else if (value = matches[2]) { // CDATA\r
2998                                         self.cdata(value);\r
2999                                 } else if (value = matches[3]) { // DOCTYPE\r
3000                                         self.doctype(value);\r
3001                                 } else if (value = matches[4]) { // PI\r
3002                                         self.pi(value, matches[5]);\r
3003                                 }\r
3004 \r
3005                                 index = matches.index + matches[0].length;\r
3006                         }\r
3007 \r
3008                         // Text\r
3009                         if (index < html.length)\r
3010                                 self.text(decode(html.substr(index)));\r
3011 \r
3012                         // Close any open elements\r
3013                         for (i = stack.length - 1; i >= 0; i--) {\r
3014                                 value = stack[i];\r
3015 \r
3016                                 if (value.valid)\r
3017                                         self.end(value.name);\r
3018                         }\r
3019                 };\r
3020         }\r
3021 })(tinymce);\r
3022 \r
3023 (function(tinymce) {\r
3024         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {\r
3025                 '#text' : 3,\r
3026                 '#comment' : 8,\r
3027                 '#cdata' : 4,\r
3028                 '#pi' : 7,\r
3029                 '#doctype' : 10,\r
3030                 '#document-fragment' : 11\r
3031         };\r
3032 \r
3033         // Walks the tree left/right\r
3034         function walk(node, root_node, prev) {\r
3035                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';\r
3036 \r
3037                 // Walk into nodes if it has a start\r
3038                 if (node[startName])\r
3039                         return node[startName];\r
3040 \r
3041                 // Return the sibling if it has one\r
3042                 if (node !== root_node) {\r
3043                         sibling = node[siblingName];\r
3044 \r
3045                         if (sibling)\r
3046                                 return sibling;\r
3047 \r
3048                         // Walk up the parents to look for siblings\r
3049                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {\r
3050                                 sibling = parent[siblingName];\r
3051 \r
3052                                 if (sibling)\r
3053                                         return sibling;\r
3054                         }\r
3055                 }\r
3056         };\r
3057 \r
3058         function Node(name, type) {\r
3059                 this.name = name;\r
3060                 this.type = type;\r
3061 \r
3062                 if (type === 1) {\r
3063                         this.attributes = [];\r
3064                         this.attributes.map = {};\r
3065                 }\r
3066         }\r
3067 \r
3068         tinymce.extend(Node.prototype, {\r
3069                 replace : function(node) {\r
3070                         var self = this;\r
3071 \r
3072                         if (node.parent)\r
3073                                 node.remove();\r
3074 \r
3075                         self.insert(node, self);\r
3076                         self.remove();\r
3077 \r
3078                         return self;\r
3079                 },\r
3080 \r
3081                 attr : function(name, value) {\r
3082                         var self = this, attrs, i, undef;\r
3083 \r
3084                         if (typeof name !== "string") {\r
3085                                 for (i in name)\r
3086                                         self.attr(i, name[i]);\r
3087 \r
3088                                 return self;\r
3089                         }\r
3090 \r
3091                         if (attrs = self.attributes) {\r
3092                                 if (value !== undef) {\r
3093                                         // Remove attribute\r
3094                                         if (value === null) {\r
3095                                                 if (name in attrs.map) {\r
3096                                                         delete attrs.map[name];\r
3097 \r
3098                                                         i = attrs.length;\r
3099                                                         while (i--) {\r
3100                                                                 if (attrs[i].name === name) {\r
3101                                                                         attrs = attrs.splice(i, 1);\r
3102                                                                         return self;\r
3103                                                                 }\r
3104                                                         }\r
3105                                                 }\r
3106 \r
3107                                                 return self;\r
3108                                         }\r
3109 \r
3110                                         // Set attribute\r
3111                                         if (name in attrs.map) {\r
3112                                                 // Set attribute\r
3113                                                 i = attrs.length;\r
3114                                                 while (i--) {\r
3115                                                         if (attrs[i].name === name) {\r
3116                                                                 attrs[i].value = value;\r
3117                                                                 break;\r
3118                                                         }\r
3119                                                 }\r
3120                                         } else\r
3121                                                 attrs.push({name: name, value: value});\r
3122 \r
3123                                         attrs.map[name] = value;\r
3124 \r
3125                                         return self;\r
3126                                 } else {\r
3127                                         return attrs.map[name];\r
3128                                 }\r
3129                         }\r
3130                 },\r
3131 \r
3132                 clone : function() {\r
3133                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;\r
3134 \r
3135                         // Clone element attributes\r
3136                         if (selfAttrs = self.attributes) {\r
3137                                 cloneAttrs = [];\r
3138                                 cloneAttrs.map = {};\r
3139 \r
3140                                 for (i = 0, l = selfAttrs.length; i < l; i++) {\r
3141                                         selfAttr = selfAttrs[i];\r
3142 \r
3143                                         // Clone everything except id\r
3144                                         if (selfAttr.name !== 'id') {\r
3145                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};\r
3146                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;\r
3147                                         }\r
3148                                 }\r
3149 \r
3150                                 clone.attributes = cloneAttrs;\r
3151                         }\r
3152 \r
3153                         clone.value = self.value;\r
3154                         clone.shortEnded = self.shortEnded;\r
3155 \r
3156                         return clone;\r
3157                 },\r
3158 \r
3159                 wrap : function(wrapper) {\r
3160                         var self = this;\r
3161 \r
3162                         self.parent.insert(wrapper, self);\r
3163                         wrapper.append(self);\r
3164 \r
3165                         return self;\r
3166                 },\r
3167 \r
3168                 unwrap : function() {\r
3169                         var self = this, node, next;\r
3170 \r
3171                         for (node = self.firstChild; node; ) {\r
3172                                 next = node.next;\r
3173                                 self.insert(node, self, true);\r
3174                                 node = next;\r
3175                         }\r
3176 \r
3177                         self.remove();\r
3178                 },\r
3179 \r
3180                 remove : function() {\r
3181                         var self = this, parent = self.parent, next = self.next, prev = self.prev;\r
3182 \r
3183                         if (parent) {\r
3184                                 if (parent.firstChild === self) {\r
3185                                         parent.firstChild = next;\r
3186 \r
3187                                         if (next)\r
3188                                                 next.prev = null;\r
3189                                 } else {\r
3190                                         prev.next = next;\r
3191                                 }\r
3192 \r
3193                                 if (parent.lastChild === self) {\r
3194                                         parent.lastChild = prev;\r
3195 \r
3196                                         if (prev)\r
3197                                                 prev.next = null;\r
3198                                 } else {\r
3199                                         next.prev = prev;\r
3200                                 }\r
3201 \r
3202                                 self.parent = self.next = self.prev = null;\r
3203                         }\r
3204 \r
3205                         return self;\r
3206                 },\r
3207 \r
3208                 append : function(node) {\r
3209                         var self = this, last;\r
3210 \r
3211                         if (node.parent)\r
3212                                 node.remove();\r
3213 \r
3214                         last = self.lastChild;\r
3215                         if (last) {\r
3216                                 last.next = node;\r
3217                                 node.prev = last;\r
3218                                 self.lastChild = node;\r
3219                         } else\r
3220                                 self.lastChild = self.firstChild = node;\r
3221 \r
3222                         node.parent = self;\r
3223 \r
3224                         return node;\r
3225                 },\r
3226 \r
3227                 insert : function(node, ref_node, before) {\r
3228                         var parent;\r
3229 \r
3230                         if (node.parent)\r
3231                                 node.remove();\r
3232 \r
3233                         parent = ref_node.parent || this;\r
3234 \r
3235                         if (before) {\r
3236                                 if (ref_node === parent.firstChild)\r
3237                                         parent.firstChild = node;\r
3238                                 else\r
3239                                         ref_node.prev.next = node;\r
3240 \r
3241                                 node.prev = ref_node.prev;\r
3242                                 node.next = ref_node;\r
3243                                 ref_node.prev = node;\r
3244                         } else {\r
3245                                 if (ref_node === parent.lastChild)\r
3246                                         parent.lastChild = node;\r
3247                                 else\r
3248                                         ref_node.next.prev = node;\r
3249 \r
3250                                 node.next = ref_node.next;\r
3251                                 node.prev = ref_node;\r
3252                                 ref_node.next = node;\r
3253                         }\r
3254 \r
3255                         node.parent = parent;\r
3256 \r
3257                         return node;\r
3258                 },\r
3259 \r
3260                 getAll : function(name) {\r
3261                         var self = this, node, collection = [];\r
3262 \r
3263                         for (node = self.firstChild; node; node = walk(node, self)) {\r
3264                                 if (node.name === name)\r
3265                                         collection.push(node);\r
3266                         }\r
3267 \r
3268                         return collection;\r
3269                 },\r
3270 \r
3271                 empty : function() {\r
3272                         var self = this, nodes, i, node;\r
3273 \r
3274                         // Remove all children\r
3275                         if (self.firstChild) {\r
3276                                 nodes = [];\r
3277 \r
3278                                 // Collect the children\r
3279                                 for (node = self.firstChild; node; node = walk(node, self))\r
3280                                         nodes.push(node);\r
3281 \r
3282                                 // Remove the children\r
3283                                 i = nodes.length;\r
3284                                 while (i--) {\r
3285                                         node = nodes[i];\r
3286                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;\r
3287                                 }\r
3288                         }\r
3289 \r
3290                         self.firstChild = self.lastChild = null;\r
3291 \r
3292                         return self;\r
3293                 },\r
3294 \r
3295                 isEmpty : function(elements) {\r
3296                         var self = this, node = self.firstChild, i, name;\r
3297 \r
3298                         if (node) {\r
3299                                 do {\r
3300                                         if (node.type === 1) {\r
3301                                                 // Ignore bogus elements\r
3302                                                 if (node.attributes.map['data-mce-bogus'])\r
3303                                                         continue;\r
3304 \r
3305                                                 // Keep empty elements like <img />\r
3306                                                 if (elements[node.name])\r
3307                                                         return false;\r
3308 \r
3309                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>\r
3310                                                 i = node.attributes.length;\r
3311                                                 while (i--) {\r
3312                                                         name = node.attributes[i].name;\r
3313                                                         if (name === "name" || name.indexOf('data-') === 0)\r
3314                                                                 return false;\r
3315                                                 }\r
3316                                         }\r
3317 \r
3318                                         // Keep comments\r
3319                                         if (node.type === 8)\r
3320                                                 return false;\r
3321                                         \r
3322                                         // Keep non whitespace text nodes\r
3323                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))\r
3324                                                 return false;\r
3325                                 } while (node = walk(node, self));\r
3326                         }\r
3327 \r
3328                         return true;\r
3329                 },\r
3330 \r
3331                 walk : function(prev) {\r
3332                         return walk(this, null, prev);\r
3333                 }\r
3334         });\r
3335 \r
3336         tinymce.extend(Node, {\r
3337                 create : function(name, attrs) {\r
3338                         var node, attrName;\r
3339 \r
3340                         // Create node\r
3341                         node = new Node(name, typeLookup[name] || 1);\r
3342 \r
3343                         // Add attributes if needed\r
3344                         if (attrs) {\r
3345                                 for (attrName in attrs)\r
3346                                         node.attr(attrName, attrs[attrName]);\r
3347                         }\r
3348 \r
3349                         return node;\r
3350                 }\r
3351         });\r
3352 \r
3353         tinymce.html.Node = Node;\r
3354 })(tinymce);\r
3355 \r
3356 (function(tinymce) {\r
3357         var Node = tinymce.html.Node;\r
3358 \r
3359         tinymce.html.DomParser = function(settings, schema) {\r
3360                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};\r
3361 \r
3362                 settings = settings || {};\r
3363                 settings.validate = "validate" in settings ? settings.validate : true;\r
3364                 settings.root_name = settings.root_name || 'body';\r
3365                 self.schema = schema = schema || new tinymce.html.Schema();\r
3366 \r
3367                 function fixInvalidChildren(nodes) {\r
3368                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,\r
3369                                 childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;\r
3370 \r
3371                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');\r
3372                         nonEmptyElements = schema.getNonEmptyElements();\r
3373 \r
3374                         for (ni = 0; ni < nodes.length; ni++) {\r
3375                                 node = nodes[ni];\r
3376 \r
3377                                 // Already removed\r
3378                                 if (!node.parent)\r
3379                                         continue;\r
3380 \r
3381                                 // Get list of all parent nodes until we find a valid parent to stick the child into\r
3382                                 parents = [node];\r
3383                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)\r
3384                                         parents.push(parent);\r
3385 \r
3386                                 // Found a suitable parent\r
3387                                 if (parent && parents.length > 1) {\r
3388                                         // Reverse the array since it makes looping easier\r
3389                                         parents.reverse();\r
3390 \r
3391                                         // Clone the related parent and insert that after the moved node\r
3392                                         newParent = currentNode = self.filterNode(parents[0].clone());\r
3393 \r
3394                                         // Start cloning and moving children on the left side of the target node\r
3395                                         for (i = 0; i < parents.length - 1; i++) {\r
3396                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {\r
3397                                                         tempNode = self.filterNode(parents[i].clone());\r
3398                                                         currentNode.append(tempNode);\r
3399                                                 } else\r
3400                                                         tempNode = currentNode;\r
3401 \r
3402                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {\r
3403                                                         nextNode = childNode.next;\r
3404                                                         tempNode.append(childNode);\r
3405                                                         childNode = nextNode;\r
3406                                                 }\r
3407 \r
3408                                                 currentNode = tempNode;\r
3409                                         }\r
3410 \r
3411                                         if (!newParent.isEmpty(nonEmptyElements)) {\r
3412                                                 parent.insert(newParent, parents[0], true);\r
3413                                                 parent.insert(node, newParent);\r
3414                                         } else {\r
3415                                                 parent.insert(node, parents[0], true);\r
3416                                         }\r
3417 \r
3418                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>\r
3419                                         parent = parents[0];\r
3420                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {\r
3421                                                 parent.empty().remove();\r
3422                                         }\r
3423                                 } else if (node.parent) {\r
3424                                         // If it's an LI try to find a UL/OL for it or wrap it\r
3425                                         if (node.name === 'li') {\r
3426                                                 sibling = node.prev;\r
3427                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
3428                                                         sibling.append(node);\r
3429                                                         continue;\r
3430                                                 }\r
3431 \r
3432                                                 sibling = node.next;\r
3433                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
3434                                                         sibling.insert(node, sibling.firstChild, true);\r
3435                                                         continue;\r
3436                                                 }\r
3437 \r
3438                                                 node.wrap(self.filterNode(new Node('ul', 1)));\r
3439                                                 continue;\r
3440                                         }\r
3441 \r
3442                                         // Try wrapping the element in a DIV\r
3443                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {\r
3444                                                 node.wrap(self.filterNode(new Node('div', 1)));\r
3445                                         } else {\r
3446                                                 // We failed wrapping it, then remove or unwrap it\r
3447                                                 if (node.name === 'style' || node.name === 'script')\r
3448                                                         node.empty().remove();\r
3449                                                 else\r
3450                                                         node.unwrap();\r
3451                                         }\r
3452                                 }\r
3453                         }\r
3454                 };\r
3455 \r
3456                 self.filterNode = function(node) {\r
3457                         var i, name, list;\r
3458 \r
3459                         // Run element filters\r
3460                         if (name in nodeFilters) {\r
3461                                 list = matchedNodes[name];\r
3462 \r
3463                                 if (list)\r
3464                                         list.push(node);\r
3465                                 else\r
3466                                         matchedNodes[name] = [node];\r
3467                         }\r
3468 \r
3469                         // Run attribute filters\r
3470                         i = attributeFilters.length;\r
3471                         while (i--) {\r
3472                                 name = attributeFilters[i].name;\r
3473 \r
3474                                 if (name in node.attributes.map) {\r
3475                                         list = matchedAttributes[name];\r
3476 \r
3477                                         if (list)\r
3478                                                 list.push(node);\r
3479                                         else\r
3480                                                 matchedAttributes[name] = [node];\r
3481                                 }\r
3482                         }\r
3483 \r
3484                         return node;\r
3485                 };\r
3486 \r
3487                 self.addNodeFilter = function(name, callback) {\r
3488                         tinymce.each(tinymce.explode(name), function(name) {\r
3489                                 var list = nodeFilters[name];\r
3490 \r
3491                                 if (!list)\r
3492                                         nodeFilters[name] = list = [];\r
3493 \r
3494                                 list.push(callback);\r
3495                         });\r
3496                 };\r
3497 \r
3498                 self.addAttributeFilter = function(name, callback) {\r
3499                         tinymce.each(tinymce.explode(name), function(name) {\r
3500                                 var i;\r
3501 \r
3502                                 for (i = 0; i < attributeFilters.length; i++) {\r
3503                                         if (attributeFilters[i].name === name) {\r
3504                                                 attributeFilters[i].callbacks.push(callback);\r
3505                                                 return;\r
3506                                         }\r
3507                                 }\r
3508 \r
3509                                 attributeFilters.push({name: name, callbacks: [callback]});\r
3510                         });\r
3511                 };\r
3512 \r
3513                 self.parse = function(html, args) {\r
3514                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,\r
3515                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,\r
3516                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;\r
3517 \r
3518                         args = args || {};\r
3519                         matchedNodes = {};\r
3520                         matchedAttributes = {};\r
3521                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());\r
3522                         nonEmptyElements = schema.getNonEmptyElements();\r
3523                         children = schema.children;\r
3524                         validate = settings.validate;\r
3525                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;\r
3526 \r
3527                         whiteSpaceElements = schema.getWhiteSpaceElements();\r
3528                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;\r
3529                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;\r
3530                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;\r
3531                         isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;\r
3532 \r
3533                         function addRootBlocks() {\r
3534                                 var node = rootNode.firstChild, next, rootBlockNode;\r
3535 \r
3536                                 while (node) {\r
3537                                         next = node.next;\r
3538 \r
3539                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {\r
3540                                                 if (!rootBlockNode) {\r
3541                                                         // Create a new root block element\r
3542                                                         rootBlockNode = createNode(rootBlockName, 1);\r
3543                                                         rootNode.insert(rootBlockNode, node);\r
3544                                                         rootBlockNode.append(node);\r
3545                                                 } else\r
3546                                                         rootBlockNode.append(node);\r
3547                                         } else {\r
3548                                                 rootBlockNode = null;\r
3549                                         }\r
3550 \r
3551                                         node = next;\r
3552                                 };\r
3553                         };\r
3554 \r
3555                         function createNode(name, type) {\r
3556                                 var node = new Node(name, type), list;\r
3557 \r
3558                                 if (name in nodeFilters) {\r
3559                                         list = matchedNodes[name];\r
3560 \r
3561                                         if (list)\r
3562                                                 list.push(node);\r
3563                                         else\r
3564                                                 matchedNodes[name] = [node];\r
3565                                 }\r
3566 \r
3567                                 return node;\r
3568                         };\r
3569 \r
3570                         function removeWhitespaceBefore(node) {\r
3571                                 var textNode, textVal, sibling;\r
3572 \r
3573                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {\r
3574                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');\r
3575 \r
3576                                         if (textVal.length > 0) {\r
3577                                                 textNode.value = textVal;\r
3578                                                 textNode = textNode.prev;\r
3579                                         } else {\r
3580                                                 sibling = textNode.prev;\r
3581                                                 textNode.remove();\r
3582                                                 textNode = sibling;\r
3583                                         }\r
3584                                 }\r
3585                         };\r
3586 \r
3587                         parser = new tinymce.html.SaxParser({\r
3588                                 validate : validate,\r
3589                                 fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results\r
3590 \r
3591                                 cdata: function(text) {\r
3592                                         node.append(createNode('#cdata', 4)).value = text;\r
3593                                 },\r
3594 \r
3595                                 text: function(text, raw) {\r
3596                                         var textNode;\r
3597 \r
3598                                         // Trim all redundant whitespace on non white space elements\r
3599                                         if (!isInWhiteSpacePreservedElement) {\r
3600                                                 text = text.replace(allWhiteSpaceRegExp, ' ');\r
3601 \r
3602                                                 if (node.lastChild && blockElements[node.lastChild.name])\r
3603                                                         text = text.replace(startWhiteSpaceRegExp, '');\r
3604                                         }\r
3605 \r
3606                                         // Do we need to create the node\r
3607                                         if (text.length !== 0) {\r
3608                                                 textNode = createNode('#text', 3);\r
3609                                                 textNode.raw = !!raw;\r
3610                                                 node.append(textNode).value = text;\r
3611                                         }\r
3612                                 },\r
3613 \r
3614                                 comment: function(text) {\r
3615                                         node.append(createNode('#comment', 8)).value = text;\r
3616                                 },\r
3617 \r
3618                                 pi: function(name, text) {\r
3619                                         node.append(createNode(name, 7)).value = text;\r
3620                                         removeWhitespaceBefore(node);\r
3621                                 },\r
3622 \r
3623                                 doctype: function(text) {\r
3624                                         var newNode;\r
3625                 \r
3626                                         newNode = node.append(createNode('#doctype', 10));\r
3627                                         newNode.value = text;\r
3628                                         removeWhitespaceBefore(node);\r
3629                                 },\r
3630 \r
3631                                 start: function(name, attrs, empty) {\r
3632                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;\r
3633 \r
3634                                         elementRule = validate ? schema.getElementRule(name) : {};\r
3635                                         if (elementRule) {\r
3636                                                 newNode = createNode(elementRule.outputName || name, 1);\r
3637                                                 newNode.attributes = attrs;\r
3638                                                 newNode.shortEnded = empty;\r
3639 \r
3640                                                 node.append(newNode);\r
3641 \r
3642                                                 // Check if node is valid child of the parent node is the child is\r
3643                                                 // unknown we don't collect it since it's probably a custom element\r
3644                                                 parent = children[node.name];\r
3645                                                 if (parent && children[newNode.name] && !parent[newNode.name])\r
3646                                                         invalidChildren.push(newNode);\r
3647 \r
3648                                                 attrFiltersLen = attributeFilters.length;\r
3649                                                 while (attrFiltersLen--) {\r
3650                                                         attrName = attributeFilters[attrFiltersLen].name;\r
3651 \r
3652                                                         if (attrName in attrs.map) {\r
3653                                                                 list = matchedAttributes[attrName];\r
3654 \r
3655                                                                 if (list)\r
3656                                                                         list.push(newNode);\r
3657                                                                 else\r
3658                                                                         matchedAttributes[attrName] = [newNode];\r
3659                                                         }\r
3660                                                 }\r
3661 \r
3662                                                 // Trim whitespace before block\r
3663                                                 if (blockElements[name])\r
3664                                                         removeWhitespaceBefore(newNode);\r
3665 \r
3666                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />\r
3667                                                 if (!empty)\r
3668                                                         node = newNode;\r
3669 \r
3670                                                 // Check if we are inside a whitespace preserved element\r
3671                                                 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {\r
3672                                                         isInWhiteSpacePreservedElement = true;\r
3673                                                 }\r
3674                                         }\r
3675                                 },\r
3676 \r
3677                                 end: function(name) {\r
3678                                         var textNode, elementRule, text, sibling, tempNode;\r
3679 \r
3680                                         elementRule = validate ? schema.getElementRule(name) : {};\r
3681                                         if (elementRule) {\r
3682                                                 if (blockElements[name]) {\r
3683                                                         if (!isInWhiteSpacePreservedElement) {\r
3684                                                                 // Trim whitespace of the first node in a block\r
3685                                                                 textNode = node.firstChild;\r
3686                                                                 if (textNode && textNode.type === 3) {\r
3687                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
3688 \r
3689                                                                         // Any characters left after trim or should we remove it\r
3690                                                                         if (text.length > 0) {\r
3691                                                                                 textNode.value = text;\r
3692                                                                                 textNode = textNode.next;\r
3693                                                                         } else {\r
3694                                                                                 sibling = textNode.next;\r
3695                                                                                 textNode.remove();\r
3696                                                                                 textNode = sibling;\r
3697                                                                         }\r
3698 \r
3699                                                                         // Remove any pure whitespace siblings\r
3700                                                                         while (textNode && textNode.type === 3) {\r
3701                                                                                 text = textNode.value;\r
3702                                                                                 sibling = textNode.next;\r
3703 \r
3704                                                                                 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {\r
3705                                                                                         textNode.remove();\r
3706                                                                                         textNode = sibling;\r
3707                                                                                 }\r
3708 \r
3709                                                                                 textNode = sibling;\r
3710                                                                         }\r
3711                                                                 }\r
3712 \r
3713                                                                 // Trim whitespace of the last node in a block\r
3714                                                                 textNode = node.lastChild;\r
3715                                                                 if (textNode && textNode.type === 3) {\r
3716                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');\r
3717 \r
3718                                                                         // Any characters left after trim or should we remove it\r
3719                                                                         if (text.length > 0) {\r
3720                                                                                 textNode.value = text;\r
3721                                                                                 textNode = textNode.prev;\r
3722                                                                         } else {\r
3723                                                                                 sibling = textNode.prev;\r
3724                                                                                 textNode.remove();\r
3725                                                                                 textNode = sibling;\r
3726                                                                         }\r
3727 \r
3728                                                                         // Remove any pure whitespace siblings\r
3729                                                                         while (textNode && textNode.type === 3) {\r
3730                                                                                 text = textNode.value;\r
3731                                                                                 sibling = textNode.prev;\r
3732 \r
3733                                                                                 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {\r
3734                                                                                         textNode.remove();\r
3735                                                                                         textNode = sibling;\r
3736                                                                                 }\r
3737 \r
3738                                                                                 textNode = sibling;\r
3739                                                                         }\r
3740                                                                 }\r
3741                                                         }\r
3742 \r
3743                                                         // Trim start white space\r
3744                                                         textNode = node.prev;\r
3745                                                         if (textNode && textNode.type === 3) {\r
3746                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
3747 \r
3748                                                                 if (text.length > 0)\r
3749                                                                         textNode.value = text;\r
3750                                                                 else\r
3751                                                                         textNode.remove();\r
3752                                                         }\r
3753                                                 }\r
3754 \r
3755                                                 // Check if we exited a whitespace preserved element\r
3756                                                 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {\r
3757                                                         isInWhiteSpacePreservedElement = false;\r
3758                                                 }\r
3759 \r
3760                                                 // Handle empty nodes\r
3761                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {\r
3762                                                         if (node.isEmpty(nonEmptyElements)) {\r
3763                                                                 if (elementRule.paddEmpty)\r
3764                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';\r
3765                                                                 else {\r
3766                                                                         // Leave nodes that have a name like <a name="name">\r
3767                                                                         if (!node.attributes.map.name) {\r
3768                                                                                 tempNode = node.parent;\r
3769                                                                                 node.empty().remove();\r
3770                                                                                 node = tempNode;\r
3771                                                                                 return;\r
3772                                                                         }\r
3773                                                                 }\r
3774                                                         }\r
3775                                                 }\r
3776 \r
3777                                                 node = node.parent;\r
3778                                         }\r
3779                                 }\r
3780                         }, schema);\r
3781 \r
3782                         rootNode = node = new Node(args.context || settings.root_name, 11);\r
3783 \r
3784                         parser.parse(html);\r
3785 \r
3786                         // Fix invalid children or report invalid children in a contextual parsing\r
3787                         if (validate && invalidChildren.length) {\r
3788                                 if (!args.context)\r
3789                                         fixInvalidChildren(invalidChildren);\r
3790                                 else\r
3791                                         args.invalid = true;\r
3792                         }\r
3793 \r
3794                         // Wrap nodes in the root into block elements if the root is body\r
3795                         if (rootBlockName && rootNode.name == 'body')\r
3796                                 addRootBlocks();\r
3797 \r
3798                         // Run filters only when the contents is valid\r
3799                         if (!args.invalid) {\r
3800                                 // Run node filters\r
3801                                 for (name in matchedNodes) {\r
3802                                         list = nodeFilters[name];\r
3803                                         nodes = matchedNodes[name];\r
3804 \r
3805                                         // Remove already removed children\r
3806                                         fi = nodes.length;\r
3807                                         while (fi--) {\r
3808                                                 if (!nodes[fi].parent)\r
3809                                                         nodes.splice(fi, 1);\r
3810                                         }\r
3811 \r
3812                                         for (i = 0, l = list.length; i < l; i++)\r
3813                                                 list[i](nodes, name, args);\r
3814                                 }\r
3815 \r
3816                                 // Run attribute filters\r
3817                                 for (i = 0, l = attributeFilters.length; i < l; i++) {\r
3818                                         list = attributeFilters[i];\r
3819 \r
3820                                         if (list.name in matchedAttributes) {\r
3821                                                 nodes = matchedAttributes[list.name];\r
3822 \r
3823                                                 // Remove already removed children\r
3824                                                 fi = nodes.length;\r
3825                                                 while (fi--) {\r
3826                                                         if (!nodes[fi].parent)\r
3827                                                                 nodes.splice(fi, 1);\r
3828                                                 }\r
3829 \r
3830                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)\r
3831                                                         list.callbacks[fi](nodes, list.name, args);\r
3832                                         }\r
3833                                 }\r
3834                         }\r
3835 \r
3836                         return rootNode;\r
3837                 };\r
3838 \r
3839                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to\r
3840                 // make it possible to place the caret inside empty blocks. This logic tries to remove\r
3841                 // these elements and keep br elements that where intended to be there intact\r
3842                 if (settings.remove_trailing_brs) {\r
3843                         self.addNodeFilter('br', function(nodes, name) {\r
3844                                 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),\r
3845                                         nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;\r
3846 \r
3847                                 // Remove brs from body element as well\r
3848                                 blockElements.body = 1;\r
3849 \r
3850                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>\r
3851                                 for (i = 0; i < l; i++) {\r
3852                                         node = nodes[i];\r
3853                                         parent = node.parent;\r
3854 \r
3855                                         if (blockElements[node.parent.name] && node === parent.lastChild) {\r
3856                                                 // Loop all nodes to the left of the current node and check for other BR elements\r
3857                                                 // excluding bookmarks since they are invisible\r
3858                                                 prev = node.prev;\r
3859                                                 while (prev) {\r
3860                                                         prevName = prev.name;\r
3861 \r
3862                                                         // Ignore bookmarks\r
3863                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {\r
3864                                                                 // Found a non BR element\r
3865                                                                 if (prevName !== "br")\r
3866                                                                         break;\r
3867         \r
3868                                                                 // Found another br it's a <br><br> structure then don't remove anything\r
3869                                                                 if (prevName === 'br') {\r
3870                                                                         node = null;\r
3871                                                                         break;\r
3872                                                                 }\r
3873                                                         }\r
3874 \r
3875                                                         prev = prev.prev;\r
3876                                                 }\r
3877 \r
3878                                                 if (node) {\r
3879                                                         node.remove();\r
3880 \r
3881                                                         // Is the parent to be considered empty after we removed the BR\r
3882                                                         if (parent.isEmpty(nonEmptyElements)) {\r
3883                                                                 elementRule = schema.getElementRule(parent.name);\r
3884 \r
3885                                                                 // Remove or padd the element depending on schema rule\r
3886                                                                 if (elementRule) {\r
3887                                                                         if (elementRule.removeEmpty)\r
3888                                                                                 parent.remove();\r
3889                                                                         else if (elementRule.paddEmpty)\r
3890                                                                                 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';\r
3891                                                                 }\r
3892                                                         }\r
3893                                                 }\r
3894                                         } else {\r
3895                                                 // Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i>&nbsp;</i></b></p> \r
3896                                                 lastParent = node;\r
3897                                                 while (parent.firstChild === lastParent && parent.lastChild === lastParent) {\r
3898                                                         lastParent = parent;\r
3899 \r
3900                                                         if (blockElements[parent.name]) {\r
3901                                                                 break;\r
3902                                                         }\r
3903 \r
3904                                                         parent = parent.parent;\r
3905                                                 }\r
3906 \r
3907                                                 if (lastParent === parent) {\r
3908                                                         textNode = new tinymce.html.Node('#text', 3);\r
3909                                                         textNode.value = '\u00a0';\r
3910                                                         node.replace(textNode);\r
3911                                                 }\r
3912                                         }\r
3913                                 }\r
3914                         });\r
3915                 }\r
3916 \r
3917                 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.\r
3918                 if (!settings.allow_html_in_named_anchor) {\r
3919                         self.addAttributeFilter('name', function(nodes, name) {\r
3920                                 var i = nodes.length, sibling, prevSibling, parent, node;\r
3921 \r
3922                                 while (i--) {\r
3923                                         node = nodes[i];\r
3924                                         if (node.name === 'a' && node.firstChild) {\r
3925                                                 parent = node.parent;\r
3926 \r
3927                                                 // Move children after current node\r
3928                                                 sibling = node.lastChild;\r
3929                                                 do {\r
3930                                                         prevSibling = sibling.prev;\r
3931                                                         parent.insert(sibling, node);\r
3932                                                         sibling = prevSibling;\r
3933                                                 } while (sibling);\r
3934                                         }\r
3935                                 }\r
3936                         });\r
3937                 }\r
3938         }\r
3939 })(tinymce);\r
3940 \r
3941 tinymce.html.Writer = function(settings) {\r
3942         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;\r
3943 \r
3944         settings = settings || {};\r
3945         indent = settings.indent;\r
3946         indentBefore = tinymce.makeMap(settings.indent_before || '');\r
3947         indentAfter = tinymce.makeMap(settings.indent_after || '');\r
3948         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);\r
3949         htmlOutput = settings.element_format == "html";\r
3950 \r
3951         return {\r
3952                 start: function(name, attrs, empty) {\r
3953                         var i, l, attr, value;\r
3954 \r
3955                         if (indent && indentBefore[name] && html.length > 0) {\r
3956                                 value = html[html.length - 1];\r
3957 \r
3958                                 if (value.length > 0 && value !== '\n')\r
3959                                         html.push('\n');\r
3960                         }\r
3961 \r
3962                         html.push('<', name);\r
3963 \r
3964                         if (attrs) {\r
3965                                 for (i = 0, l = attrs.length; i < l; i++) {\r
3966                                         attr = attrs[i];\r
3967                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');\r
3968                                 }\r
3969                         }\r
3970 \r
3971                         if (!empty || htmlOutput)\r
3972                                 html[html.length] = '>';\r
3973                         else\r
3974                                 html[html.length] = ' />';\r
3975 \r
3976                         if (empty && indent && indentAfter[name] && html.length > 0) {\r
3977                                 value = html[html.length - 1];\r
3978 \r
3979                                 if (value.length > 0 && value !== '\n')\r
3980                                         html.push('\n');\r
3981                         }\r
3982                 },\r
3983 \r
3984                 end: function(name) {\r
3985                         var value;\r
3986 \r
3987                         /*if (indent && indentBefore[name] && html.length > 0) {\r
3988                                 value = html[html.length - 1];\r
3989 \r
3990                                 if (value.length > 0 && value !== '\n')\r
3991                                         html.push('\n');\r
3992                         }*/\r
3993 \r
3994                         html.push('</', name, '>');\r
3995 \r
3996                         if (indent && indentAfter[name] && html.length > 0) {\r
3997                                 value = html[html.length - 1];\r
3998 \r
3999                                 if (value.length > 0 && value !== '\n')\r
4000                                         html.push('\n');\r
4001                         }\r
4002                 },\r
4003 \r
4004                 text: function(text, raw) {\r
4005                         if (text.length > 0)\r
4006                                 html[html.length] = raw ? text : encode(text);\r
4007                 },\r
4008 \r
4009                 cdata: function(text) {\r
4010                         html.push('<![CDATA[', text, ']]>');\r
4011                 },\r
4012 \r
4013                 comment: function(text) {\r
4014                         html.push('<!--', text, '-->');\r
4015                 },\r
4016 \r
4017                 pi: function(name, text) {\r
4018                         if (text)\r
4019                                 html.push('<?', name, ' ', text, '?>');\r
4020                         else\r
4021                                 html.push('<?', name, '?>');\r
4022 \r
4023                         if (indent)\r
4024                                 html.push('\n');\r
4025                 },\r
4026 \r
4027                 doctype: function(text) {\r
4028                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');\r
4029                 },\r
4030 \r
4031                 reset: function() {\r
4032                         html.length = 0;\r
4033                 },\r
4034 \r
4035                 getContent: function() {\r
4036                         return html.join('').replace(/\n$/, '');\r
4037                 }\r
4038         };\r
4039 };\r
4040 \r
4041 (function(tinymce) {\r
4042         tinymce.html.Serializer = function(settings, schema) {\r
4043                 var self = this, writer = new tinymce.html.Writer(settings);\r
4044 \r
4045                 settings = settings || {};\r
4046                 settings.validate = "validate" in settings ? settings.validate : true;\r
4047 \r
4048                 self.schema = schema = schema || new tinymce.html.Schema();\r
4049                 self.writer = writer;\r
4050 \r
4051                 self.serialize = function(node) {\r
4052                         var handlers, validate;\r
4053 \r
4054                         validate = settings.validate;\r
4055 \r
4056                         handlers = {\r
4057                                 // #text\r
4058                                 3: function(node, raw) {\r
4059                                         writer.text(node.value, node.raw);\r
4060                                 },\r
4061 \r
4062                                 // #comment\r
4063                                 8: function(node) {\r
4064                                         writer.comment(node.value);\r
4065                                 },\r
4066 \r
4067                                 // Processing instruction\r
4068                                 7: function(node) {\r
4069                                         writer.pi(node.name, node.value);\r
4070                                 },\r
4071 \r
4072                                 // Doctype\r
4073                                 10: function(node) {\r
4074                                         writer.doctype(node.value);\r
4075                                 },\r
4076 \r
4077                                 // CDATA\r
4078                                 4: function(node) {\r
4079                                         writer.cdata(node.value);\r
4080                                 },\r
4081 \r
4082                                 // Document fragment\r
4083                                 11: function(node) {\r
4084                                         if ((node = node.firstChild)) {\r
4085                                                 do {\r
4086                                                         walk(node);\r
4087                                                 } while (node = node.next);\r
4088                                         }\r
4089                                 }\r
4090                         };\r
4091 \r
4092                         writer.reset();\r
4093 \r
4094                         function walk(node) {\r
4095                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;\r
4096 \r
4097                                 if (!handler) {\r
4098                                         name = node.name;\r
4099                                         isEmpty = node.shortEnded;\r
4100                                         attrs = node.attributes;\r
4101 \r
4102                                         // Sort attributes\r
4103                                         if (validate && attrs && attrs.length > 1) {\r
4104                                                 sortedAttrs = [];\r
4105                                                 sortedAttrs.map = {};\r
4106 \r
4107                                                 elementRule = schema.getElementRule(node.name);\r
4108                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {\r
4109                                                         attrName = elementRule.attributesOrder[i];\r
4110 \r
4111                                                         if (attrName in attrs.map) {\r
4112                                                                 attrValue = attrs.map[attrName];\r
4113                                                                 sortedAttrs.map[attrName] = attrValue;\r
4114                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
4115                                                         }\r
4116                                                 }\r
4117 \r
4118                                                 for (i = 0, l = attrs.length; i < l; i++) {\r
4119                                                         attrName = attrs[i].name;\r
4120 \r
4121                                                         if (!(attrName in sortedAttrs.map)) {\r
4122                                                                 attrValue = attrs.map[attrName];\r
4123                                                                 sortedAttrs.map[attrName] = attrValue;\r
4124                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
4125                                                         }\r
4126                                                 }\r
4127 \r
4128                                                 attrs = sortedAttrs;\r
4129                                         }\r
4130 \r
4131                                         writer.start(node.name, attrs, isEmpty);\r
4132 \r
4133                                         if (!isEmpty) {\r
4134                                                 if ((node = node.firstChild)) {\r
4135                                                         do {\r
4136                                                                 walk(node);\r
4137                                                         } while (node = node.next);\r
4138                                                 }\r
4139 \r
4140                                                 writer.end(name);\r
4141                                         }\r
4142                                 } else\r
4143                                         handler(node);\r
4144                         }\r
4145 \r
4146                         // Serialize element and treat all non elements as fragments\r
4147                         if (node.type == 1 && !settings.inner)\r
4148                                 walk(node);\r
4149                         else\r
4150                                 handlers[11](node);\r
4151 \r
4152                         return writer.getContent();\r
4153                 };\r
4154         }\r
4155 })(tinymce);\r
4156 \r
4157 // JSLint defined globals\r
4158 /*global tinymce:false, window:false */\r
4159 \r
4160 tinymce.dom = {};\r
4161 \r
4162 (function(namespace, expando) {\r
4163         var w3cEventModel = !!document.addEventListener;\r
4164 \r
4165         function addEvent(target, name, callback, capture) {\r
4166                 if (target.addEventListener) {\r
4167                         target.addEventListener(name, callback, capture || false);\r
4168                 } else if (target.attachEvent) {\r
4169                         target.attachEvent('on' + name, callback);\r
4170                 }\r
4171         }\r
4172 \r
4173         function removeEvent(target, name, callback, capture) {\r
4174                 if (target.removeEventListener) {\r
4175                         target.removeEventListener(name, callback, capture || false);\r
4176                 } else if (target.detachEvent) {\r
4177                         target.detachEvent('on' + name, callback);\r
4178                 }\r
4179         }\r
4180 \r
4181         function fix(original_event, data) {\r
4182                 var name, event = data || {};\r
4183 \r
4184                 // Dummy function that gets replaced on the delegation state functions\r
4185                 function returnFalse() {\r
4186                         return false;\r
4187                 }\r
4188 \r
4189                 // Dummy function that gets replaced on the delegation state functions\r
4190                 function returnTrue() {\r
4191                         return true;\r
4192                 }\r
4193 \r
4194                 // Copy all properties from the original event\r
4195                 for (name in original_event) {\r
4196                         // layerX/layerY is deprecated in Chrome and produces a warning\r
4197                         if (name !== "layerX" && name !== "layerY") {\r
4198                                 event[name] = original_event[name];\r
4199                         }\r
4200                 }\r
4201 \r
4202                 // Normalize target IE uses srcElement\r
4203                 if (!event.target) {\r
4204                         event.target = event.srcElement || document;\r
4205                 }\r
4206 \r
4207                 // Add preventDefault method\r
4208                 event.preventDefault = function() {\r
4209                         event.isDefaultPrevented = returnTrue;\r
4210 \r
4211                         // Execute preventDefault on the original event object\r
4212                         if (original_event) {\r
4213                                 if (original_event.preventDefault) {\r
4214                                         original_event.preventDefault();\r
4215                                 } else {\r
4216                                         original_event.returnValue = false; // IE\r
4217                                 }\r
4218                         }\r
4219                 };\r
4220 \r
4221                 // Add stopPropagation\r
4222                 event.stopPropagation = function() {\r
4223                         event.isPropagationStopped = returnTrue;\r
4224 \r
4225                         // Execute stopPropagation on the original event object\r
4226                         if (original_event) {\r
4227                                 if (original_event.stopPropagation) {\r
4228                                         original_event.stopPropagation();\r
4229                                 } else {\r
4230                                         original_event.cancelBubble = true; // IE\r
4231                                 }\r
4232                          }\r
4233                 };\r
4234 \r
4235                 // Add stopImmediatePropagation\r
4236                 event.stopImmediatePropagation = function() {\r
4237                         event.isImmediatePropagationStopped = returnTrue;\r
4238                         event.stopPropagation();\r
4239                 };\r
4240 \r
4241                 // Add event delegation states\r
4242                 if (!event.isDefaultPrevented) {\r
4243                         event.isDefaultPrevented = returnFalse;\r
4244                         event.isPropagationStopped = returnFalse;\r
4245                         event.isImmediatePropagationStopped = returnFalse;\r
4246                 }\r
4247 \r
4248                 return event;\r
4249         }\r
4250 \r
4251         function bindOnReady(win, callback, event_utils) {\r
4252                 var doc = win.document, event = {type: 'ready'};\r
4253 \r
4254                 // Gets called when the DOM is ready\r
4255                 function readyHandler() {\r
4256                         if (!event_utils.domLoaded) {\r
4257                                 event_utils.domLoaded = true;\r
4258                                 callback(event);\r
4259                         }\r
4260                 }\r
4261 \r
4262                 // Use W3C method\r
4263                 if (w3cEventModel) {\r
4264                         addEvent(win, 'DOMContentLoaded', readyHandler);\r
4265                 } else {\r
4266                         // Use IE method\r
4267                         addEvent(doc, "readystatechange", function() {\r
4268                                 if (doc.readyState === "complete") {\r
4269                                         removeEvent(doc, "readystatechange", arguments.callee);\r
4270                                         readyHandler();\r
4271                                 }\r
4272                         });\r
4273 \r
4274                         // Wait until we can scroll, when we can the DOM is initialized\r
4275                         if (doc.documentElement.doScroll && win === win.top) {\r
4276                                 (function() {\r
4277                                         try {\r
4278                                                 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.\r
4279                                                 // http://javascript.nwbox.com/IEContentLoaded/\r
4280                                                 doc.documentElement.doScroll("left");\r
4281                                         } catch (ex) {\r
4282                                                 setTimeout(arguments.callee, 0);\r
4283                                                 return;\r
4284                                         }\r
4285 \r
4286                                         readyHandler();\r
4287                                 })();\r
4288                         }\r
4289                 }\r
4290 \r
4291                 // Fallback if any of the above methods should fail for some odd reason\r
4292                 addEvent(win, 'load', readyHandler);\r
4293         }\r
4294 \r
4295         function EventUtils(proxy) {\r
4296                 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;\r
4297 \r
4298                 hasMouseEnterLeave = "onmouseenter" in document.documentElement;\r
4299                 hasFocusIn = "onfocusin" in document.documentElement;\r
4300                 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};\r
4301                 count = 1;\r
4302 \r
4303                 // State if the DOMContentLoaded was executed or not\r
4304                 self.domLoaded = false;\r
4305                 self.events = events;\r
4306 \r
4307                 function executeHandlers(evt, id) {\r
4308                         var callbackList, i, l, callback;\r
4309 \r
4310                         callbackList = events[id][evt.type];\r
4311                         if (callbackList) {\r
4312                                 for (i = 0, l = callbackList.length; i < l; i++) {\r
4313                                         callback = callbackList[i];\r
4314                                         \r
4315                                         // Check if callback exists might be removed if a unbind is called inside the callback\r
4316                                         if (callback && callback.func.call(callback.scope, evt) === false) {\r
4317                                                 evt.preventDefault();\r
4318                                         }\r
4319 \r
4320                                         // Should we stop propagation to immediate listeners\r
4321                                         if (evt.isImmediatePropagationStopped()) {\r
4322                                                 return;\r
4323                                         }\r
4324                                 }\r
4325                         }\r
4326                 }\r
4327 \r
4328                 self.bind = function(target, names, callback, scope) {\r
4329                         var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;\r
4330 \r
4331                         // Native event handler function patches the event and executes the callbacks for the expando\r
4332                         function defaultNativeHandler(evt) {\r
4333                                 executeHandlers(fix(evt || win.event), id);\r
4334                         }\r
4335 \r
4336                         // Don't bind to text nodes or comments\r
4337                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4338                                 return;\r
4339                         }\r
4340 \r
4341                         // Create or get events id for the target\r
4342                         if (!target[expando]) {\r
4343                                 id = count++;\r
4344                                 target[expando] = id;\r
4345                                 events[id] = {};\r
4346                         } else {\r
4347                                 id = target[expando];\r
4348 \r
4349                                 if (!events[id]) {\r
4350                                         events[id] = {};\r
4351                                 }\r
4352                         }\r
4353 \r
4354                         // Setup the specified scope or use the target as a default\r
4355                         scope = scope || target;\r
4356 \r
4357                         // Split names and bind each event, enables you to bind multiple events with one call\r
4358                         names = names.split(' ');\r
4359                         i = names.length;\r
4360                         while (i--) {\r
4361                                 name = names[i];\r
4362                                 nativeHandler = defaultNativeHandler;\r
4363                                 fakeName = capture = false;\r
4364 \r
4365                                 // Use ready instead of DOMContentLoaded\r
4366                                 if (name === "DOMContentLoaded") {\r
4367                                         name = "ready";\r
4368                                 }\r
4369 \r
4370                                 // DOM is already ready\r
4371                                 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {\r
4372                                         self.domLoaded = true;\r
4373                                         callback.call(scope, fix({type: name}));\r
4374                                         continue;\r
4375                                 }\r
4376 \r
4377                                 // Handle mouseenter/mouseleaver\r
4378                                 if (!hasMouseEnterLeave) {\r
4379                                         fakeName = mouseEnterLeave[name];\r
4380 \r
4381                                         if (fakeName) {\r
4382                                                 nativeHandler = function(evt) {\r
4383                                                         var current, related;\r
4384 \r
4385                                                         current = evt.currentTarget;\r
4386                                                         related = evt.relatedTarget;\r
4387 \r
4388                                                         // Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element\r
4389                                                         if (related && current.contains) {\r
4390                                                                 // Use contains for performance\r
4391                                                                 related = current.contains(related);\r
4392                                                         } else {\r
4393                                                                 while (related && related !== current) {\r
4394                                                                         related = related.parentNode;\r
4395                                                                 }\r
4396                                                         }\r
4397 \r
4398                                                         // Fire fake event\r
4399                                                         if (!related) {\r
4400                                                                 evt = fix(evt || win.event);\r
4401                                                                 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';\r
4402                                                                 evt.target = current;\r
4403                                                                 executeHandlers(evt, id);\r
4404                                                         }\r
4405                                                 };\r
4406                                         }\r
4407                                 }\r
4408 \r
4409                                 // Fake bubbeling of focusin/focusout\r
4410                                 if (!hasFocusIn && (name === "focusin" || name === "focusout")) {\r
4411                                         capture = true;\r
4412                                         fakeName = name === "focusin" ? "focus" : "blur";\r
4413                                         nativeHandler = function(evt) {\r
4414                                                 evt = fix(evt || win.event);\r
4415                                                 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';\r
4416                                                 executeHandlers(evt, id);\r
4417                                         };\r
4418                                 }\r
4419 \r
4420                                 // Setup callback list and bind native event\r
4421                                 callbackList = events[id][name];\r
4422                                 if (!callbackList) {\r
4423                                         events[id][name] = callbackList = [{func: callback, scope: scope}];\r
4424                                         callbackList.fakeName = fakeName;\r
4425                                         callbackList.capture = capture;\r
4426 \r
4427                                         // Add the nativeHandler to the callback list so that we can later unbind it\r
4428                                         callbackList.nativeHandler = nativeHandler;\r
4429                                         if (!w3cEventModel) {\r
4430                                                 callbackList.proxyHandler = proxy(id);\r
4431                                         }\r
4432 \r
4433                                         // Check if the target has native events support\r
4434                                         if (name === "ready") {\r
4435                                                 bindOnReady(target, nativeHandler, self);\r
4436                                         } else {\r
4437                                                 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);\r
4438                                         }\r
4439                                 } else {\r
4440                                         // If it already has an native handler then just push the callback\r
4441                                         callbackList.push({func: callback, scope: scope});\r
4442                                 }\r
4443                         }\r
4444 \r
4445                         target = callbackList = 0; // Clean memory for IE\r
4446 \r
4447                         return callback;\r
4448                 };\r
4449 \r
4450                 self.unbind = function(target, names, callback) {\r
4451                         var id, callbackList, i, ci, name, eventMap;\r
4452 \r
4453                         // Don't bind to text nodes or comments\r
4454                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4455                                 return self;\r
4456                         }\r
4457 \r
4458                         // Unbind event or events if the target has the expando\r
4459                         id = target[expando];\r
4460                         if (id) {\r
4461                                 eventMap = events[id];\r
4462 \r
4463                                 // Specific callback\r
4464                                 if (names) {\r
4465                                         names = names.split(' ');\r
4466                                         i = names.length;\r
4467                                         while (i--) {\r
4468                                                 name = names[i];\r
4469                                                 callbackList = eventMap[name];\r
4470 \r
4471                                                 // Unbind the event if it exists in the map\r
4472                                                 if (callbackList) {\r
4473                                                         // Remove specified callback\r
4474                                                         if (callback) {\r
4475                                                                 ci = callbackList.length;\r
4476                                                                 while (ci--) {\r
4477                                                                         if (callbackList[ci].func === callback) {\r
4478                                                                                 callbackList.splice(ci, 1);\r
4479                                                                         }\r
4480                                                                 }\r
4481                                                         }\r
4482 \r
4483                                                         // Remove all callbacks if there isn't a specified callback or there is no callbacks left\r
4484                                                         if (!callback || callbackList.length === 0) {\r
4485                                                                 delete eventMap[name];\r
4486                                                                 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);\r
4487                                                         }\r
4488                                                 }\r
4489                                         }\r
4490                                 } else {\r
4491                                         // All events for a specific element\r
4492                                         for (name in eventMap) {\r
4493                                                 callbackList = eventMap[name];\r
4494                                                 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);\r
4495                                         }\r
4496 \r
4497                                         eventMap = {};\r
4498                                 }\r
4499 \r
4500                                 // Check if object is empty, if it isn't then we won't remove the expando map\r
4501                                 for (name in eventMap) {\r
4502                                         return self;\r
4503                                 }\r
4504 \r
4505                                 // Delete event object\r
4506                                 delete events[id];\r
4507 \r
4508                                 // Remove expando from target\r
4509                                 try {\r
4510                                         // IE will fail here since it can't delete properties from window\r
4511                                         delete target[expando];\r
4512                                 } catch (ex) {\r
4513                                         // IE will set it to null\r
4514                                         target[expando] = null;\r
4515                                 }\r
4516                         }\r
4517 \r
4518                         return self;\r
4519                 };\r
4520 \r
4521                 self.fire = function(target, name, args) {\r
4522                         var id, event;\r
4523 \r
4524                         // Don't bind to text nodes or comments\r
4525                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4526                                 return self;\r
4527                         }\r
4528 \r
4529                         // Build event object by patching the args\r
4530                         event = fix(null, args);\r
4531                         event.type = name;\r
4532 \r
4533                         do {\r
4534                                 // Found an expando that means there is listeners to execute\r
4535                                 id = target[expando];\r
4536                                 if (id) {\r
4537                                         executeHandlers(event, id);\r
4538                                 }\r
4539 \r
4540                                 // Walk up the DOM\r
4541                                 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;\r
4542                         } while (target && !event.isPropagationStopped());\r
4543 \r
4544                         return self;\r
4545                 };\r
4546 \r
4547                 self.clean = function(target) {\r
4548                         var i, children, unbind = self.unbind;\r
4549         \r
4550                         // Don't bind to text nodes or comments\r
4551                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4552                                 return self;\r
4553                         }\r
4554 \r
4555                         // Unbind any element on the specificed target\r
4556                         if (target[expando]) {\r
4557                                 unbind(target);\r
4558                         }\r
4559 \r
4560                         // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children\r
4561                         if (!target.getElementsByTagName) {\r
4562                                 target = target.document;\r
4563                         }\r
4564 \r
4565                         // Remove events from each child element\r
4566                         if (target && target.getElementsByTagName) {\r
4567                                 unbind(target);\r
4568 \r
4569                                 children = target.getElementsByTagName('*');\r
4570                                 i = children.length;\r
4571                                 while (i--) {\r
4572                                         target = children[i];\r
4573 \r
4574                                         if (target[expando]) {\r
4575                                                 unbind(target);\r
4576                                         }\r
4577                                 }\r
4578                         }\r
4579 \r
4580                         return self;\r
4581                 };\r
4582 \r
4583                 self.callNativeHandler = function(id, evt) {\r
4584                         if (events) {\r
4585                                 events[id][evt.type].nativeHandler(evt);\r
4586                         }\r
4587                 };\r
4588 \r
4589                 self.destory = function() {\r
4590                         events = {};\r
4591                 };\r
4592 \r
4593                 // Legacy function calls\r
4594 \r
4595                 self.add = function(target, events, func, scope) {\r
4596                         // Old API supported direct ID assignment\r
4597                         if (typeof(target) === "string") {\r
4598                                 target = document.getElementById(target);\r
4599                         }\r
4600 \r
4601                         // Old API supported multiple targets\r
4602                         if (target && target instanceof Array) {\r
4603                                 var i = target;\r
4604 \r
4605                                 while (i--) {\r
4606                                         self.add(target[i], events, func, scope);\r
4607                                 }\r
4608 \r
4609                                 return;\r
4610                         }\r
4611 \r
4612                         // Old API called ready init\r
4613                         if (events === "init") {\r
4614                                 events = "ready";\r
4615                         }\r
4616 \r
4617                         return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);\r
4618                 };\r
4619 \r
4620                 self.remove = function(target, events, func, scope) {\r
4621                         if (!target) {\r
4622                                 return self;\r
4623                         }\r
4624 \r
4625                         // Old API supported direct ID assignment\r
4626                         if (typeof(target) === "string") {\r
4627                                 target = document.getElementById(target);\r
4628                         }\r
4629 \r
4630                         // Old API supported multiple targets\r
4631                         if (target instanceof Array) {\r
4632                                 var i = target.length;\r
4633 \r
4634                                 while (i--) {\r
4635                                         self.remove(target[i], events, func, scope);\r
4636                                 }\r
4637 \r
4638                                 return self;\r
4639                         }\r
4640 \r
4641                         return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);\r
4642                 };\r
4643 \r
4644                 self.clear = function(target) {\r
4645                         // Old API supported direct ID assignment\r
4646                         if (typeof(target) === "string") {\r
4647                                 target = document.getElementById(target);\r
4648                         }\r
4649 \r
4650                         return self.clean(target);\r
4651                 };\r
4652 \r
4653                 self.cancel = function(e) {\r
4654                         if (e) {\r
4655                                 self.prevent(e);\r
4656                                 self.stop(e);\r
4657                         }\r
4658 \r
4659                         return false;\r
4660                 };\r
4661 \r
4662                 self.prevent = function(e) {\r
4663                         e.preventDefault();\r
4664 \r
4665                         return false;\r
4666                 };\r
4667 \r
4668                 self.stop = function(e) {\r
4669                         e.stopPropagation();\r
4670 \r
4671                         return false;\r
4672                 };\r
4673         }\r
4674 \r
4675         namespace.EventUtils = EventUtils;\r
4676 \r
4677         namespace.Event = new EventUtils(function(id) {\r
4678                 return function(evt) {\r
4679                         tinymce.dom.Event.callNativeHandler(id, evt);\r
4680                 };\r
4681         });\r
4682 \r
4683         // Bind ready event when tinymce script is loaded\r
4684         namespace.Event.bind(window, 'ready', function() {});\r
4685 \r
4686         namespace = 0;\r
4687 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando\r
4688 \r
4689 tinymce.dom.TreeWalker = function(start_node, root_node) {\r
4690         var node = start_node;\r
4691 \r
4692         function findSibling(node, start_name, sibling_name, shallow) {\r
4693                 var sibling, parent;\r
4694 \r
4695                 if (node) {\r
4696                         // Walk into nodes if it has a start\r
4697                         if (!shallow && node[start_name])\r
4698                                 return node[start_name];\r
4699 \r
4700                         // Return the sibling if it has one\r
4701                         if (node != root_node) {\r
4702                                 sibling = node[sibling_name];\r
4703                                 if (sibling)\r
4704                                         return sibling;\r
4705 \r
4706                                 // Walk up the parents to look for siblings\r
4707                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {\r
4708                                         sibling = parent[sibling_name];\r
4709                                         if (sibling)\r
4710                                                 return sibling;\r
4711                                 }\r
4712                         }\r
4713                 }\r
4714         };\r
4715 \r
4716         this.current = function() {\r
4717                 return node;\r
4718         };\r
4719 \r
4720         this.next = function(shallow) {\r
4721                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));\r
4722         };\r
4723 \r
4724         this.prev = function(shallow) {\r
4725                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));\r
4726         };\r
4727 };\r
4728 \r
4729 (function(tinymce) {\r
4730         // Shorten names\r
4731         var each = tinymce.each,\r
4732                 is = tinymce.is,\r
4733                 isWebKit = tinymce.isWebKit,\r
4734                 isIE = tinymce.isIE,\r
4735                 Entities = tinymce.html.Entities,\r
4736                 simpleSelectorRe = /^([a-z0-9],?)+$/i,\r
4737                 whiteSpaceRegExp = /^[ \t\r\n]*$/;\r
4738 \r
4739         tinymce.create('tinymce.dom.DOMUtils', {\r
4740                 doc : null,\r
4741                 root : null,\r
4742                 files : null,\r
4743                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,\r
4744                 props : {\r
4745                         "for" : "htmlFor",\r
4746                         "class" : "className",\r
4747                         className : "className",\r
4748                         checked : "checked",\r
4749                         disabled : "disabled",\r
4750                         maxlength : "maxLength",\r
4751                         readonly : "readOnly",\r
4752                         selected : "selected",\r
4753                         value : "value",\r
4754                         id : "id",\r
4755                         name : "name",\r
4756                         type : "type"\r
4757                 },\r
4758 \r
4759                 DOMUtils : function(d, s) {\r
4760                         var t = this, globalStyle, name, blockElementsMap;\r
4761 \r
4762                         t.doc = d;\r
4763                         t.win = window;\r
4764                         t.files = {};\r
4765                         t.cssFlicker = false;\r
4766                         t.counter = 0;\r
4767                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;\r
4768                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;\r
4769                         t.hasOuterHTML = "outerHTML" in d.createElement("a");\r
4770 \r
4771                         t.settings = s = tinymce.extend({\r
4772                                 keep_values : false,\r
4773                                 hex_colors : 1\r
4774                         }, s);\r
4775                         \r
4776                         t.schema = s.schema;\r
4777                         t.styles = new tinymce.html.Styles({\r
4778                                 url_converter : s.url_converter,\r
4779                                 url_converter_scope : s.url_converter_scope\r
4780                         }, s.schema);\r
4781 \r
4782                         // Fix IE6SP2 flicker and check it failed for pre SP2\r
4783                         if (tinymce.isIE6) {\r
4784                                 try {\r
4785                                         d.execCommand('BackgroundImageCache', false, true);\r
4786                                 } catch (e) {\r
4787                                         t.cssFlicker = true;\r
4788                                 }\r
4789                         }\r
4790 \r
4791                         t.fixDoc(d);\r
4792                         t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;\r
4793                         tinymce.addUnload(t.destroy, t);\r
4794                         blockElementsMap = s.schema ? s.schema.getBlockElements() : {};\r
4795 \r
4796                         t.isBlock = function(node) {\r
4797                                 // This function is called in module pattern style since it might be executed with the wrong this scope\r
4798                                 var type = node.nodeType;\r
4799 \r
4800                                 // If it's a node then check the type and use the nodeName\r
4801                                 if (type)\r
4802                                         return !!(type === 1 && blockElementsMap[node.nodeName]);\r
4803 \r
4804                                 return !!blockElementsMap[node];\r
4805                         };\r
4806                 },\r
4807 \r
4808                 fixDoc: function(doc) {\r
4809                         var settings = this.settings, name;\r
4810 \r
4811                         if (isIE && settings.schema) {\r
4812                                 // Add missing HTML 4/5 elements to IE\r
4813                                 ('abbr article aside audio canvas ' +\r
4814                                 'details figcaption figure footer ' +\r
4815                                 'header hgroup mark menu meter nav ' +\r
4816                                 'output progress section summary ' +\r
4817                                 'time video').replace(/\w+/g, function(name) {\r
4818                                         doc.createElement(name);\r
4819                                 });\r
4820 \r
4821                                 // Create all custom elements\r
4822                                 for (name in settings.schema.getCustomElements()) {\r
4823                                         doc.createElement(name);\r
4824                                 }\r
4825                         }\r
4826                 },\r
4827 \r
4828                 clone: function(node, deep) {\r
4829                         var self = this, clone, doc;\r
4830 \r
4831                         // TODO: Add feature detection here in the future\r
4832                         if (!isIE || node.nodeType !== 1 || deep) {\r
4833                                 return node.cloneNode(deep);\r
4834                         }\r
4835 \r
4836                         doc = self.doc;\r
4837 \r
4838                         // Make a HTML5 safe shallow copy\r
4839                         if (!deep) {\r
4840                                 clone = doc.createElement(node.nodeName);\r
4841 \r
4842                                 // Copy attribs\r
4843                                 each(self.getAttribs(node), function(attr) {\r
4844                                         self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));\r
4845                                 });\r
4846 \r
4847                                 return clone;\r
4848                         }\r
4849 /*\r
4850                         // Setup HTML5 patched document fragment\r
4851                         if (!self.frag) {\r
4852                                 self.frag = doc.createDocumentFragment();\r
4853                                 self.fixDoc(self.frag);\r
4854                         }\r
4855 \r
4856                         // Make a deep copy by adding it to the document fragment then removing it this removed the :section\r
4857                         clone = doc.createElement('div');\r
4858                         self.frag.appendChild(clone);\r
4859                         clone.innerHTML = node.outerHTML;\r
4860                         self.frag.removeChild(clone);\r
4861 */\r
4862                         return clone.firstChild;\r
4863                 },\r
4864 \r
4865                 getRoot : function() {\r
4866                         var t = this, s = t.settings;\r
4867 \r
4868                         return (s && t.get(s.root_element)) || t.doc.body;\r
4869                 },\r
4870 \r
4871                 getViewPort : function(w) {\r
4872                         var d, b;\r
4873 \r
4874                         w = !w ? this.win : w;\r
4875                         d = w.document;\r
4876                         b = this.boxModel ? d.documentElement : d.body;\r
4877 \r
4878                         // Returns viewport size excluding scrollbars\r
4879                         return {\r
4880                                 x : w.pageXOffset || b.scrollLeft,\r
4881                                 y : w.pageYOffset || b.scrollTop,\r
4882                                 w : w.innerWidth || b.clientWidth,\r
4883                                 h : w.innerHeight || b.clientHeight\r
4884                         };\r
4885                 },\r
4886 \r
4887                 getRect : function(e) {\r
4888                         var p, t = this, sr;\r
4889 \r
4890                         e = t.get(e);\r
4891                         p = t.getPos(e);\r
4892                         sr = t.getSize(e);\r
4893 \r
4894                         return {\r
4895                                 x : p.x,\r
4896                                 y : p.y,\r
4897                                 w : sr.w,\r
4898                                 h : sr.h\r
4899                         };\r
4900                 },\r
4901 \r
4902                 getSize : function(e) {\r
4903                         var t = this, w, h;\r
4904 \r
4905                         e = t.get(e);\r
4906                         w = t.getStyle(e, 'width');\r
4907                         h = t.getStyle(e, 'height');\r
4908 \r
4909                         // Non pixel value, then force offset/clientWidth\r
4910                         if (w.indexOf('px') === -1)\r
4911                                 w = 0;\r
4912 \r
4913                         // Non pixel value, then force offset/clientWidth\r
4914                         if (h.indexOf('px') === -1)\r
4915                                 h = 0;\r
4916 \r
4917                         return {\r
4918                                 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,\r
4919                                 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight\r
4920                         };\r
4921                 },\r
4922 \r
4923                 getParent : function(n, f, r) {\r
4924                         return this.getParents(n, f, r, false);\r
4925                 },\r
4926 \r
4927                 getParents : function(n, f, r, c) {\r
4928                         var t = this, na, se = t.settings, o = [];\r
4929 \r
4930                         n = t.get(n);\r
4931                         c = c === undefined;\r
4932 \r
4933                         if (se.strict_root)\r
4934                                 r = r || t.getRoot();\r
4935 \r
4936                         // Wrap node name as func\r
4937                         if (is(f, 'string')) {\r
4938                                 na = f;\r
4939 \r
4940                                 if (f === '*') {\r
4941                                         f = function(n) {return n.nodeType == 1;};\r
4942                                 } else {\r
4943                                         f = function(n) {\r
4944                                                 return t.is(n, na);\r
4945                                         };\r
4946                                 }\r
4947                         }\r
4948 \r
4949                         while (n) {\r
4950                                 if (n == r || !n.nodeType || n.nodeType === 9)\r
4951                                         break;\r
4952 \r
4953                                 if (!f || f(n)) {\r
4954                                         if (c)\r
4955                                                 o.push(n);\r
4956                                         else\r
4957                                                 return n;\r
4958                                 }\r
4959 \r
4960                                 n = n.parentNode;\r
4961                         }\r
4962 \r
4963                         return c ? o : null;\r
4964                 },\r
4965 \r
4966                 get : function(e) {\r
4967                         var n;\r
4968 \r
4969                         if (e && this.doc && typeof(e) == 'string') {\r
4970                                 n = e;\r
4971                                 e = this.doc.getElementById(e);\r
4972 \r
4973                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick\r
4974                                 if (e && e.id !== n)\r
4975                                         return this.doc.getElementsByName(n)[1];\r
4976                         }\r
4977 \r
4978                         return e;\r
4979                 },\r
4980 \r
4981                 getNext : function(node, selector) {\r
4982                         return this._findSib(node, selector, 'nextSibling');\r
4983                 },\r
4984 \r
4985                 getPrev : function(node, selector) {\r
4986                         return this._findSib(node, selector, 'previousSibling');\r
4987                 },\r
4988 \r
4989 \r
4990                 select : function(pa, s) {\r
4991                         var t = this;\r
4992 \r
4993                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);\r
4994                 },\r
4995 \r
4996                 is : function(n, selector) {\r
4997                         var i;\r
4998 \r
4999                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance\r
5000                         if (n.length === undefined) {\r
5001                                 // Simple all selector\r
5002                                 if (selector === '*')\r
5003                                         return n.nodeType == 1;\r
5004 \r
5005                                 // Simple selector just elements\r
5006                                 if (simpleSelectorRe.test(selector)) {\r
5007                                         selector = selector.toLowerCase().split(/,/);\r
5008                                         n = n.nodeName.toLowerCase();\r
5009 \r
5010                                         for (i = selector.length - 1; i >= 0; i--) {\r
5011                                                 if (selector[i] == n)\r
5012                                                         return true;\r
5013                                         }\r
5014 \r
5015                                         return false;\r
5016                                 }\r
5017                         }\r
5018 \r
5019                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;\r
5020                 },\r
5021 \r
5022 \r
5023                 add : function(p, n, a, h, c) {\r
5024                         var t = this;\r
5025 \r
5026                         return this.run(p, function(p) {\r
5027                                 var e, k;\r
5028 \r
5029                                 e = is(n, 'string') ? t.doc.createElement(n) : n;\r
5030                                 t.setAttribs(e, a);\r
5031 \r
5032                                 if (h) {\r
5033                                         if (h.nodeType)\r
5034                                                 e.appendChild(h);\r
5035                                         else\r
5036                                                 t.setHTML(e, h);\r
5037                                 }\r
5038 \r
5039                                 return !c ? p.appendChild(e) : e;\r
5040                         });\r
5041                 },\r
5042 \r
5043                 create : function(n, a, h) {\r
5044                         return this.add(this.doc.createElement(n), n, a, h, 1);\r
5045                 },\r
5046 \r
5047                 createHTML : function(n, a, h) {\r
5048                         var o = '', t = this, k;\r
5049 \r
5050                         o += '<' + n;\r
5051 \r
5052                         for (k in a) {\r
5053                                 if (a.hasOwnProperty(k))\r
5054                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';\r
5055                         }\r
5056 \r
5057                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime\r
5058                         if (typeof(h) != "undefined")\r
5059                                 return o + '>' + h + '</' + n + '>';\r
5060 \r
5061                         return o + ' />';\r
5062                 },\r
5063 \r
5064                 remove : function(node, keep_children) {\r
5065                         return this.run(node, function(node) {\r
5066                                 var child, parent = node.parentNode;\r
5067 \r
5068                                 if (!parent)\r
5069                                         return null;\r
5070 \r
5071                                 if (keep_children) {\r
5072                                         while (child = node.firstChild) {\r
5073                                                 // IE 8 will crash if you don't remove completely empty text nodes\r
5074                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)\r
5075                                                         parent.insertBefore(child, node);\r
5076                                                 else\r
5077                                                         node.removeChild(child);\r
5078                                         }\r
5079                                 }\r
5080 \r
5081                                 return parent.removeChild(node);\r
5082                         });\r
5083                 },\r
5084 \r
5085                 setStyle : function(n, na, v) {\r
5086                         var t = this;\r
5087 \r
5088                         return t.run(n, function(e) {\r
5089                                 var s, i;\r
5090 \r
5091                                 s = e.style;\r
5092 \r
5093                                 // Camelcase it, if needed\r
5094                                 na = na.replace(/-(\D)/g, function(a, b){\r
5095                                         return b.toUpperCase();\r
5096                                 });\r
5097 \r
5098                                 // Default px suffix on these\r
5099                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))\r
5100                                         v += 'px';\r
5101 \r
5102                                 switch (na) {\r
5103                                         case 'opacity':\r
5104                                                 // IE specific opacity\r
5105                                                 if (isIE) {\r
5106                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";\r
5107 \r
5108                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)\r
5109                                                                 s.display = 'inline-block';\r
5110                                                 }\r
5111 \r
5112                                                 // Fix for older browsers\r
5113                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';\r
5114                                                 break;\r
5115 \r
5116                                         case 'float':\r
5117                                                 isIE ? s.styleFloat = v : s.cssFloat = v;\r
5118                                                 break;\r
5119                                         \r
5120                                         default:\r
5121                                                 s[na] = v || '';\r
5122                                 }\r
5123 \r
5124                                 // Force update of the style data\r
5125                                 if (t.settings.update_styles)\r
5126                                         t.setAttrib(e, 'data-mce-style');\r
5127                         });\r
5128                 },\r
5129 \r
5130                 getStyle : function(n, na, c) {\r
5131                         n = this.get(n);\r
5132 \r
5133                         if (!n)\r
5134                                 return;\r
5135 \r
5136                         // Gecko\r
5137                         if (this.doc.defaultView && c) {\r
5138                                 // Remove camelcase\r
5139                                 na = na.replace(/[A-Z]/g, function(a){\r
5140                                         return '-' + a;\r
5141                                 });\r
5142 \r
5143                                 try {\r
5144                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);\r
5145                                 } catch (ex) {\r
5146                                         // Old safari might fail\r
5147                                         return null;\r
5148                                 }\r
5149                         }\r
5150 \r
5151                         // Camelcase it, if needed\r
5152                         na = na.replace(/-(\D)/g, function(a, b){\r
5153                                 return b.toUpperCase();\r
5154                         });\r
5155 \r
5156                         if (na == 'float')\r
5157                                 na = isIE ? 'styleFloat' : 'cssFloat';\r
5158 \r
5159                         // IE & Opera\r
5160                         if (n.currentStyle && c)\r
5161                                 return n.currentStyle[na];\r
5162 \r
5163                         return n.style ? n.style[na] : undefined;\r
5164                 },\r
5165 \r
5166                 setStyles : function(e, o) {\r
5167                         var t = this, s = t.settings, ol;\r
5168 \r
5169                         ol = s.update_styles;\r
5170                         s.update_styles = 0;\r
5171 \r
5172                         each(o, function(v, n) {\r
5173                                 t.setStyle(e, n, v);\r
5174                         });\r
5175 \r
5176                         // Update style info\r
5177                         s.update_styles = ol;\r
5178                         if (s.update_styles)\r
5179                                 t.setAttrib(e, s.cssText);\r
5180                 },\r
5181 \r
5182                 removeAllAttribs: function(e) {\r
5183                         return this.run(e, function(e) {\r
5184                                 var i, attrs = e.attributes;\r
5185                                 for (i = attrs.length - 1; i >= 0; i--) {\r
5186                                         e.removeAttributeNode(attrs.item(i));\r
5187                                 }\r
5188                         });\r
5189                 },\r
5190 \r
5191                 setAttrib : function(e, n, v) {\r
5192                         var t = this;\r
5193 \r
5194                         // Whats the point\r
5195                         if (!e || !n)\r
5196                                 return;\r
5197 \r
5198                         // Strict XML mode\r
5199                         if (t.settings.strict)\r
5200                                 n = n.toLowerCase();\r
5201 \r
5202                         return this.run(e, function(e) {\r
5203                                 var s = t.settings;\r
5204                                 var originalValue = e.getAttribute(n);\r
5205                                 if (v !== null) {\r
5206                                         switch (n) {\r
5207                                                 case "style":\r
5208                                                         if (!is(v, 'string')) {\r
5209                                                                 each(v, function(v, n) {\r
5210                                                                         t.setStyle(e, n, v);\r
5211                                                                 });\r
5212 \r
5213                                                                 return;\r
5214                                                         }\r
5215 \r
5216                                                         // No mce_style for elements with these since they might get resized by the user\r
5217                                                         if (s.keep_values) {\r
5218                                                                 if (v && !t._isRes(v))\r
5219                                                                         e.setAttribute('data-mce-style', v, 2);\r
5220                                                                 else\r
5221                                                                         e.removeAttribute('data-mce-style', 2);\r
5222                                                         }\r
5223 \r
5224                                                         e.style.cssText = v;\r
5225                                                         break;\r
5226 \r
5227                                                 case "class":\r
5228                                                         e.className = v || ''; // Fix IE null bug\r
5229                                                         break;\r
5230 \r
5231                                                 case "src":\r
5232                                                 case "href":\r
5233                                                         if (s.keep_values) {\r
5234                                                                 if (s.url_converter)\r
5235                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, e);\r
5236 \r
5237                                                                 t.setAttrib(e, 'data-mce-' + n, v, 2);\r
5238                                                         }\r
5239 \r
5240                                                         break;\r
5241 \r
5242                                                 case "shape":\r
5243                                                         e.setAttribute('data-mce-style', v);\r
5244                                                         break;\r
5245                                         }\r
5246                                 }\r
5247                                 if (is(v) && v !== null && v.length !== 0)\r
5248                                         e.setAttribute(n, '' + v, 2);\r
5249                                 else\r
5250                                         e.removeAttribute(n, 2);\r
5251 \r
5252                                 // fire onChangeAttrib event for attributes that have changed\r
5253                                 if (tinyMCE.activeEditor && originalValue != v) {\r
5254                                         var ed = tinyMCE.activeEditor;\r
5255                                         ed.onSetAttrib.dispatch(ed, e, n, v);\r
5256                                 }\r
5257                         });\r
5258                 },\r
5259 \r
5260                 setAttribs : function(e, o) {\r
5261                         var t = this;\r
5262 \r
5263                         return this.run(e, function(e) {\r
5264                                 each(o, function(v, n) {\r
5265                                         t.setAttrib(e, n, v);\r
5266                                 });\r
5267                         });\r
5268                 },\r
5269 \r
5270                 getAttrib : function(e, n, dv) {\r
5271                         var v, t = this, undef;\r
5272 \r
5273                         e = t.get(e);\r
5274 \r
5275                         if (!e || e.nodeType !== 1)\r
5276                                 return dv === undef ? false : dv;\r
5277 \r
5278                         if (!is(dv))\r
5279                                 dv = '';\r
5280 \r
5281                         // Try the mce variant for these\r
5282                         if (/^(src|href|style|coords|shape)$/.test(n)) {\r
5283                                 v = e.getAttribute("data-mce-" + n);\r
5284 \r
5285                                 if (v)\r
5286                                         return v;\r
5287                         }\r
5288 \r
5289                         if (isIE && t.props[n]) {\r
5290                                 v = e[t.props[n]];\r
5291                                 v = v && v.nodeValue ? v.nodeValue : v;\r
5292                         }\r
5293 \r
5294                         if (!v)\r
5295                                 v = e.getAttribute(n, 2);\r
5296 \r
5297                         // Check boolean attribs\r
5298                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {\r
5299                                 if (e[t.props[n]] === true && v === '')\r
5300                                         return n;\r
5301 \r
5302                                 return v ? n : '';\r
5303                         }\r
5304 \r
5305                         // Inner input elements will override attributes on form elements\r
5306                         if (e.nodeName === "FORM" && e.getAttributeNode(n))\r
5307                                 return e.getAttributeNode(n).nodeValue;\r
5308 \r
5309                         if (n === 'style') {\r
5310                                 v = v || e.style.cssText;\r
5311 \r
5312                                 if (v) {\r
5313                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);\r
5314 \r
5315                                         if (t.settings.keep_values && !t._isRes(v))\r
5316                                                 e.setAttribute('data-mce-style', v);\r
5317                                 }\r
5318                         }\r
5319 \r
5320                         // Remove Apple and WebKit stuff\r
5321                         if (isWebKit && n === "class" && v)\r
5322                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');\r
5323 \r
5324                         // Handle IE issues\r
5325                         if (isIE) {\r
5326                                 switch (n) {\r
5327                                         case 'rowspan':\r
5328                                         case 'colspan':\r
5329                                                 // IE returns 1 as default value\r
5330                                                 if (v === 1)\r
5331                                                         v = '';\r
5332 \r
5333                                                 break;\r
5334 \r
5335                                         case 'size':\r
5336                                                 // IE returns +0 as default value for size\r
5337                                                 if (v === '+0' || v === 20 || v === 0)\r
5338                                                         v = '';\r
5339 \r
5340                                                 break;\r
5341 \r
5342                                         case 'width':\r
5343                                         case 'height':\r
5344                                         case 'vspace':\r
5345                                         case 'checked':\r
5346                                         case 'disabled':\r
5347                                         case 'readonly':\r
5348                                                 if (v === 0)\r
5349                                                         v = '';\r
5350 \r
5351                                                 break;\r
5352 \r
5353                                         case 'hspace':\r
5354                                                 // IE returns -1 as default value\r
5355                                                 if (v === -1)\r
5356                                                         v = '';\r
5357 \r
5358                                                 break;\r
5359 \r
5360                                         case 'maxlength':\r
5361                                         case 'tabindex':\r
5362                                                 // IE returns default value\r
5363                                                 if (v === 32768 || v === 2147483647 || v === '32768')\r
5364                                                         v = '';\r
5365 \r
5366                                                 break;\r
5367 \r
5368                                         case 'multiple':\r
5369                                         case 'compact':\r
5370                                         case 'noshade':\r
5371                                         case 'nowrap':\r
5372                                                 if (v === 65535)\r
5373                                                         return n;\r
5374 \r
5375                                                 return dv;\r
5376 \r
5377                                         case 'shape':\r
5378                                                 v = v.toLowerCase();\r
5379                                                 break;\r
5380 \r
5381                                         default:\r
5382                                                 // IE has odd anonymous function for event attributes\r
5383                                                 if (n.indexOf('on') === 0 && v)\r
5384                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);\r
5385                                 }\r
5386                         }\r
5387 \r
5388                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;\r
5389                 },\r
5390 \r
5391                 getPos : function(n, ro) {\r
5392                         var t = this, x = 0, y = 0, e, d = t.doc, r;\r
5393 \r
5394                         n = t.get(n);\r
5395                         ro = ro || d.body;\r
5396 \r
5397                         if (n) {\r
5398                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes\r
5399                                 if (n.getBoundingClientRect) {\r
5400                                         n = n.getBoundingClientRect();\r
5401                                         e = t.boxModel ? d.documentElement : d.body;\r
5402 \r
5403                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit\r
5404                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position\r
5405                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;\r
5406                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;\r
5407 \r
5408                                         return {x : x, y : y};\r
5409                                 }\r
5410 \r
5411                                 r = n;\r
5412                                 while (r && r != ro && r.nodeType) {\r
5413                                         x += r.offsetLeft || 0;\r
5414                                         y += r.offsetTop || 0;\r
5415                                         r = r.offsetParent;\r
5416                                 }\r
5417 \r
5418                                 r = n.parentNode;\r
5419                                 while (r && r != ro && r.nodeType) {\r
5420                                         x -= r.scrollLeft || 0;\r
5421                                         y -= r.scrollTop || 0;\r
5422                                         r = r.parentNode;\r
5423                                 }\r
5424                         }\r
5425 \r
5426                         return {x : x, y : y};\r
5427                 },\r
5428 \r
5429                 parseStyle : function(st) {\r
5430                         return this.styles.parse(st);\r
5431                 },\r
5432 \r
5433                 serializeStyle : function(o, name) {\r
5434                         return this.styles.serialize(o, name);\r
5435                 },\r
5436 \r
5437                 loadCSS : function(u) {\r
5438                         var t = this, d = t.doc, head;\r
5439 \r
5440                         if (!u)\r
5441                                 u = '';\r
5442 \r
5443                         head = d.getElementsByTagName('head')[0];\r
5444 \r
5445                         each(u.split(','), function(u) {\r
5446                                 var link;\r
5447 \r
5448                                 if (t.files[u])\r
5449                                         return;\r
5450 \r
5451                                 t.files[u] = true;\r
5452                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});\r
5453 \r
5454                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug\r
5455                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading\r
5456                                 // It's ugly but it seems to work fine.\r
5457                                 if (isIE && d.documentMode && d.recalc) {\r
5458                                         link.onload = function() {\r
5459                                                 if (d.recalc)\r
5460                                                         d.recalc();\r
5461 \r
5462                                                 link.onload = null;\r
5463                                         };\r
5464                                 }\r
5465 \r
5466                                 head.appendChild(link);\r
5467                         });\r
5468                 },\r
5469 \r
5470                 addClass : function(e, c) {\r
5471                         return this.run(e, function(e) {\r
5472                                 var o;\r
5473 \r
5474                                 if (!c)\r
5475                                         return 0;\r
5476 \r
5477                                 if (this.hasClass(e, c))\r
5478                                         return e.className;\r
5479 \r
5480                                 o = this.removeClass(e, c);\r
5481 \r
5482                                 return e.className = (o != '' ? (o + ' ') : '') + c;\r
5483                         });\r
5484                 },\r
5485 \r
5486                 removeClass : function(e, c) {\r
5487                         var t = this, re;\r
5488 \r
5489                         return t.run(e, function(e) {\r
5490                                 var v;\r
5491 \r
5492                                 if (t.hasClass(e, c)) {\r
5493                                         if (!re)\r
5494                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");\r
5495 \r
5496                                         v = e.className.replace(re, ' ');\r
5497                                         v = tinymce.trim(v != ' ' ? v : '');\r
5498 \r
5499                                         e.className = v;\r
5500 \r
5501                                         // Empty class attr\r
5502                                         if (!v) {\r
5503                                                 e.removeAttribute('class');\r
5504                                                 e.removeAttribute('className');\r
5505                                         }\r
5506 \r
5507                                         return v;\r
5508                                 }\r
5509 \r
5510                                 return e.className;\r
5511                         });\r
5512                 },\r
5513 \r
5514                 hasClass : function(n, c) {\r
5515                         n = this.get(n);\r
5516 \r
5517                         if (!n || !c)\r
5518                                 return false;\r
5519 \r
5520                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;\r
5521                 },\r
5522 \r
5523                 show : function(e) {\r
5524                         return this.setStyle(e, 'display', 'block');\r
5525                 },\r
5526 \r
5527                 hide : function(e) {\r
5528                         return this.setStyle(e, 'display', 'none');\r
5529                 },\r
5530 \r
5531                 isHidden : function(e) {\r
5532                         e = this.get(e);\r
5533 \r
5534                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';\r
5535                 },\r
5536 \r
5537                 uniqueId : function(p) {\r
5538                         return (!p ? 'mce_' : p) + (this.counter++);\r
5539                 },\r
5540 \r
5541                 setHTML : function(element, html) {\r
5542                         var self = this;\r
5543 \r
5544                         return self.run(element, function(element) {\r
5545                                 if (isIE) {\r
5546                                         // Remove all child nodes, IE keeps empty text nodes in DOM\r
5547                                         while (element.firstChild)\r
5548                                                 element.removeChild(element.firstChild);\r
5549 \r
5550                                         try {\r
5551                                                 // IE will remove comments from the beginning\r
5552                                                 // unless you padd the contents with something\r
5553                                                 element.innerHTML = '<br />' + html;\r
5554                                                 element.removeChild(element.firstChild);\r
5555                                         } catch (ex) {\r
5556                                                 // 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
5557                                                 // This seems to fix this problem\r
5558 \r
5559                                                 // Create new div with HTML contents and a BR infront to keep comments\r
5560                                                 element = self.create('div');\r
5561                                                 element.innerHTML = '<br />' + html;\r
5562 \r
5563                                                 // Add all children from div to target\r
5564                                                 each (element.childNodes, function(node, i) {\r
5565                                                         // Skip br element\r
5566                                                         if (i)\r
5567                                                                 element.appendChild(node);\r
5568                                                 });\r
5569                                         }\r
5570                                 } else\r
5571                                         element.innerHTML = html;\r
5572 \r
5573                                 return html;\r
5574                         });\r
5575                 },\r
5576 \r
5577                 getOuterHTML : function(elm) {\r
5578                         var doc, self = this;\r
5579 \r
5580                         elm = self.get(elm);\r
5581 \r
5582                         if (!elm)\r
5583                                 return null;\r
5584 \r
5585                         if (elm.nodeType === 1 && self.hasOuterHTML)\r
5586                                 return elm.outerHTML;\r
5587 \r
5588                         doc = (elm.ownerDocument || self.doc).createElement("body");\r
5589                         doc.appendChild(elm.cloneNode(true));\r
5590 \r
5591                         return doc.innerHTML;\r
5592                 },\r
5593 \r
5594                 setOuterHTML : function(e, h, d) {\r
5595                         var t = this;\r
5596 \r
5597                         function setHTML(e, h, d) {\r
5598                                 var n, tp;\r
5599 \r
5600                                 tp = d.createElement("body");\r
5601                                 tp.innerHTML = h;\r
5602 \r
5603                                 n = tp.lastChild;\r
5604                                 while (n) {\r
5605                                         t.insertAfter(n.cloneNode(true), e);\r
5606                                         n = n.previousSibling;\r
5607                                 }\r
5608 \r
5609                                 t.remove(e);\r
5610                         };\r
5611 \r
5612                         return this.run(e, function(e) {\r
5613                                 e = t.get(e);\r
5614 \r
5615                                 // Only set HTML on elements\r
5616                                 if (e.nodeType == 1) {\r
5617                                         d = d || e.ownerDocument || t.doc;\r
5618 \r
5619                                         if (isIE) {\r
5620                                                 try {\r
5621                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error\r
5622                                                         if (isIE && e.nodeType == 1)\r
5623                                                                 e.outerHTML = h;\r
5624                                                         else\r
5625                                                                 setHTML(e, h, d);\r
5626                                                 } catch (ex) {\r
5627                                                         // Fix for unknown runtime error\r
5628                                                         setHTML(e, h, d);\r
5629                                                 }\r
5630                                         } else\r
5631                                                 setHTML(e, h, d);\r
5632                                 }\r
5633                         });\r
5634                 },\r
5635 \r
5636                 decode : Entities.decode,\r
5637 \r
5638                 encode : Entities.encodeAllRaw,\r
5639 \r
5640                 insertAfter : function(node, reference_node) {\r
5641                         reference_node = this.get(reference_node);\r
5642 \r
5643                         return this.run(node, function(node) {\r
5644                                 var parent, nextSibling;\r
5645 \r
5646                                 parent = reference_node.parentNode;\r
5647                                 nextSibling = reference_node.nextSibling;\r
5648 \r
5649                                 if (nextSibling)\r
5650                                         parent.insertBefore(node, nextSibling);\r
5651                                 else\r
5652                                         parent.appendChild(node);\r
5653 \r
5654                                 return node;\r
5655                         });\r
5656                 },\r
5657 \r
5658                 replace : function(n, o, k) {\r
5659                         var t = this;\r
5660 \r
5661                         if (is(o, 'array'))\r
5662                                 n = n.cloneNode(true);\r
5663 \r
5664                         return t.run(o, function(o) {\r
5665                                 if (k) {\r
5666                                         each(tinymce.grep(o.childNodes), function(c) {\r
5667                                                 n.appendChild(c);\r
5668                                         });\r
5669                                 }\r
5670 \r
5671                                 return o.parentNode.replaceChild(n, o);\r
5672                         });\r
5673                 },\r
5674 \r
5675                 rename : function(elm, name) {\r
5676                         var t = this, newElm;\r
5677 \r
5678                         if (elm.nodeName != name.toUpperCase()) {\r
5679                                 // Rename block element\r
5680                                 newElm = t.create(name);\r
5681 \r
5682                                 // Copy attribs to new block\r
5683                                 each(t.getAttribs(elm), function(attr_node) {\r
5684                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));\r
5685                                 });\r
5686 \r
5687                                 // Replace block\r
5688                                 t.replace(newElm, elm, 1);\r
5689                         }\r
5690 \r
5691                         return newElm || elm;\r
5692                 },\r
5693 \r
5694                 findCommonAncestor : function(a, b) {\r
5695                         var ps = a, pe;\r
5696 \r
5697                         while (ps) {\r
5698                                 pe = b;\r
5699 \r
5700                                 while (pe && ps != pe)\r
5701                                         pe = pe.parentNode;\r
5702 \r
5703                                 if (ps == pe)\r
5704                                         break;\r
5705 \r
5706                                 ps = ps.parentNode;\r
5707                         }\r
5708 \r
5709                         if (!ps && a.ownerDocument)\r
5710                                 return a.ownerDocument.documentElement;\r
5711 \r
5712                         return ps;\r
5713                 },\r
5714 \r
5715                 toHex : function(s) {\r
5716                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);\r
5717 \r
5718                         function hex(s) {\r
5719                                 s = parseInt(s, 10).toString(16);\r
5720 \r
5721                                 return s.length > 1 ? s : '0' + s; // 0 -> 00\r
5722                         };\r
5723 \r
5724                         if (c) {\r
5725                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);\r
5726 \r
5727                                 return s;\r
5728                         }\r
5729 \r
5730                         return s;\r
5731                 },\r
5732 \r
5733                 getClasses : function() {\r
5734                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;\r
5735 \r
5736                         if (t.classes)\r
5737                                 return t.classes;\r
5738 \r
5739                         function addClasses(s) {\r
5740                                 // IE style imports\r
5741                                 each(s.imports, function(r) {\r
5742                                         addClasses(r);\r
5743                                 });\r
5744 \r
5745                                 each(s.cssRules || s.rules, function(r) {\r
5746                                         // Real type or fake it on IE\r
5747                                         switch (r.type || 1) {\r
5748                                                 // Rule\r
5749                                                 case 1:\r
5750                                                         if (r.selectorText) {\r
5751                                                                 each(r.selectorText.split(','), function(v) {\r
5752                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");\r
5753 \r
5754                                                                         // Is internal or it doesn't contain a class\r
5755                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))\r
5756                                                                                 return;\r
5757 \r
5758                                                                         // Remove everything but class name\r
5759                                                                         ov = v;\r
5760                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);\r
5761 \r
5762                                                                         // Filter classes\r
5763                                                                         if (f && !(v = f(v, ov)))\r
5764                                                                                 return;\r
5765 \r
5766                                                                         if (!lo[v]) {\r
5767                                                                                 cl.push({'class' : v});\r
5768                                                                                 lo[v] = 1;\r
5769                                                                         }\r
5770                                                                 });\r
5771                                                         }\r
5772                                                         break;\r
5773 \r
5774                                                 // Import\r
5775                                                 case 3:\r
5776                                                         addClasses(r.styleSheet);\r
5777                                                         break;\r
5778                                         }\r
5779                                 });\r
5780                         };\r
5781 \r
5782                         try {\r
5783                                 each(t.doc.styleSheets, addClasses);\r
5784                         } catch (ex) {\r
5785                                 // Ignore\r
5786                         }\r
5787 \r
5788                         if (cl.length > 0)\r
5789                                 t.classes = cl;\r
5790 \r
5791                         return cl;\r
5792                 },\r
5793 \r
5794                 run : function(e, f, s) {\r
5795                         var t = this, o;\r
5796 \r
5797                         if (t.doc && typeof(e) === 'string')\r
5798                                 e = t.get(e);\r
5799 \r
5800                         if (!e)\r
5801                                 return false;\r
5802 \r
5803                         s = s || this;\r
5804                         if (!e.nodeType && (e.length || e.length === 0)) {\r
5805                                 o = [];\r
5806 \r
5807                                 each(e, function(e, i) {\r
5808                                         if (e) {\r
5809                                                 if (typeof(e) == 'string')\r
5810                                                         e = t.doc.getElementById(e);\r
5811 \r
5812                                                 o.push(f.call(s, e, i));\r
5813                                         }\r
5814                                 });\r
5815 \r
5816                                 return o;\r
5817                         }\r
5818 \r
5819                         return f.call(s, e);\r
5820                 },\r
5821 \r
5822                 getAttribs : function(n) {\r
5823                         var o;\r
5824 \r
5825                         n = this.get(n);\r
5826 \r
5827                         if (!n)\r
5828                                 return [];\r
5829 \r
5830                         if (isIE) {\r
5831                                 o = [];\r
5832 \r
5833                                 // Object will throw exception in IE\r
5834                                 if (n.nodeName == 'OBJECT')\r
5835                                         return n.attributes;\r
5836 \r
5837                                 // IE doesn't keep the selected attribute if you clone option elements\r
5838                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))\r
5839                                         o.push({specified : 1, nodeName : 'selected'});\r
5840 \r
5841                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time\r
5842                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {\r
5843                                         o.push({specified : 1, nodeName : a});\r
5844                                 });\r
5845 \r
5846                                 return o;\r
5847                         }\r
5848 \r
5849                         return n.attributes;\r
5850                 },\r
5851 \r
5852                 isEmpty : function(node, elements) {\r
5853                         var self = this, i, attributes, type, walker, name, brCount = 0;\r
5854 \r
5855                         node = node.firstChild;\r
5856                         if (node) {\r
5857                                 walker = new tinymce.dom.TreeWalker(node, node.parentNode);\r
5858                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;\r
5859 \r
5860                                 do {\r
5861                                         type = node.nodeType;\r
5862 \r
5863                                         if (type === 1) {\r
5864                                                 // Ignore bogus elements\r
5865                                                 if (node.getAttribute('data-mce-bogus'))\r
5866                                                         continue;\r
5867 \r
5868                                                 // Keep empty elements like <img />\r
5869                                                 name = node.nodeName.toLowerCase();\r
5870                                                 if (elements && elements[name]) {\r
5871                                                         // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>\r
5872                                                         if (name === 'br') {\r
5873                                                                 brCount++;\r
5874                                                                 continue;\r
5875                                                         }\r
5876 \r
5877                                                         return false;\r
5878                                                 }\r
5879 \r
5880                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>\r
5881                                                 attributes = self.getAttribs(node);\r
5882                                                 i = node.attributes.length;\r
5883                                                 while (i--) {\r
5884                                                         name = node.attributes[i].nodeName;\r
5885                                                         if (name === "name" || name === 'data-mce-bookmark')\r
5886                                                                 return false;\r
5887                                                 }\r
5888                                         }\r
5889 \r
5890                                         // Keep comment nodes\r
5891                                         if (type == 8)\r
5892                                                 return false;\r
5893 \r
5894                                         // Keep non whitespace text nodes\r
5895                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))\r
5896                                                 return false;\r
5897                                 } while (node = walker.next());\r
5898                         }\r
5899 \r
5900                         return brCount <= 1;\r
5901                 },\r
5902 \r
5903                 destroy : function(s) {\r
5904                         var t = this;\r
5905 \r
5906                         t.win = t.doc = t.root = t.events = t.frag = null;\r
5907 \r
5908                         // Manual destroy then remove unload handler\r
5909                         if (!s)\r
5910                                 tinymce.removeUnload(t.destroy);\r
5911                 },\r
5912 \r
5913                 createRng : function() {\r
5914                         var d = this.doc;\r
5915 \r
5916                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);\r
5917                 },\r
5918 \r
5919                 nodeIndex : function(node, normalized) {\r
5920                         var idx = 0, lastNodeType, lastNode, nodeType;\r
5921 \r
5922                         if (node) {\r
5923                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {\r
5924                                         nodeType = node.nodeType;\r
5925 \r
5926                                         // Normalize text nodes\r
5927                                         if (normalized && nodeType == 3) {\r
5928                                                 if (nodeType == lastNodeType || !node.nodeValue.length)\r
5929                                                         continue;\r
5930                                         }\r
5931                                         idx++;\r
5932                                         lastNodeType = nodeType;\r
5933                                 }\r
5934                         }\r
5935 \r
5936                         return idx;\r
5937                 },\r
5938 \r
5939                 split : function(pe, e, re) {\r
5940                         var t = this, r = t.createRng(), bef, aft, pa;\r
5941 \r
5942                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense\r
5943                         // but we don't want that in our code since it serves no purpose for the end user\r
5944                         // For example if this is chopped:\r
5945                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>\r
5946                         // would produce:\r
5947                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>\r
5948                         // this function will then trim of empty edges and produce:\r
5949                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>\r
5950                         function trim(node) {\r
5951                                 var i, children = node.childNodes, type = node.nodeType;\r
5952 \r
5953                                 function surroundedBySpans(node) {\r
5954                                         var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';\r
5955                                         var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';\r
5956                                         return previousIsSpan && nextIsSpan;\r
5957                                 }\r
5958 \r
5959                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')\r
5960                                         return;\r
5961 \r
5962                                 for (i = children.length - 1; i >= 0; i--)\r
5963                                         trim(children[i]);\r
5964 \r
5965                                 if (type != 9) {\r
5966                                         // Keep non whitespace text nodes\r
5967                                         if (type == 3 && node.nodeValue.length > 0) {\r
5968                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"\r
5969                                                 // Also keep text nodes with only spaces if surrounded by spans.\r
5970                                                 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b\r
5971                                                 var trimmedLength = tinymce.trim(node.nodeValue).length;\r
5972                                                 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))\r
5973                                                         return;\r
5974                                         } else if (type == 1) {\r
5975                                                 // If the only child is a bookmark then move it up\r
5976                                                 children = node.childNodes;\r
5977                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')\r
5978                                                         node.parentNode.insertBefore(children[0], node);\r
5979 \r
5980                                                 // Keep non empty elements or img, hr etc\r
5981                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))\r
5982                                                         return;\r
5983                                         }\r
5984 \r
5985                                         t.remove(node);\r
5986                                 }\r
5987 \r
5988                                 return node;\r
5989                         };\r
5990 \r
5991                         if (pe && e) {\r
5992                                 // Get before chunk\r
5993                                 r.setStart(pe.parentNode, t.nodeIndex(pe));\r
5994                                 r.setEnd(e.parentNode, t.nodeIndex(e));\r
5995                                 bef = r.extractContents();\r
5996 \r
5997                                 // Get after chunk\r
5998                                 r = t.createRng();\r
5999                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);\r
6000                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);\r
6001                                 aft = r.extractContents();\r
6002 \r
6003                                 // Insert before chunk\r
6004                                 pa = pe.parentNode;\r
6005                                 pa.insertBefore(trim(bef), pe);\r
6006 \r
6007                                 // Insert middle chunk\r
6008                                 if (re)\r
6009                                 pa.replaceChild(re, e);\r
6010                         else\r
6011                                 pa.insertBefore(e, pe);\r
6012 \r
6013                                 // Insert after chunk\r
6014                                 pa.insertBefore(trim(aft), pe);\r
6015                                 t.remove(pe);\r
6016 \r
6017                                 return re || e;\r
6018                         }\r
6019                 },\r
6020 \r
6021                 bind : function(target, name, func, scope) {\r
6022                         return this.events.add(target, name, func, scope || this);\r
6023                 },\r
6024 \r
6025                 unbind : function(target, name, func) {\r
6026                         return this.events.remove(target, name, func);\r
6027                 },\r
6028 \r
6029                 fire : function(target, name, evt) {\r
6030                         return this.events.fire(target, name, evt);\r
6031                 },\r
6032 \r
6033                 // Returns the content editable state of a node\r
6034                 getContentEditable: function(node) {\r
6035                         var contentEditable;\r
6036 \r
6037                         // Check type\r
6038                         if (node.nodeType != 1) {\r
6039                                 return null;\r
6040                         }\r
6041 \r
6042                         // Check for fake content editable\r
6043                         contentEditable = node.getAttribute("data-mce-contenteditable");\r
6044                         if (contentEditable && contentEditable !== "inherit") {\r
6045                                 return contentEditable;\r
6046                         }\r
6047 \r
6048                         // Check for real content editable\r
6049                         return node.contentEditable !== "inherit" ? node.contentEditable : null;\r
6050                 },\r
6051 \r
6052 \r
6053                 _findSib : function(node, selector, name) {\r
6054                         var t = this, f = selector;\r
6055 \r
6056                         if (node) {\r
6057                                 // If expression make a function of it using is\r
6058                                 if (is(f, 'string')) {\r
6059                                         f = function(node) {\r
6060                                                 return t.is(node, selector);\r
6061                                         };\r
6062                                 }\r
6063 \r
6064                                 // Loop all siblings\r
6065                                 for (node = node[name]; node; node = node[name]) {\r
6066                                         if (f(node))\r
6067                                                 return node;\r
6068                                 }\r
6069                         }\r
6070 \r
6071                         return null;\r
6072                 },\r
6073 \r
6074                 _isRes : function(c) {\r
6075                         // Is live resizble element\r
6076                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);\r
6077                 }\r
6078 \r
6079                 /*\r
6080                 walk : function(n, f, s) {\r
6081                         var d = this.doc, w;\r
6082 \r
6083                         if (d.createTreeWalker) {\r
6084                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);\r
6085 \r
6086                                 while ((n = w.nextNode()) != null)\r
6087                                         f.call(s || this, n);\r
6088                         } else\r
6089                                 tinymce.walk(n, f, 'childNodes', s);\r
6090                 }\r
6091                 */\r
6092 \r
6093                 /*\r
6094                 toRGB : function(s) {\r
6095                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);\r
6096 \r
6097                         if (c) {\r
6098                                 // #FFF -> #FFFFFF\r
6099                                 if (!is(c[3]))\r
6100                                         c[3] = c[2] = c[1];\r
6101 \r
6102                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";\r
6103                         }\r
6104 \r
6105                         return s;\r
6106                 }\r
6107                 */\r
6108         });\r
6109 \r
6110         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});\r
6111 })(tinymce);\r
6112 \r
6113 (function(ns) {\r
6114         // Range constructor\r
6115         function Range(dom) {\r
6116                 var t = this,\r
6117                         doc = dom.doc,\r
6118                         EXTRACT = 0,\r
6119                         CLONE = 1,\r
6120                         DELETE = 2,\r
6121                         TRUE = true,\r
6122                         FALSE = false,\r
6123                         START_OFFSET = 'startOffset',\r
6124                         START_CONTAINER = 'startContainer',\r
6125                         END_CONTAINER = 'endContainer',\r
6126                         END_OFFSET = 'endOffset',\r
6127                         extend = tinymce.extend,\r
6128                         nodeIndex = dom.nodeIndex;\r
6129 \r
6130                 extend(t, {\r
6131                         // Inital states\r
6132                         startContainer : doc,\r
6133                         startOffset : 0,\r
6134                         endContainer : doc,\r
6135                         endOffset : 0,\r
6136                         collapsed : TRUE,\r
6137                         commonAncestorContainer : doc,\r
6138 \r
6139                         // Range constants\r
6140                         START_TO_START : 0,\r
6141                         START_TO_END : 1,\r
6142                         END_TO_END : 2,\r
6143                         END_TO_START : 3,\r
6144 \r
6145                         // Public methods\r
6146                         setStart : setStart,\r
6147                         setEnd : setEnd,\r
6148                         setStartBefore : setStartBefore,\r
6149                         setStartAfter : setStartAfter,\r
6150                         setEndBefore : setEndBefore,\r
6151                         setEndAfter : setEndAfter,\r
6152                         collapse : collapse,\r
6153                         selectNode : selectNode,\r
6154                         selectNodeContents : selectNodeContents,\r
6155                         compareBoundaryPoints : compareBoundaryPoints,\r
6156                         deleteContents : deleteContents,\r
6157                         extractContents : extractContents,\r
6158                         cloneContents : cloneContents,\r
6159                         insertNode : insertNode,\r
6160                         surroundContents : surroundContents,\r
6161                         cloneRange : cloneRange\r
6162                 });\r
6163 \r
6164                 function createDocumentFragment() {\r
6165                         return doc.createDocumentFragment();\r
6166                 };\r
6167 \r
6168                 function setStart(n, o) {\r
6169                         _setEndPoint(TRUE, n, o);\r
6170                 };\r
6171 \r
6172                 function setEnd(n, o) {\r
6173                         _setEndPoint(FALSE, n, o);\r
6174                 };\r
6175 \r
6176                 function setStartBefore(n) {\r
6177                         setStart(n.parentNode, nodeIndex(n));\r
6178                 };\r
6179 \r
6180                 function setStartAfter(n) {\r
6181                         setStart(n.parentNode, nodeIndex(n) + 1);\r
6182                 };\r
6183 \r
6184                 function setEndBefore(n) {\r
6185                         setEnd(n.parentNode, nodeIndex(n));\r
6186                 };\r
6187 \r
6188                 function setEndAfter(n) {\r
6189                         setEnd(n.parentNode, nodeIndex(n) + 1);\r
6190                 };\r
6191 \r
6192                 function collapse(ts) {\r
6193                         if (ts) {\r
6194                                 t[END_CONTAINER] = t[START_CONTAINER];\r
6195                                 t[END_OFFSET] = t[START_OFFSET];\r
6196                         } else {\r
6197                                 t[START_CONTAINER] = t[END_CONTAINER];\r
6198                                 t[START_OFFSET] = t[END_OFFSET];\r
6199                         }\r
6200 \r
6201                         t.collapsed = TRUE;\r
6202                 };\r
6203 \r
6204                 function selectNode(n) {\r
6205                         setStartBefore(n);\r
6206                         setEndAfter(n);\r
6207                 };\r
6208 \r
6209                 function selectNodeContents(n) {\r
6210                         setStart(n, 0);\r
6211                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);\r
6212                 };\r
6213 \r
6214                 function compareBoundaryPoints(h, r) {\r
6215                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],\r
6216                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;\r
6217 \r
6218                         // Check START_TO_START\r
6219                         if (h === 0)\r
6220                                 return _compareBoundaryPoints(sc, so, rsc, rso);\r
6221         \r
6222                         // Check START_TO_END\r
6223                         if (h === 1)\r
6224                                 return _compareBoundaryPoints(ec, eo, rsc, rso);\r
6225         \r
6226                         // Check END_TO_END\r
6227                         if (h === 2)\r
6228                                 return _compareBoundaryPoints(ec, eo, rec, reo);\r
6229         \r
6230                         // Check END_TO_START\r
6231                         if (h === 3) \r
6232                                 return _compareBoundaryPoints(sc, so, rec, reo);\r
6233                 };\r
6234 \r
6235                 function deleteContents() {\r
6236                         _traverse(DELETE);\r
6237                 };\r
6238 \r
6239                 function extractContents() {\r
6240                         return _traverse(EXTRACT);\r
6241                 };\r
6242 \r
6243                 function cloneContents() {\r
6244                         return _traverse(CLONE);\r
6245                 };\r
6246 \r
6247                 function insertNode(n) {\r
6248                         var startContainer = this[START_CONTAINER],\r
6249                                 startOffset = this[START_OFFSET], nn, o;\r
6250 \r
6251                         // Node is TEXT_NODE or CDATA\r
6252                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {\r
6253                                 if (!startOffset) {\r
6254                                         // At the start of text\r
6255                                         startContainer.parentNode.insertBefore(n, startContainer);\r
6256                                 } else if (startOffset >= startContainer.nodeValue.length) {\r
6257                                         // At the end of text\r
6258                                         dom.insertAfter(n, startContainer);\r
6259                                 } else {\r
6260                                         // Middle, need to split\r
6261                                         nn = startContainer.splitText(startOffset);\r
6262                                         startContainer.parentNode.insertBefore(n, nn);\r
6263                                 }\r
6264                         } else {\r
6265                                 // Insert element node\r
6266                                 if (startContainer.childNodes.length > 0)\r
6267                                         o = startContainer.childNodes[startOffset];\r
6268 \r
6269                                 if (o)\r
6270                                         startContainer.insertBefore(n, o);\r
6271                                 else\r
6272                                         startContainer.appendChild(n);\r
6273                         }\r
6274                 };\r
6275 \r
6276                 function surroundContents(n) {\r
6277                         var f = t.extractContents();\r
6278 \r
6279                         t.insertNode(n);\r
6280                         n.appendChild(f);\r
6281                         t.selectNode(n);\r
6282                 };\r
6283 \r
6284                 function cloneRange() {\r
6285                         return extend(new Range(dom), {\r
6286                                 startContainer : t[START_CONTAINER],\r
6287                                 startOffset : t[START_OFFSET],\r
6288                                 endContainer : t[END_CONTAINER],\r
6289                                 endOffset : t[END_OFFSET],\r
6290                                 collapsed : t.collapsed,\r
6291                                 commonAncestorContainer : t.commonAncestorContainer\r
6292                         });\r
6293                 };\r
6294 \r
6295                 // Private methods\r
6296 \r
6297                 function _getSelectedNode(container, offset) {\r
6298                         var child;\r
6299 \r
6300                         if (container.nodeType == 3 /* TEXT_NODE */)\r
6301                                 return container;\r
6302 \r
6303                         if (offset < 0)\r
6304                                 return container;\r
6305 \r
6306                         child = container.firstChild;\r
6307                         while (child && offset > 0) {\r
6308                                 --offset;\r
6309                                 child = child.nextSibling;\r
6310                         }\r
6311 \r
6312                         if (child)\r
6313                                 return child;\r
6314 \r
6315                         return container;\r
6316                 };\r
6317 \r
6318                 function _isCollapsed() {\r
6319                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);\r
6320                 };\r
6321 \r
6322                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {\r
6323                         var c, offsetC, n, cmnRoot, childA, childB;\r
6324                         \r
6325                         // In the first case the boundary-points have the same container. A is before B\r
6326                         // if its offset is less than the offset of B, A is equal to B if its offset is\r
6327                         // equal to the offset of B, and A is after B if its offset is greater than the\r
6328                         // offset of B.\r
6329                         if (containerA == containerB) {\r
6330                                 if (offsetA == offsetB)\r
6331                                         return 0; // equal\r
6332 \r
6333                                 if (offsetA < offsetB)\r
6334                                         return -1; // before\r
6335 \r
6336                                 return 1; // after\r
6337                         }\r
6338 \r
6339                         // In the second case a child node C of the container of A is an ancestor\r
6340                         // container of B. In this case, A is before B if the offset of A is less than or\r
6341                         // equal to the index of the child node C and A is after B otherwise.\r
6342                         c = containerB;\r
6343                         while (c && c.parentNode != containerA)\r
6344                                 c = c.parentNode;\r
6345 \r
6346                         if (c) {\r
6347                                 offsetC = 0;\r
6348                                 n = containerA.firstChild;\r
6349 \r
6350                                 while (n != c && offsetC < offsetA) {\r
6351                                         offsetC++;\r
6352                                         n = n.nextSibling;\r
6353                                 }\r
6354 \r
6355                                 if (offsetA <= offsetC)\r
6356                                         return -1; // before\r
6357 \r
6358                                 return 1; // after\r
6359                         }\r
6360 \r
6361                         // In the third case a child node C of the container of B is an ancestor container\r
6362                         // of A. In this case, A is before B if the index of the child node C is less than\r
6363                         // the offset of B and A is after B otherwise.\r
6364                         c = containerA;\r
6365                         while (c && c.parentNode != containerB) {\r
6366                                 c = c.parentNode;\r
6367                         }\r
6368 \r
6369                         if (c) {\r
6370                                 offsetC = 0;\r
6371                                 n = containerB.firstChild;\r
6372 \r
6373                                 while (n != c && offsetC < offsetB) {\r
6374                                         offsetC++;\r
6375                                         n = n.nextSibling;\r
6376                                 }\r
6377 \r
6378                                 if (offsetC < offsetB)\r
6379                                         return -1; // before\r
6380 \r
6381                                 return 1; // after\r
6382                         }\r
6383 \r
6384                         // In the fourth case, none of three other cases hold: the containers of A and B\r
6385                         // are siblings or descendants of sibling nodes. In this case, A is before B if\r
6386                         // the container of A is before the container of B in a pre-order traversal of the\r
6387                         // Ranges' context tree and A is after B otherwise.\r
6388                         cmnRoot = dom.findCommonAncestor(containerA, containerB);\r
6389                         childA = containerA;\r
6390 \r
6391                         while (childA && childA.parentNode != cmnRoot)\r
6392                                 childA = childA.parentNode;\r
6393 \r
6394                         if (!childA)\r
6395                                 childA = cmnRoot;\r
6396 \r
6397                         childB = containerB;\r
6398                         while (childB && childB.parentNode != cmnRoot)\r
6399                                 childB = childB.parentNode;\r
6400 \r
6401                         if (!childB)\r
6402                                 childB = cmnRoot;\r
6403 \r
6404                         if (childA == childB)\r
6405                                 return 0; // equal\r
6406 \r
6407                         n = cmnRoot.firstChild;\r
6408                         while (n) {\r
6409                                 if (n == childA)\r
6410                                         return -1; // before\r
6411 \r
6412                                 if (n == childB)\r
6413                                         return 1; // after\r
6414 \r
6415                                 n = n.nextSibling;\r
6416                         }\r
6417                 };\r
6418 \r
6419                 function _setEndPoint(st, n, o) {\r
6420                         var ec, sc;\r
6421 \r
6422                         if (st) {\r
6423                                 t[START_CONTAINER] = n;\r
6424                                 t[START_OFFSET] = o;\r
6425                         } else {\r
6426                                 t[END_CONTAINER] = n;\r
6427                                 t[END_OFFSET] = o;\r
6428                         }\r
6429 \r
6430                         // If one boundary-point of a Range is set to have a root container\r
6431                         // other than the current one for the Range, the Range is collapsed to\r
6432                         // the new position. This enforces the restriction that both boundary-\r
6433                         // points of a Range must have the same root container.\r
6434                         ec = t[END_CONTAINER];\r
6435                         while (ec.parentNode)\r
6436                                 ec = ec.parentNode;\r
6437 \r
6438                         sc = t[START_CONTAINER];\r
6439                         while (sc.parentNode)\r
6440                                 sc = sc.parentNode;\r
6441 \r
6442                         if (sc == ec) {\r
6443                                 // The start position of a Range is guaranteed to never be after the\r
6444                                 // end position. To enforce this restriction, if the start is set to\r
6445                                 // be at a position after the end, the Range is collapsed to that\r
6446                                 // position.\r
6447                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)\r
6448                                         t.collapse(st);\r
6449                         } else\r
6450                                 t.collapse(st);\r
6451 \r
6452                         t.collapsed = _isCollapsed();\r
6453                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);\r
6454                 };\r
6455 \r
6456                 function _traverse(how) {\r
6457                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;\r
6458 \r
6459                         if (t[START_CONTAINER] == t[END_CONTAINER])\r
6460                                 return _traverseSameContainer(how);\r
6461 \r
6462                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
6463                                 if (p == t[START_CONTAINER])\r
6464                                         return _traverseCommonStartContainer(c, how);\r
6465 \r
6466                                 ++endContainerDepth;\r
6467                         }\r
6468 \r
6469                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
6470                                 if (p == t[END_CONTAINER])\r
6471                                         return _traverseCommonEndContainer(c, how);\r
6472 \r
6473                                 ++startContainerDepth;\r
6474                         }\r
6475 \r
6476                         depthDiff = startContainerDepth - endContainerDepth;\r
6477 \r
6478                         startNode = t[START_CONTAINER];\r
6479                         while (depthDiff > 0) {\r
6480                                 startNode = startNode.parentNode;\r
6481                                 depthDiff--;\r
6482                         }\r
6483 \r
6484                         endNode = t[END_CONTAINER];\r
6485                         while (depthDiff < 0) {\r
6486                                 endNode = endNode.parentNode;\r
6487                                 depthDiff++;\r
6488                         }\r
6489 \r
6490                         // ascend the ancestor hierarchy until we have a common parent.\r
6491                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {\r
6492                                 startNode = sp;\r
6493                                 endNode = ep;\r
6494                         }\r
6495 \r
6496                         return _traverseCommonAncestors(startNode, endNode, how);\r
6497                 };\r
6498 \r
6499                  function _traverseSameContainer(how) {\r
6500                         var frag, s, sub, n, cnt, sibling, xferNode, start, len;\r
6501 \r
6502                         if (how != DELETE)\r
6503                                 frag = createDocumentFragment();\r
6504 \r
6505                         // If selection is empty, just return the fragment\r
6506                         if (t[START_OFFSET] == t[END_OFFSET])\r
6507                                 return frag;\r
6508 \r
6509                         // Text node needs special case handling\r
6510                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {\r
6511                                 // get the substring\r
6512                                 s = t[START_CONTAINER].nodeValue;\r
6513                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);\r
6514 \r
6515                                 // set the original text node to its new value\r
6516                                 if (how != CLONE) {\r
6517                                         n = t[START_CONTAINER];\r
6518                                         start = t[START_OFFSET];\r
6519                                         len = t[END_OFFSET] - t[START_OFFSET];\r
6520 \r
6521                                         if (start === 0 && len >= n.nodeValue.length - 1) {\r
6522                                                 n.parentNode.removeChild(n);\r
6523                                         } else {\r
6524                                                 n.deleteData(start, len);\r
6525                                         }\r
6526 \r
6527                                         // Nothing is partially selected, so collapse to start point\r
6528                                         t.collapse(TRUE);\r
6529                                 }\r
6530 \r
6531                                 if (how == DELETE)\r
6532                                         return;\r
6533 \r
6534                                 if (sub.length > 0) {\r
6535                                         frag.appendChild(doc.createTextNode(sub));\r
6536                                 }\r
6537 \r
6538                                 return frag;\r
6539                         }\r
6540 \r
6541                         // Copy nodes between the start/end offsets.\r
6542                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);\r
6543                         cnt = t[END_OFFSET] - t[START_OFFSET];\r
6544 \r
6545                         while (n && cnt > 0) {\r
6546                                 sibling = n.nextSibling;\r
6547                                 xferNode = _traverseFullySelected(n, how);\r
6548 \r
6549                                 if (frag)\r
6550                                         frag.appendChild( xferNode );\r
6551 \r
6552                                 --cnt;\r
6553                                 n = sibling;\r
6554                         }\r
6555 \r
6556                         // Nothing is partially selected, so collapse to start point\r
6557                         if (how != CLONE)\r
6558                                 t.collapse(TRUE);\r
6559 \r
6560                         return frag;\r
6561                 };\r
6562 \r
6563                 function _traverseCommonStartContainer(endAncestor, how) {\r
6564                         var frag, n, endIdx, cnt, sibling, xferNode;\r
6565 \r
6566                         if (how != DELETE)\r
6567                                 frag = createDocumentFragment();\r
6568 \r
6569                         n = _traverseRightBoundary(endAncestor, how);\r
6570 \r
6571                         if (frag)\r
6572                                 frag.appendChild(n);\r
6573 \r
6574                         endIdx = nodeIndex(endAncestor);\r
6575                         cnt = endIdx - t[START_OFFSET];\r
6576 \r
6577                         if (cnt <= 0) {\r
6578                                 // Collapse to just before the endAncestor, which\r
6579                                 // is partially selected.\r
6580                                 if (how != CLONE) {\r
6581                                         t.setEndBefore(endAncestor);\r
6582                                         t.collapse(FALSE);\r
6583                                 }\r
6584 \r
6585                                 return frag;\r
6586                         }\r
6587 \r
6588                         n = endAncestor.previousSibling;\r
6589                         while (cnt > 0) {\r
6590                                 sibling = n.previousSibling;\r
6591                                 xferNode = _traverseFullySelected(n, how);\r
6592 \r
6593                                 if (frag)\r
6594                                         frag.insertBefore(xferNode, frag.firstChild);\r
6595 \r
6596                                 --cnt;\r
6597                                 n = sibling;\r
6598                         }\r
6599 \r
6600                         // Collapse to just before the endAncestor, which\r
6601                         // is partially selected.\r
6602                         if (how != CLONE) {\r
6603                                 t.setEndBefore(endAncestor);\r
6604                                 t.collapse(FALSE);\r
6605                         }\r
6606 \r
6607                         return frag;\r
6608                 };\r
6609 \r
6610                 function _traverseCommonEndContainer(startAncestor, how) {\r
6611                         var frag, startIdx, n, cnt, sibling, xferNode;\r
6612 \r
6613                         if (how != DELETE)\r
6614                                 frag = createDocumentFragment();\r
6615 \r
6616                         n = _traverseLeftBoundary(startAncestor, how);\r
6617                         if (frag)\r
6618                                 frag.appendChild(n);\r
6619 \r
6620                         startIdx = nodeIndex(startAncestor);\r
6621                         ++startIdx; // Because we already traversed it\r
6622 \r
6623                         cnt = t[END_OFFSET] - startIdx;\r
6624                         n = startAncestor.nextSibling;\r
6625                         while (n && cnt > 0) {\r
6626                                 sibling = n.nextSibling;\r
6627                                 xferNode = _traverseFullySelected(n, how);\r
6628 \r
6629                                 if (frag)\r
6630                                         frag.appendChild(xferNode);\r
6631 \r
6632                                 --cnt;\r
6633                                 n = sibling;\r
6634                         }\r
6635 \r
6636                         if (how != CLONE) {\r
6637                                 t.setStartAfter(startAncestor);\r
6638                                 t.collapse(TRUE);\r
6639                         }\r
6640 \r
6641                         return frag;\r
6642                 };\r
6643 \r
6644                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {\r
6645                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;\r
6646 \r
6647                         if (how != DELETE)\r
6648                                 frag = createDocumentFragment();\r
6649 \r
6650                         n = _traverseLeftBoundary(startAncestor, how);\r
6651                         if (frag)\r
6652                                 frag.appendChild(n);\r
6653 \r
6654                         commonParent = startAncestor.parentNode;\r
6655                         startOffset = nodeIndex(startAncestor);\r
6656                         endOffset = nodeIndex(endAncestor);\r
6657                         ++startOffset;\r
6658 \r
6659                         cnt = endOffset - startOffset;\r
6660                         sibling = startAncestor.nextSibling;\r
6661 \r
6662                         while (cnt > 0) {\r
6663                                 nextSibling = sibling.nextSibling;\r
6664                                 n = _traverseFullySelected(sibling, how);\r
6665 \r
6666                                 if (frag)\r
6667                                         frag.appendChild(n);\r
6668 \r
6669                                 sibling = nextSibling;\r
6670                                 --cnt;\r
6671                         }\r
6672 \r
6673                         n = _traverseRightBoundary(endAncestor, how);\r
6674 \r
6675                         if (frag)\r
6676                                 frag.appendChild(n);\r
6677 \r
6678                         if (how != CLONE) {\r
6679                                 t.setStartAfter(startAncestor);\r
6680                                 t.collapse(TRUE);\r
6681                         }\r
6682 \r
6683                         return frag;\r
6684                 };\r
6685 \r
6686                 function _traverseRightBoundary(root, how) {\r
6687                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];\r
6688 \r
6689                         if (next == root)\r
6690                                 return _traverseNode(next, isFullySelected, FALSE, how);\r
6691 \r
6692                         parent = next.parentNode;\r
6693                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);\r
6694 \r
6695                         while (parent) {\r
6696                                 while (next) {\r
6697                                         prevSibling = next.previousSibling;\r
6698                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);\r
6699 \r
6700                                         if (how != DELETE)\r
6701                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);\r
6702 \r
6703                                         isFullySelected = TRUE;\r
6704                                         next = prevSibling;\r
6705                                 }\r
6706 \r
6707                                 if (parent == root)\r
6708                                         return clonedParent;\r
6709 \r
6710                                 next = parent.previousSibling;\r
6711                                 parent = parent.parentNode;\r
6712 \r
6713                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);\r
6714 \r
6715                                 if (how != DELETE)\r
6716                                         clonedGrandParent.appendChild(clonedParent);\r
6717 \r
6718                                 clonedParent = clonedGrandParent;\r
6719                         }\r
6720                 };\r
6721 \r
6722                 function _traverseLeftBoundary(root, how) {\r
6723                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;\r
6724 \r
6725                         if (next == root)\r
6726                                 return _traverseNode(next, isFullySelected, TRUE, how);\r
6727 \r
6728                         parent = next.parentNode;\r
6729                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);\r
6730 \r
6731                         while (parent) {\r
6732                                 while (next) {\r
6733                                         nextSibling = next.nextSibling;\r
6734                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);\r
6735 \r
6736                                         if (how != DELETE)\r
6737                                                 clonedParent.appendChild(clonedChild);\r
6738 \r
6739                                         isFullySelected = TRUE;\r
6740                                         next = nextSibling;\r
6741                                 }\r
6742 \r
6743                                 if (parent == root)\r
6744                                         return clonedParent;\r
6745 \r
6746                                 next = parent.nextSibling;\r
6747                                 parent = parent.parentNode;\r
6748 \r
6749                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);\r
6750 \r
6751                                 if (how != DELETE)\r
6752                                         clonedGrandParent.appendChild(clonedParent);\r
6753 \r
6754                                 clonedParent = clonedGrandParent;\r
6755                         }\r
6756                 };\r
6757 \r
6758                 function _traverseNode(n, isFullySelected, isLeft, how) {\r
6759                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;\r
6760 \r
6761                         if (isFullySelected)\r
6762                                 return _traverseFullySelected(n, how);\r
6763 \r
6764                         if (n.nodeType == 3 /* TEXT_NODE */) {\r
6765                                 txtValue = n.nodeValue;\r
6766 \r
6767                                 if (isLeft) {\r
6768                                         offset = t[START_OFFSET];\r
6769                                         newNodeValue = txtValue.substring(offset);\r
6770                                         oldNodeValue = txtValue.substring(0, offset);\r
6771                                 } else {\r
6772                                         offset = t[END_OFFSET];\r
6773                                         newNodeValue = txtValue.substring(0, offset);\r
6774                                         oldNodeValue = txtValue.substring(offset);\r
6775                                 }\r
6776 \r
6777                                 if (how != CLONE)\r
6778                                         n.nodeValue = oldNodeValue;\r
6779 \r
6780                                 if (how == DELETE)\r
6781                                         return;\r
6782 \r
6783                                 newNode = dom.clone(n, FALSE);\r
6784                                 newNode.nodeValue = newNodeValue;\r
6785 \r
6786                                 return newNode;\r
6787                         }\r
6788 \r
6789                         if (how == DELETE)\r
6790                                 return;\r
6791 \r
6792                         return dom.clone(n, FALSE);\r
6793                 };\r
6794 \r
6795                 function _traverseFullySelected(n, how) {\r
6796                         if (how != DELETE)\r
6797                                 return how == CLONE ? dom.clone(n, TRUE) : n;\r
6798 \r
6799                         n.parentNode.removeChild(n);\r
6800                 };\r
6801         };\r
6802 \r
6803         ns.Range = Range;\r
6804 })(tinymce.dom);\r
6805 \r
6806 (function() {\r
6807         function Selection(selection) {\r
6808                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;\r
6809 \r
6810                 function getPosition(rng, start) {\r
6811                         var checkRng, startIndex = 0, endIndex, inside,\r
6812                                 children, child, offset, index, position = -1, parent;\r
6813 \r
6814                         // Setup test range, collapse it and get the parent\r
6815                         checkRng = rng.duplicate();\r
6816                         checkRng.collapse(start);\r
6817                         parent = checkRng.parentElement();\r
6818 \r
6819                         // Check if the selection is within the right document\r
6820                         if (parent.ownerDocument !== selection.dom.doc)\r
6821                                 return;\r
6822 \r
6823                         // IE will report non editable elements as it's parent so look for an editable one\r
6824                         while (parent.contentEditable === "false") {\r
6825                                 parent = parent.parentNode;\r
6826                         }\r
6827 \r
6828                         // If parent doesn't have any children then return that we are inside the element\r
6829                         if (!parent.hasChildNodes()) {\r
6830                                 return {node : parent, inside : 1};\r
6831                         }\r
6832 \r
6833                         // Setup node list and endIndex\r
6834                         children = parent.children;\r
6835                         endIndex = children.length - 1;\r
6836 \r
6837                         // Perform a binary search for the position\r
6838                         while (startIndex <= endIndex) {\r
6839                                 index = Math.floor((startIndex + endIndex) / 2);\r
6840 \r
6841                                 // Move selection to node and compare the ranges\r
6842                                 child = children[index];\r
6843                                 checkRng.moveToElementText(child);\r
6844                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);\r
6845 \r
6846                                 // Before/after or an exact match\r
6847                                 if (position > 0) {\r
6848                                         endIndex = index - 1;\r
6849                                 } else if (position < 0) {\r
6850                                         startIndex = index + 1;\r
6851                                 } else {\r
6852                                         return {node : child};\r
6853                                 }\r
6854                         }\r
6855 \r
6856                         // Check if child position is before or we didn't find a position\r
6857                         if (position < 0) {\r
6858                                 // No element child was found use the parent element and the offset inside that\r
6859                                 if (!child) {\r
6860                                         checkRng.moveToElementText(parent);\r
6861                                         checkRng.collapse(true);\r
6862                                         child = parent;\r
6863                                         inside = true;\r
6864                                 } else\r
6865                                         checkRng.collapse(false);\r
6866 \r
6867                                 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one\r
6868                                 // We need to walk char by char since rng.text or rng.htmlText will trim line endings\r
6869                                 offset = 0;\r
6870                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {\r
6871                                         if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {\r
6872                                                 break;\r
6873                                         }\r
6874 \r
6875                                         offset++;\r
6876                                 }\r
6877                         } else {\r
6878                                 // Child position is after the selection endpoint\r
6879                                 checkRng.collapse(true);\r
6880 \r
6881                                 // Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one\r
6882                                 offset = 0;\r
6883                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {\r
6884                                         if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {\r
6885                                                 break;\r
6886                                         }\r
6887 \r
6888                                         offset++;\r
6889                                 }\r
6890                         }\r
6891 \r
6892                         return {node : child, position : position, offset : offset, inside : inside};\r
6893                 };\r
6894 \r
6895                 // Returns a W3C DOM compatible range object by using the IE Range API\r
6896                 function getRange() {\r
6897                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;\r
6898 \r
6899                         // If selection is outside the current document just return an empty range\r
6900                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();\r
6901                         if (element.ownerDocument != dom.doc)\r
6902                                 return domRange;\r
6903 \r
6904                         collapsed = selection.isCollapsed();\r
6905 \r
6906                         // Handle control selection\r
6907                         if (ieRange.item) {\r
6908                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));\r
6909                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);\r
6910 \r
6911                                 return domRange;\r
6912                         }\r
6913 \r
6914                         function findEndPoint(start) {\r
6915                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;\r
6916 \r
6917                                 container = endPoint.node;\r
6918                                 offset = endPoint.offset;\r
6919 \r
6920                                 if (endPoint.inside && !container.hasChildNodes()) {\r
6921                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);\r
6922                                         return;\r
6923                                 }\r
6924 \r
6925                                 if (offset === undef) {\r
6926                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);\r
6927                                         return;\r
6928                                 }\r
6929 \r
6930                                 if (endPoint.position < 0) {\r
6931                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;\r
6932 \r
6933                                         if (!sibling) {\r
6934                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);\r
6935                                                 return;\r
6936                                         }\r
6937 \r
6938                                         if (!offset) {\r
6939                                                 if (sibling.nodeType == 3)\r
6940                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);\r
6941                                                 else\r
6942                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);\r
6943 \r
6944                                                 return;\r
6945                                         }\r
6946 \r
6947                                         // Find the text node and offset\r
6948                                         while (sibling) {\r
6949                                                 nodeValue = sibling.nodeValue;\r
6950                                                 textNodeOffset += nodeValue.length;\r
6951 \r
6952                                                 // We are at or passed the position we where looking for\r
6953                                                 if (textNodeOffset >= offset) {\r
6954                                                         container = sibling;\r
6955                                                         textNodeOffset -= offset;\r
6956                                                         textNodeOffset = nodeValue.length - textNodeOffset;\r
6957                                                         break;\r
6958                                                 }\r
6959 \r
6960                                                 sibling = sibling.nextSibling;\r
6961                                         }\r
6962                                 } else {\r
6963                                         // Find the text node and offset\r
6964                                         sibling = container.previousSibling;\r
6965 \r
6966                                         if (!sibling)\r
6967                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);\r
6968 \r
6969                                         // If there isn't any text to loop then use the first position\r
6970                                         if (!offset) {\r
6971                                                 if (container.nodeType == 3)\r
6972                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);\r
6973                                                 else\r
6974                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);\r
6975 \r
6976                                                 return;\r
6977                                         }\r
6978 \r
6979                                         while (sibling) {\r
6980                                                 textNodeOffset += sibling.nodeValue.length;\r
6981 \r
6982                                                 // We are at or passed the position we where looking for\r
6983                                                 if (textNodeOffset >= offset) {\r
6984                                                         container = sibling;\r
6985                                                         textNodeOffset -= offset;\r
6986                                                         break;\r
6987                                                 }\r
6988 \r
6989                                                 sibling = sibling.previousSibling;\r
6990                                         }\r
6991                                 }\r
6992 \r
6993                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);\r
6994                         };\r
6995 \r
6996                         try {\r
6997                                 // Find start point\r
6998                                 findEndPoint(true);\r
6999 \r
7000                                 // Find end point if needed\r
7001                                 if (!collapsed)\r
7002                                         findEndPoint();\r
7003                         } catch (ex) {\r
7004                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you\r
7005                                 // access the nodeValue or other properties of text nodes. This seems to happend when\r
7006                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.\r
7007                                 if (ex.number == -2147024809) {\r
7008                                         // Get the current selection\r
7009                                         bookmark = self.getBookmark(2);\r
7010 \r
7011                                         // Get start element\r
7012                                         tmpRange = ieRange.duplicate();\r
7013                                         tmpRange.collapse(true);\r
7014                                         element = tmpRange.parentElement();\r
7015 \r
7016                                         // Get end element\r
7017                                         if (!collapsed) {\r
7018                                                 tmpRange = ieRange.duplicate();\r
7019                                                 tmpRange.collapse(false);\r
7020                                                 element2 = tmpRange.parentElement();\r
7021                                                 element2.innerHTML = element2.innerHTML;\r
7022                                         }\r
7023 \r
7024                                         // Remove the broken elements\r
7025                                         element.innerHTML = element.innerHTML;\r
7026 \r
7027                                         // Restore the selection\r
7028                                         self.moveToBookmark(bookmark);\r
7029 \r
7030                                         // Since the range has moved we need to re-get it\r
7031                                         ieRange = selection.getRng();\r
7032 \r
7033                                         // Find start point\r
7034                                         findEndPoint(true);\r
7035 \r
7036                                         // Find end point if needed\r
7037                                         if (!collapsed)\r
7038                                                 findEndPoint();\r
7039                                 } else\r
7040                                         throw ex; // Throw other errors\r
7041                         }\r
7042 \r
7043                         return domRange;\r
7044                 };\r
7045 \r
7046                 this.getBookmark = function(type) {\r
7047                         var rng = selection.getRng(), start, end, bookmark = {};\r
7048 \r
7049                         function getIndexes(node) {\r
7050                                 var parent, root, children, i, indexes = [];\r
7051 \r
7052                                 parent = node.parentNode;\r
7053                                 root = dom.getRoot().parentNode;\r
7054 \r
7055                                 while (parent != root && parent.nodeType !== 9) {\r
7056                                         children = parent.children;\r
7057 \r
7058                                         i = children.length;\r
7059                                         while (i--) {\r
7060                                                 if (node === children[i]) {\r
7061                                                         indexes.push(i);\r
7062                                                         break;\r
7063                                                 }\r
7064                                         }\r
7065 \r
7066                                         node = parent;\r
7067                                         parent = parent.parentNode;\r
7068                                 }\r
7069 \r
7070                                 return indexes;\r
7071                         };\r
7072 \r
7073                         function getBookmarkEndPoint(start) {\r
7074                                 var position;\r
7075 \r
7076                                 position = getPosition(rng, start);\r
7077                                 if (position) {\r
7078                                         return {\r
7079                                                 position : position.position,\r
7080                                                 offset : position.offset,\r
7081                                                 indexes : getIndexes(position.node),\r
7082                                                 inside : position.inside\r
7083                                         };\r
7084                                 }\r
7085                         };\r
7086 \r
7087                         // Non ubstructive bookmark\r
7088                         if (type === 2) {\r
7089                                 // Handle text selection\r
7090                                 if (!rng.item) {\r
7091                                         bookmark.start = getBookmarkEndPoint(true);\r
7092 \r
7093                                         if (!selection.isCollapsed())\r
7094                                                 bookmark.end = getBookmarkEndPoint();\r
7095                                 } else\r
7096                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};\r
7097                         }\r
7098 \r
7099                         return bookmark;\r
7100                 };\r
7101 \r
7102                 this.moveToBookmark = function(bookmark) {\r
7103                         var rng, body = dom.doc.body;\r
7104 \r
7105                         function resolveIndexes(indexes) {\r
7106                                 var node, i, idx, children;\r
7107 \r
7108                                 node = dom.getRoot();\r
7109                                 for (i = indexes.length - 1; i >= 0; i--) {\r
7110                                         children = node.children;\r
7111                                         idx = indexes[i];\r
7112 \r
7113                                         if (idx <= children.length - 1) {\r
7114                                                 node = children[idx];\r
7115                                         }\r
7116                                 }\r
7117 \r
7118                                 return node;\r
7119                         };\r
7120                         \r
7121                         function setBookmarkEndPoint(start) {\r
7122                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;\r
7123 \r
7124                                 if (endPoint) {\r
7125                                         moveLeft = endPoint.position > 0;\r
7126 \r
7127                                         moveRng = body.createTextRange();\r
7128                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));\r
7129 \r
7130                                         offset = endPoint.offset;\r
7131                                         if (offset !== undef) {\r
7132                                                 moveRng.collapse(endPoint.inside || moveLeft);\r
7133                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);\r
7134                                         } else\r
7135                                                 moveRng.collapse(start);\r
7136 \r
7137                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);\r
7138 \r
7139                                         if (start)\r
7140                                                 rng.collapse(true);\r
7141                                 }\r
7142                         };\r
7143 \r
7144                         if (bookmark.start) {\r
7145                                 if (bookmark.start.ctrl) {\r
7146                                         rng = body.createControlRange();\r
7147                                         rng.addElement(resolveIndexes(bookmark.start.indexes));\r
7148                                         rng.select();\r
7149                                 } else {\r
7150                                         rng = body.createTextRange();\r
7151                                         setBookmarkEndPoint(true);\r
7152                                         setBookmarkEndPoint();\r
7153                                         rng.select();\r
7154                                 }\r
7155                         }\r
7156                 };\r
7157 \r
7158                 this.addRange = function(rng) {\r
7159                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;\r
7160 \r
7161                         function setEndPoint(start) {\r
7162                                 var container, offset, marker, tmpRng, nodes;\r
7163 \r
7164                                 marker = dom.create('a');\r
7165                                 container = start ? startContainer : endContainer;\r
7166                                 offset = start ? startOffset : endOffset;\r
7167                                 tmpRng = ieRng.duplicate();\r
7168 \r
7169                                 if (container == doc || container == doc.documentElement) {\r
7170                                         container = body;\r
7171                                         offset = 0;\r
7172                                 }\r
7173 \r
7174                                 if (container.nodeType == 3) {\r
7175                                         container.parentNode.insertBefore(marker, container);\r
7176                                         tmpRng.moveToElementText(marker);\r
7177                                         tmpRng.moveStart('character', offset);\r
7178                                         dom.remove(marker);\r
7179                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
7180                                 } else {\r
7181                                         nodes = container.childNodes;\r
7182 \r
7183                                         if (nodes.length) {\r
7184                                                 if (offset >= nodes.length) {\r
7185                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);\r
7186                                                 } else {\r
7187                                                         container.insertBefore(marker, nodes[offset]);\r
7188                                                 }\r
7189 \r
7190                                                 tmpRng.moveToElementText(marker);\r
7191                                         } else if (container.canHaveHTML) {\r
7192                                                 // Empty node selection for example <div>|</div>\r
7193                                                 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open\r
7194                                                 container.innerHTML = '<span>\uFEFF</span>';\r
7195                                                 marker = container.firstChild;\r
7196                                                 tmpRng.moveToElementText(marker);\r
7197                                                 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason\r
7198                                         }\r
7199 \r
7200                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
7201                                         dom.remove(marker);\r
7202                                 }\r
7203                         }\r
7204 \r
7205                         // Setup some shorter versions\r
7206                         startContainer = rng.startContainer;\r
7207                         startOffset = rng.startOffset;\r
7208                         endContainer = rng.endContainer;\r
7209                         endOffset = rng.endOffset;\r
7210                         ieRng = body.createTextRange();\r
7211 \r
7212                         // If single element selection then try making a control selection out of it\r
7213                         if (startContainer == endContainer && startContainer.nodeType == 1) {\r
7214                                 // Trick to place the caret inside an empty block element like <p></p>\r
7215                                 if (startOffset == endOffset && !startContainer.hasChildNodes()) {\r
7216                                         if (startContainer.canHaveHTML) {\r
7217                                                 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';\r
7218                                                 ieRng.moveToElementText(startContainer.lastChild);\r
7219                                                 ieRng.select();\r
7220                                                 dom.doc.selection.clear();\r
7221                                                 startContainer.innerHTML = '';\r
7222                                                 return;\r
7223                                         } else {\r
7224                                                 startOffset = dom.nodeIndex(startContainer);\r
7225                                                 startContainer = startContainer.parentNode;\r
7226                                         }\r
7227                                 }\r
7228 \r
7229                                 if (startOffset == endOffset - 1) {\r
7230                                         try {\r
7231                                                 ctrlRng = body.createControlRange();\r
7232                                                 ctrlRng.addElement(startContainer.childNodes[startOffset]);\r
7233                                                 ctrlRng.select();\r
7234                                                 return;\r
7235                                         } catch (ex) {\r
7236                                                 // Ignore\r
7237                                         }\r
7238                                 }\r
7239                         }\r
7240 \r
7241                         // Set start/end point of selection\r
7242                         setEndPoint(true);\r
7243                         setEndPoint();\r
7244 \r
7245                         // Select the new range and scroll it into view\r
7246                         ieRng.select();\r
7247                 };\r
7248 \r
7249                 // Expose range method\r
7250                 this.getRangeAt = getRange;\r
7251         };\r
7252 \r
7253         // Expose the selection object\r
7254         tinymce.dom.TridentSelection = Selection;\r
7255 })();\r
7256 \r
7257 \r
7258 /*\r
7259  * Sizzle CSS Selector Engine\r
7260  *  Copyright, The Dojo Foundation\r
7261  *  Released under the MIT, BSD, and GPL Licenses.\r
7262  *  More information: http://sizzlejs.com/\r
7263  */\r
7264 (function(){\r
7265 \r
7266 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,\r
7267         expando = "sizcache",\r
7268         done = 0,\r
7269         toString = Object.prototype.toString,\r
7270         hasDuplicate = false,\r
7271         baseHasDuplicate = true,\r
7272         rBackslash = /\\/g,\r
7273         rReturn = /\r\n/g,\r
7274         rNonWord = /\W/;\r
7275 \r
7276 // Here we check if the JavaScript engine is using some sort of\r
7277 // optimization where it does not always call our comparision\r
7278 // function. If that is the case, discard the hasDuplicate value.\r
7279 //   Thus far that includes Google Chrome.\r
7280 [0, 0].sort(function() {\r
7281         baseHasDuplicate = false;\r
7282         return 0;\r
7283 });\r
7284 \r
7285 var Sizzle = function( selector, context, results, seed ) {\r
7286         results = results || [];\r
7287         context = context || document;\r
7288 \r
7289         var origContext = context;\r
7290 \r
7291         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {\r
7292                 return [];\r
7293         }\r
7294 \r
7295         if ( !selector || typeof selector !== "string" ) {\r
7296                 return results;\r
7297         }\r
7298 \r
7299         var m, set, checkSet, extra, ret, cur, pop, i,\r
7300                 prune = true,\r
7301                 contextXML = Sizzle.isXML( context ),\r
7302                 parts = [],\r
7303                 soFar = selector;\r
7304 \r
7305         // Reset the position of the chunker regexp (start from head)\r
7306         do {\r
7307                 chunker.exec( "" );\r
7308                 m = chunker.exec( soFar );\r
7309 \r
7310                 if ( m ) {\r
7311                         soFar = m[3];\r
7312 \r
7313                         parts.push( m[1] );\r
7314 \r
7315                         if ( m[2] ) {\r
7316                                 extra = m[3];\r
7317                                 break;\r
7318                         }\r
7319                 }\r
7320         } while ( m );\r
7321 \r
7322         if ( parts.length > 1 && origPOS.exec( selector ) ) {\r
7323 \r
7324                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {\r
7325                         set = posProcess( parts[0] + parts[1], context, seed );\r
7326 \r
7327                 } else {\r
7328                         set = Expr.relative[ parts[0] ] ?\r
7329                                 [ context ] :\r
7330                                 Sizzle( parts.shift(), context );\r
7331 \r
7332                         while ( parts.length ) {\r
7333                                 selector = parts.shift();\r
7334 \r
7335                                 if ( Expr.relative[ selector ] ) {\r
7336                                         selector += parts.shift();\r
7337                                 }\r
7338 \r
7339                                 set = posProcess( selector, set, seed );\r
7340                         }\r
7341                 }\r
7342 \r
7343         } else {\r
7344                 // Take a shortcut and set the context if the root selector is an ID\r
7345                 // (but not if it'll be faster if the inner selector is an ID)\r
7346                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&\r
7347                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {\r
7348 \r
7349                         ret = Sizzle.find( parts.shift(), context, contextXML );\r
7350                         context = ret.expr ?\r
7351                                 Sizzle.filter( ret.expr, ret.set )[0] :\r
7352                                 ret.set[0];\r
7353                 }\r
7354 \r
7355                 if ( context ) {\r
7356                         ret = seed ?\r
7357                                 { expr: parts.pop(), set: makeArray(seed) } :\r
7358                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );\r
7359 \r
7360                         set = ret.expr ?\r
7361                                 Sizzle.filter( ret.expr, ret.set ) :\r
7362                                 ret.set;\r
7363 \r
7364                         if ( parts.length > 0 ) {\r
7365                                 checkSet = makeArray( set );\r
7366 \r
7367                         } else {\r
7368                                 prune = false;\r
7369                         }\r
7370 \r
7371                         while ( parts.length ) {\r
7372                                 cur = parts.pop();\r
7373                                 pop = cur;\r
7374 \r
7375                                 if ( !Expr.relative[ cur ] ) {\r
7376                                         cur = "";\r
7377                                 } else {\r
7378                                         pop = parts.pop();\r
7379                                 }\r
7380 \r
7381                                 if ( pop == null ) {\r
7382                                         pop = context;\r
7383                                 }\r
7384 \r
7385                                 Expr.relative[ cur ]( checkSet, pop, contextXML );\r
7386                         }\r
7387 \r
7388                 } else {\r
7389                         checkSet = parts = [];\r
7390                 }\r
7391         }\r
7392 \r
7393         if ( !checkSet ) {\r
7394                 checkSet = set;\r
7395         }\r
7396 \r
7397         if ( !checkSet ) {\r
7398                 Sizzle.error( cur || selector );\r
7399         }\r
7400 \r
7401         if ( toString.call(checkSet) === "[object Array]" ) {\r
7402                 if ( !prune ) {\r
7403                         results.push.apply( results, checkSet );\r
7404 \r
7405                 } else if ( context && context.nodeType === 1 ) {\r
7406                         for ( i = 0; checkSet[i] != null; i++ ) {\r
7407                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {\r
7408                                         results.push( set[i] );\r
7409                                 }\r
7410                         }\r
7411 \r
7412                 } else {\r
7413                         for ( i = 0; checkSet[i] != null; i++ ) {\r
7414                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {\r
7415                                         results.push( set[i] );\r
7416                                 }\r
7417                         }\r
7418                 }\r
7419 \r
7420         } else {\r
7421                 makeArray( checkSet, results );\r
7422         }\r
7423 \r
7424         if ( extra ) {\r
7425                 Sizzle( extra, origContext, results, seed );\r
7426                 Sizzle.uniqueSort( results );\r
7427         }\r
7428 \r
7429         return results;\r
7430 };\r
7431 \r
7432 Sizzle.uniqueSort = function( results ) {\r
7433         if ( sortOrder ) {\r
7434                 hasDuplicate = baseHasDuplicate;\r
7435                 results.sort( sortOrder );\r
7436 \r
7437                 if ( hasDuplicate ) {\r
7438                         for ( var i = 1; i < results.length; i++ ) {\r
7439                                 if ( results[i] === results[ i - 1 ] ) {\r
7440                                         results.splice( i--, 1 );\r
7441                                 }\r
7442                         }\r
7443                 }\r
7444         }\r
7445 \r
7446         return results;\r
7447 };\r
7448 \r
7449 Sizzle.matches = function( expr, set ) {\r
7450         return Sizzle( expr, null, null, set );\r
7451 };\r
7452 \r
7453 Sizzle.matchesSelector = function( node, expr ) {\r
7454         return Sizzle( expr, null, null, [node] ).length > 0;\r
7455 };\r
7456 \r
7457 Sizzle.find = function( expr, context, isXML ) {\r
7458         var set, i, len, match, type, left;\r
7459 \r
7460         if ( !expr ) {\r
7461                 return [];\r
7462         }\r
7463 \r
7464         for ( i = 0, len = Expr.order.length; i < len; i++ ) {\r
7465                 type = Expr.order[i];\r
7466 \r
7467                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {\r
7468                         left = match[1];\r
7469                         match.splice( 1, 1 );\r
7470 \r
7471                         if ( left.substr( left.length - 1 ) !== "\\" ) {\r
7472                                 match[1] = (match[1] || "").replace( rBackslash, "" );\r
7473                                 set = Expr.find[ type ]( match, context, isXML );\r
7474 \r
7475                                 if ( set != null ) {\r
7476                                         expr = expr.replace( Expr.match[ type ], "" );\r
7477                                         break;\r
7478                                 }\r
7479                         }\r
7480                 }\r
7481         }\r
7482 \r
7483         if ( !set ) {\r
7484                 set = typeof context.getElementsByTagName !== "undefined" ?\r
7485                         context.getElementsByTagName( "*" ) :\r
7486                         [];\r
7487         }\r
7488 \r
7489         return { set: set, expr: expr };\r
7490 };\r
7491 \r
7492 Sizzle.filter = function( expr, set, inplace, not ) {\r
7493         var match, anyFound,\r
7494                 type, found, item, filter, left,\r
7495                 i, pass,\r
7496                 old = expr,\r
7497                 result = [],\r
7498                 curLoop = set,\r
7499                 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );\r
7500 \r
7501         while ( expr && set.length ) {\r
7502                 for ( type in Expr.filter ) {\r
7503                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {\r
7504                                 filter = Expr.filter[ type ];\r
7505                                 left = match[1];\r
7506 \r
7507                                 anyFound = false;\r
7508 \r
7509                                 match.splice(1,1);\r
7510 \r
7511                                 if ( left.substr( left.length - 1 ) === "\\" ) {\r
7512                                         continue;\r
7513                                 }\r
7514 \r
7515                                 if ( curLoop === result ) {\r
7516                                         result = [];\r
7517                                 }\r
7518 \r
7519                                 if ( Expr.preFilter[ type ] ) {\r
7520                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );\r
7521 \r
7522                                         if ( !match ) {\r
7523                                                 anyFound = found = true;\r
7524 \r
7525                                         } else if ( match === true ) {\r
7526                                                 continue;\r
7527                                         }\r
7528                                 }\r
7529 \r
7530                                 if ( match ) {\r
7531                                         for ( i = 0; (item = curLoop[i]) != null; i++ ) {\r
7532                                                 if ( item ) {\r
7533                                                         found = filter( item, match, i, curLoop );\r
7534                                                         pass = not ^ found;\r
7535 \r
7536                                                         if ( inplace && found != null ) {\r
7537                                                                 if ( pass ) {\r
7538                                                                         anyFound = true;\r
7539 \r
7540                                                                 } else {\r
7541                                                                         curLoop[i] = false;\r
7542                                                                 }\r
7543 \r
7544                                                         } else if ( pass ) {\r
7545                                                                 result.push( item );\r
7546                                                                 anyFound = true;\r
7547                                                         }\r
7548                                                 }\r
7549                                         }\r
7550                                 }\r
7551 \r
7552                                 if ( found !== undefined ) {\r
7553                                         if ( !inplace ) {\r
7554                                                 curLoop = result;\r
7555                                         }\r
7556 \r
7557                                         expr = expr.replace( Expr.match[ type ], "" );\r
7558 \r
7559                                         if ( !anyFound ) {\r
7560                                                 return [];\r
7561                                         }\r
7562 \r
7563                                         break;\r
7564                                 }\r
7565                         }\r
7566                 }\r
7567 \r
7568                 // Improper expression\r
7569                 if ( expr === old ) {\r
7570                         if ( anyFound == null ) {\r
7571                                 Sizzle.error( expr );\r
7572 \r
7573                         } else {\r
7574                                 break;\r
7575                         }\r
7576                 }\r
7577 \r
7578                 old = expr;\r
7579         }\r
7580 \r
7581         return curLoop;\r
7582 };\r
7583 \r
7584 Sizzle.error = function( msg ) {\r
7585         throw new Error( "Syntax error, unrecognized expression: " + msg );\r
7586 };\r
7587 \r
7588 var getText = Sizzle.getText = function( elem ) {\r
7589     var i, node,\r
7590                 nodeType = elem.nodeType,\r
7591                 ret = "";\r
7592 \r
7593         if ( nodeType ) {\r
7594                 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\r
7595                         // Use textContent || innerText for elements\r
7596                         if ( typeof elem.textContent === 'string' ) {\r
7597                                 return elem.textContent;\r
7598                         } else if ( typeof elem.innerText === 'string' ) {\r
7599                                 // Replace IE's carriage returns\r
7600                                 return elem.innerText.replace( rReturn, '' );\r
7601                         } else {\r
7602                                 // Traverse it's children\r
7603                                 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {\r
7604                                         ret += getText( elem );\r
7605                                 }\r
7606                         }\r
7607                 } else if ( nodeType === 3 || nodeType === 4 ) {\r
7608                         return elem.nodeValue;\r
7609                 }\r
7610         } else {\r
7611 \r
7612                 // If no nodeType, this is expected to be an array\r
7613                 for ( i = 0; (node = elem[i]); i++ ) {\r
7614                         // Do not traverse comment nodes\r
7615                         if ( node.nodeType !== 8 ) {\r
7616                                 ret += getText( node );\r
7617                         }\r
7618                 }\r
7619         }\r
7620         return ret;\r
7621 };\r
7622 \r
7623 var Expr = Sizzle.selectors = {\r
7624         order: [ "ID", "NAME", "TAG" ],\r
7625 \r
7626         match: {\r
7627                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
7628                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
7629                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,\r
7630                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,\r
7631                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,\r
7632                 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,\r
7633                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,\r
7634                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/\r
7635         },\r
7636 \r
7637         leftMatch: {},\r
7638 \r
7639         attrMap: {\r
7640                 "class": "className",\r
7641                 "for": "htmlFor"\r
7642         },\r
7643 \r
7644         attrHandle: {\r
7645                 href: function( elem ) {\r
7646                         return elem.getAttribute( "href" );\r
7647                 },\r
7648                 type: function( elem ) {\r
7649                         return elem.getAttribute( "type" );\r
7650                 }\r
7651         },\r
7652 \r
7653         relative: {\r
7654                 "+": function(checkSet, part){\r
7655                         var isPartStr = typeof part === "string",\r
7656                                 isTag = isPartStr && !rNonWord.test( part ),\r
7657                                 isPartStrNotTag = isPartStr && !isTag;\r
7658 \r
7659                         if ( isTag ) {\r
7660                                 part = part.toLowerCase();\r
7661                         }\r
7662 \r
7663                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {\r
7664                                 if ( (elem = checkSet[i]) ) {\r
7665                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}\r
7666 \r
7667                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?\r
7668                                                 elem || false :\r
7669                                                 elem === part;\r
7670                                 }\r
7671                         }\r
7672 \r
7673                         if ( isPartStrNotTag ) {\r
7674                                 Sizzle.filter( part, checkSet, true );\r
7675                         }\r
7676                 },\r
7677 \r
7678                 ">": function( checkSet, part ) {\r
7679                         var elem,\r
7680                                 isPartStr = typeof part === "string",\r
7681                                 i = 0,\r
7682                                 l = checkSet.length;\r
7683 \r
7684                         if ( isPartStr && !rNonWord.test( part ) ) {\r
7685                                 part = part.toLowerCase();\r
7686 \r
7687                                 for ( ; i < l; i++ ) {\r
7688                                         elem = checkSet[i];\r
7689 \r
7690                                         if ( elem ) {\r
7691                                                 var parent = elem.parentNode;\r
7692                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;\r
7693                                         }\r
7694                                 }\r
7695 \r
7696                         } else {\r
7697                                 for ( ; i < l; i++ ) {\r
7698                                         elem = checkSet[i];\r
7699 \r
7700                                         if ( elem ) {\r
7701                                                 checkSet[i] = isPartStr ?\r
7702                                                         elem.parentNode :\r
7703                                                         elem.parentNode === part;\r
7704                                         }\r
7705                                 }\r
7706 \r
7707                                 if ( isPartStr ) {\r
7708                                         Sizzle.filter( part, checkSet, true );\r
7709                                 }\r
7710                         }\r
7711                 },\r
7712 \r
7713                 "": function(checkSet, part, isXML){\r
7714                         var nodeCheck,\r
7715                                 doneName = done++,\r
7716                                 checkFn = dirCheck;\r
7717 \r
7718                         if ( typeof part === "string" && !rNonWord.test( part ) ) {\r
7719                                 part = part.toLowerCase();\r
7720                                 nodeCheck = part;\r
7721                                 checkFn = dirNodeCheck;\r
7722                         }\r
7723 \r
7724                         checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );\r
7725                 },\r
7726 \r
7727                 "~": function( checkSet, part, isXML ) {\r
7728                         var nodeCheck,\r
7729                                 doneName = done++,\r
7730                                 checkFn = dirCheck;\r
7731 \r
7732                         if ( typeof part === "string" && !rNonWord.test( part ) ) {\r
7733                                 part = part.toLowerCase();\r
7734                                 nodeCheck = part;\r
7735                                 checkFn = dirNodeCheck;\r
7736                         }\r
7737 \r
7738                         checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );\r
7739                 }\r
7740         },\r
7741 \r
7742         find: {\r
7743                 ID: function( match, context, isXML ) {\r
7744                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
7745                                 var m = context.getElementById(match[1]);\r
7746                                 // Check parentNode to catch when Blackberry 4.6 returns\r
7747                                 // nodes that are no longer in the document #6963\r
7748                                 return m && m.parentNode ? [m] : [];\r
7749                         }\r
7750                 },\r
7751 \r
7752                 NAME: function( match, context ) {\r
7753                         if ( typeof context.getElementsByName !== "undefined" ) {\r
7754                                 var ret = [],\r
7755                                         results = context.getElementsByName( match[1] );\r
7756 \r
7757                                 for ( var i = 0, l = results.length; i < l; i++ ) {\r
7758                                         if ( results[i].getAttribute("name") === match[1] ) {\r
7759                                                 ret.push( results[i] );\r
7760                                         }\r
7761                                 }\r
7762 \r
7763                                 return ret.length === 0 ? null : ret;\r
7764                         }\r
7765                 },\r
7766 \r
7767                 TAG: function( match, context ) {\r
7768                         if ( typeof context.getElementsByTagName !== "undefined" ) {\r
7769                                 return context.getElementsByTagName( match[1] );\r
7770                         }\r
7771                 }\r
7772         },\r
7773         preFilter: {\r
7774                 CLASS: function( match, curLoop, inplace, result, not, isXML ) {\r
7775                         match = " " + match[1].replace( rBackslash, "" ) + " ";\r
7776 \r
7777                         if ( isXML ) {\r
7778                                 return match;\r
7779                         }\r
7780 \r
7781                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {\r
7782                                 if ( elem ) {\r
7783                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {\r
7784                                                 if ( !inplace ) {\r
7785                                                         result.push( elem );\r
7786                                                 }\r
7787 \r
7788                                         } else if ( inplace ) {\r
7789                                                 curLoop[i] = false;\r
7790                                         }\r
7791                                 }\r
7792                         }\r
7793 \r
7794                         return false;\r
7795                 },\r
7796 \r
7797                 ID: function( match ) {\r
7798                         return match[1].replace( rBackslash, "" );\r
7799                 },\r
7800 \r
7801                 TAG: function( match, curLoop ) {\r
7802                         return match[1].replace( rBackslash, "" ).toLowerCase();\r
7803                 },\r
7804 \r
7805                 CHILD: function( match ) {\r
7806                         if ( match[1] === "nth" ) {\r
7807                                 if ( !match[2] ) {\r
7808                                         Sizzle.error( match[0] );\r
7809                                 }\r
7810 \r
7811                                 match[2] = match[2].replace(/^\+|\s*/g, '');\r
7812 \r
7813                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'\r
7814                                 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(\r
7815                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||\r
7816                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);\r
7817 \r
7818                                 // calculate the numbers (first)n+(last) including if they are negative\r
7819                                 match[2] = (test[1] + (test[2] || 1)) - 0;\r
7820                                 match[3] = test[3] - 0;\r
7821                         }\r
7822                         else if ( match[2] ) {\r
7823                                 Sizzle.error( match[0] );\r
7824                         }\r
7825 \r
7826                         // TODO: Move to normal caching system\r
7827                         match[0] = done++;\r
7828 \r
7829                         return match;\r
7830                 },\r
7831 \r
7832                 ATTR: function( match, curLoop, inplace, result, not, isXML ) {\r
7833                         var name = match[1] = match[1].replace( rBackslash, "" );\r
7834 \r
7835                         if ( !isXML && Expr.attrMap[name] ) {\r
7836                                 match[1] = Expr.attrMap[name];\r
7837                         }\r
7838 \r
7839                         // Handle if an un-quoted value was used\r
7840                         match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );\r
7841 \r
7842                         if ( match[2] === "~=" ) {\r
7843                                 match[4] = " " + match[4] + " ";\r
7844                         }\r
7845 \r
7846                         return match;\r
7847                 },\r
7848 \r
7849                 PSEUDO: function( match, curLoop, inplace, result, not ) {\r
7850                         if ( match[1] === "not" ) {\r
7851                                 // If we're dealing with a complex expression, or a simple one\r
7852                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {\r
7853                                         match[3] = Sizzle(match[3], null, null, curLoop);\r
7854 \r
7855                                 } else {\r
7856                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);\r
7857 \r
7858                                         if ( !inplace ) {\r
7859                                                 result.push.apply( result, ret );\r
7860                                         }\r
7861 \r
7862                                         return false;\r
7863                                 }\r
7864 \r
7865                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {\r
7866                                 return true;\r
7867                         }\r
7868 \r
7869                         return match;\r
7870                 },\r
7871 \r
7872                 POS: function( match ) {\r
7873                         match.unshift( true );\r
7874 \r
7875                         return match;\r
7876                 }\r
7877         },\r
7878 \r
7879         filters: {\r
7880                 enabled: function( elem ) {\r
7881                         return elem.disabled === false && elem.type !== "hidden";\r
7882                 },\r
7883 \r
7884                 disabled: function( elem ) {\r
7885                         return elem.disabled === true;\r
7886                 },\r
7887 \r
7888                 checked: function( elem ) {\r
7889                         return elem.checked === true;\r
7890                 },\r
7891 \r
7892                 selected: function( elem ) {\r
7893                         // Accessing this property makes selected-by-default\r
7894                         // options in Safari work properly\r
7895                         if ( elem.parentNode ) {\r
7896                                 elem.parentNode.selectedIndex;\r
7897                         }\r
7898 \r
7899                         return elem.selected === true;\r
7900                 },\r
7901 \r
7902                 parent: function( elem ) {\r
7903                         return !!elem.firstChild;\r
7904                 },\r
7905 \r
7906                 empty: function( elem ) {\r
7907                         return !elem.firstChild;\r
7908                 },\r
7909 \r
7910                 has: function( elem, i, match ) {\r
7911                         return !!Sizzle( match[3], elem ).length;\r
7912                 },\r
7913 \r
7914                 header: function( elem ) {\r
7915                         return (/h\d/i).test( elem.nodeName );\r
7916                 },\r
7917 \r
7918                 text: function( elem ) {\r
7919                         var attr = elem.getAttribute( "type" ), type = elem.type;\r
7920                         // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\r
7921                         // use getAttribute instead to test this case\r
7922                         return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );\r
7923                 },\r
7924 \r
7925                 radio: function( elem ) {\r
7926                         return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;\r
7927                 },\r
7928 \r
7929                 checkbox: function( elem ) {\r
7930                         return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;\r
7931                 },\r
7932 \r
7933                 file: function( elem ) {\r
7934                         return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;\r
7935                 },\r
7936 \r
7937                 password: function( elem ) {\r
7938                         return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;\r
7939                 },\r
7940 \r
7941                 submit: function( elem ) {\r
7942                         var name = elem.nodeName.toLowerCase();\r
7943                         return (name === "input" || name === "button") && "submit" === elem.type;\r
7944                 },\r
7945 \r
7946                 image: function( elem ) {\r
7947                         return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;\r
7948                 },\r
7949 \r
7950                 reset: function( elem ) {\r
7951                         var name = elem.nodeName.toLowerCase();\r
7952                         return (name === "input" || name === "button") && "reset" === elem.type;\r
7953                 },\r
7954 \r
7955                 button: function( elem ) {\r
7956                         var name = elem.nodeName.toLowerCase();\r
7957                         return name === "input" && "button" === elem.type || name === "button";\r
7958                 },\r
7959 \r
7960                 input: function( elem ) {\r
7961                         return (/input|select|textarea|button/i).test( elem.nodeName );\r
7962                 },\r
7963 \r
7964                 focus: function( elem ) {\r
7965                         return elem === elem.ownerDocument.activeElement;\r
7966                 }\r
7967         },\r
7968         setFilters: {\r
7969                 first: function( elem, i ) {\r
7970                         return i === 0;\r
7971                 },\r
7972 \r
7973                 last: function( elem, i, match, array ) {\r
7974                         return i === array.length - 1;\r
7975                 },\r
7976 \r
7977                 even: function( elem, i ) {\r
7978                         return i % 2 === 0;\r
7979                 },\r
7980 \r
7981                 odd: function( elem, i ) {\r
7982                         return i % 2 === 1;\r
7983                 },\r
7984 \r
7985                 lt: function( elem, i, match ) {\r
7986                         return i < match[3] - 0;\r
7987                 },\r
7988 \r
7989                 gt: function( elem, i, match ) {\r
7990                         return i > match[3] - 0;\r
7991                 },\r
7992 \r
7993                 nth: function( elem, i, match ) {\r
7994                         return match[3] - 0 === i;\r
7995                 },\r
7996 \r
7997                 eq: function( elem, i, match ) {\r
7998                         return match[3] - 0 === i;\r
7999                 }\r
8000         },\r
8001         filter: {\r
8002                 PSEUDO: function( elem, match, i, array ) {\r
8003                         var name = match[1],\r
8004                                 filter = Expr.filters[ name ];\r
8005 \r
8006                         if ( filter ) {\r
8007                                 return filter( elem, i, match, array );\r
8008 \r
8009                         } else if ( name === "contains" ) {\r
8010                                 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;\r
8011 \r
8012                         } else if ( name === "not" ) {\r
8013                                 var not = match[3];\r
8014 \r
8015                                 for ( var j = 0, l = not.length; j < l; j++ ) {\r
8016                                         if ( not[j] === elem ) {\r
8017                                                 return false;\r
8018                                         }\r
8019                                 }\r
8020 \r
8021                                 return true;\r
8022 \r
8023                         } else {\r
8024                                 Sizzle.error( name );\r
8025                         }\r
8026                 },\r
8027 \r
8028                 CHILD: function( elem, match ) {\r
8029                         var first, last,\r
8030                                 doneName, parent, cache,\r
8031                                 count, diff,\r
8032                                 type = match[1],\r
8033                                 node = elem;\r
8034 \r
8035                         switch ( type ) {\r
8036                                 case "only":\r
8037                                 case "first":\r
8038                                         while ( (node = node.previousSibling) ) {\r
8039                                                 if ( node.nodeType === 1 ) {\r
8040                                                         return false;\r
8041                                                 }\r
8042                                         }\r
8043 \r
8044                                         if ( type === "first" ) {\r
8045                                                 return true;\r
8046                                         }\r
8047 \r
8048                                         node = elem;\r
8049 \r
8050                                         /* falls through */\r
8051                                 case "last":\r
8052                                         while ( (node = node.nextSibling) ) {\r
8053                                                 if ( node.nodeType === 1 ) {\r
8054                                                         return false;\r
8055                                                 }\r
8056                                         }\r
8057 \r
8058                                         return true;\r
8059 \r
8060                                 case "nth":\r
8061                                         first = match[2];\r
8062                                         last = match[3];\r
8063 \r
8064                                         if ( first === 1 && last === 0 ) {\r
8065                                                 return true;\r
8066                                         }\r
8067 \r
8068                                         doneName = match[0];\r
8069                                         parent = elem.parentNode;\r
8070 \r
8071                                         if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {\r
8072                                                 count = 0;\r
8073 \r
8074                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
8075                                                         if ( node.nodeType === 1 ) {\r
8076                                                                 node.nodeIndex = ++count;\r
8077                                                         }\r
8078                                                 }\r
8079 \r
8080                                                 parent[ expando ] = doneName;\r
8081                                         }\r
8082 \r
8083                                         diff = elem.nodeIndex - last;\r
8084 \r
8085                                         if ( first === 0 ) {\r
8086                                                 return diff === 0;\r
8087 \r
8088                                         } else {\r
8089                                                 return ( diff % first === 0 && diff / first >= 0 );\r
8090                                         }\r
8091                         }\r
8092                 },\r
8093 \r
8094                 ID: function( elem, match ) {\r
8095                         return elem.nodeType === 1 && elem.getAttribute("id") === match;\r
8096                 },\r
8097 \r
8098                 TAG: function( elem, match ) {\r
8099                         return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;\r
8100                 },\r
8101 \r
8102                 CLASS: function( elem, match ) {\r
8103                         return (" " + (elem.className || elem.getAttribute("class")) + " ")\r
8104                                 .indexOf( match ) > -1;\r
8105                 },\r
8106 \r
8107                 ATTR: function( elem, match ) {\r
8108                         var name = match[1],\r
8109                                 result = Sizzle.attr ?\r
8110                                         Sizzle.attr( elem, name ) :\r
8111                                         Expr.attrHandle[ name ] ?\r
8112                                         Expr.attrHandle[ name ]( elem ) :\r
8113                                         elem[ name ] != null ?\r
8114                                                 elem[ name ] :\r
8115                                                 elem.getAttribute( name ),\r
8116                                 value = result + "",\r
8117                                 type = match[2],\r
8118                                 check = match[4];\r
8119 \r
8120                         return result == null ?\r
8121                                 type === "!=" :\r
8122                                 !type && Sizzle.attr ?\r
8123                                 result != null :\r
8124                                 type === "=" ?\r
8125                                 value === check :\r
8126                                 type === "*=" ?\r
8127                                 value.indexOf(check) >= 0 :\r
8128                                 type === "~=" ?\r
8129                                 (" " + value + " ").indexOf(check) >= 0 :\r
8130                                 !check ?\r
8131                                 value && result !== false :\r
8132                                 type === "!=" ?\r
8133                                 value !== check :\r
8134                                 type === "^=" ?\r
8135                                 value.indexOf(check) === 0 :\r
8136                                 type === "$=" ?\r
8137                                 value.substr(value.length - check.length) === check :\r
8138                                 type === "|=" ?\r
8139                                 value === check || value.substr(0, check.length + 1) === check + "-" :\r
8140                                 false;\r
8141                 },\r
8142 \r
8143                 POS: function( elem, match, i, array ) {\r
8144                         var name = match[2],\r
8145                                 filter = Expr.setFilters[ name ];\r
8146 \r
8147                         if ( filter ) {\r
8148                                 return filter( elem, i, match, array );\r
8149                         }\r
8150                 }\r
8151         }\r
8152 };\r
8153 \r
8154 var origPOS = Expr.match.POS,\r
8155         fescape = function(all, num){\r
8156                 return "\\" + (num - 0 + 1);\r
8157         };\r
8158 \r
8159 for ( var type in Expr.match ) {\r
8160         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );\r
8161         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );\r
8162 }\r
8163 // Expose origPOS\r
8164 // "global" as in regardless of relation to brackets/parens\r
8165 Expr.match.globalPOS = origPOS;\r
8166 \r
8167 var makeArray = function( array, results ) {\r
8168         array = Array.prototype.slice.call( array, 0 );\r
8169 \r
8170         if ( results ) {\r
8171                 results.push.apply( results, array );\r
8172                 return results;\r
8173         }\r
8174 \r
8175         return array;\r
8176 };\r
8177 \r
8178 // Perform a simple check to determine if the browser is capable of\r
8179 // converting a NodeList to an array using builtin methods.\r
8180 // Also verifies that the returned array holds DOM nodes\r
8181 // (which is not the case in the Blackberry browser)\r
8182 try {\r
8183         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;\r
8184 \r
8185 // Provide a fallback method if it does not work\r
8186 } catch( e ) {\r
8187         makeArray = function( array, results ) {\r
8188                 var i = 0,\r
8189                         ret = results || [];\r
8190 \r
8191                 if ( toString.call(array) === "[object Array]" ) {\r
8192                         Array.prototype.push.apply( ret, array );\r
8193 \r
8194                 } else {\r
8195                         if ( typeof array.length === "number" ) {\r
8196                                 for ( var l = array.length; i < l; i++ ) {\r
8197                                         ret.push( array[i] );\r
8198                                 }\r
8199 \r
8200                         } else {\r
8201                                 for ( ; array[i]; i++ ) {\r
8202                                         ret.push( array[i] );\r
8203                                 }\r
8204                         }\r
8205                 }\r
8206 \r
8207                 return ret;\r
8208         };\r
8209 }\r
8210 \r
8211 var sortOrder, siblingCheck;\r
8212 \r
8213 if ( document.documentElement.compareDocumentPosition ) {\r
8214         sortOrder = function( a, b ) {\r
8215                 if ( a === b ) {\r
8216                         hasDuplicate = true;\r
8217                         return 0;\r
8218                 }\r
8219 \r
8220                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {\r
8221                         return a.compareDocumentPosition ? -1 : 1;\r
8222                 }\r
8223 \r
8224                 return a.compareDocumentPosition(b) & 4 ? -1 : 1;\r
8225         };\r
8226 \r
8227 } else {\r
8228         sortOrder = function( a, b ) {\r
8229                 // The nodes are identical, we can exit early\r
8230                 if ( a === b ) {\r
8231                         hasDuplicate = true;\r
8232                         return 0;\r
8233 \r
8234                 // Fallback to using sourceIndex (in IE) if it's available on both nodes\r
8235                 } else if ( a.sourceIndex && b.sourceIndex ) {\r
8236                         return a.sourceIndex - b.sourceIndex;\r
8237                 }\r
8238 \r
8239                 var al, bl,\r
8240                         ap = [],\r
8241                         bp = [],\r
8242                         aup = a.parentNode,\r
8243                         bup = b.parentNode,\r
8244                         cur = aup;\r
8245 \r
8246                 // If the nodes are siblings (or identical) we can do a quick check\r
8247                 if ( aup === bup ) {\r
8248                         return siblingCheck( a, b );\r
8249 \r
8250                 // If no parents were found then the nodes are disconnected\r
8251                 } else if ( !aup ) {\r
8252                         return -1;\r
8253 \r
8254                 } else if ( !bup ) {\r
8255                         return 1;\r
8256                 }\r
8257 \r
8258                 // Otherwise they're somewhere else in the tree so we need\r
8259                 // to build up a full list of the parentNodes for comparison\r
8260                 while ( cur ) {\r
8261                         ap.unshift( cur );\r
8262                         cur = cur.parentNode;\r
8263                 }\r
8264 \r
8265                 cur = bup;\r
8266 \r
8267                 while ( cur ) {\r
8268                         bp.unshift( cur );\r
8269                         cur = cur.parentNode;\r
8270                 }\r
8271 \r
8272                 al = ap.length;\r
8273                 bl = bp.length;\r
8274 \r
8275                 // Start walking down the tree looking for a discrepancy\r
8276                 for ( var i = 0; i < al && i < bl; i++ ) {\r
8277                         if ( ap[i] !== bp[i] ) {\r
8278                                 return siblingCheck( ap[i], bp[i] );\r
8279                         }\r
8280                 }\r
8281 \r
8282                 // We ended someplace up the tree so do a sibling check\r
8283                 return i === al ?\r
8284                         siblingCheck( a, bp[i], -1 ) :\r
8285                         siblingCheck( ap[i], b, 1 );\r
8286         };\r
8287 \r
8288         siblingCheck = function( a, b, ret ) {\r
8289                 if ( a === b ) {\r
8290                         return ret;\r
8291                 }\r
8292 \r
8293                 var cur = a.nextSibling;\r
8294 \r
8295                 while ( cur ) {\r
8296                         if ( cur === b ) {\r
8297                                 return -1;\r
8298                         }\r
8299 \r
8300                         cur = cur.nextSibling;\r
8301                 }\r
8302 \r
8303                 return 1;\r
8304         };\r
8305 }\r
8306 \r
8307 // Check to see if the browser returns elements by name when\r
8308 // querying by getElementById (and provide a workaround)\r
8309 (function(){\r
8310         // We're going to inject a fake input element with a specified name\r
8311         var form = document.createElement("div"),\r
8312                 id = "script" + (new Date()).getTime(),\r
8313                 root = document.documentElement;\r
8314 \r
8315         form.innerHTML = "<a name='" + id + "'/>";\r
8316 \r
8317         // Inject it into the root element, check its status, and remove it quickly\r
8318         root.insertBefore( form, root.firstChild );\r
8319 \r
8320         // The workaround has to do additional checks after a getElementById\r
8321         // Which slows things down for other browsers (hence the branching)\r
8322         if ( document.getElementById( id ) ) {\r
8323                 Expr.find.ID = function( match, context, isXML ) {\r
8324                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
8325                                 var m = context.getElementById(match[1]);\r
8326 \r
8327                                 return m ?\r
8328                                         m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?\r
8329                                                 [m] :\r
8330                                                 undefined :\r
8331                                         [];\r
8332                         }\r
8333                 };\r
8334 \r
8335                 Expr.filter.ID = function( elem, match ) {\r
8336                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");\r
8337 \r
8338                         return elem.nodeType === 1 && node && node.nodeValue === match;\r
8339                 };\r
8340         }\r
8341 \r
8342         root.removeChild( form );\r
8343 \r
8344         // release memory in IE\r
8345         root = form = null;\r
8346 })();\r
8347 \r
8348 (function(){\r
8349         // Check to see if the browser returns only elements\r
8350         // when doing getElementsByTagName("*")\r
8351 \r
8352         // Create a fake element\r
8353         var div = document.createElement("div");\r
8354         div.appendChild( document.createComment("") );\r
8355 \r
8356         // Make sure no comments are found\r
8357         if ( div.getElementsByTagName("*").length > 0 ) {\r
8358                 Expr.find.TAG = function( match, context ) {\r
8359                         var results = context.getElementsByTagName( match[1] );\r
8360 \r
8361                         // Filter out possible comments\r
8362                         if ( match[1] === "*" ) {\r
8363                                 var tmp = [];\r
8364 \r
8365                                 for ( var i = 0; results[i]; i++ ) {\r
8366                                         if ( results[i].nodeType === 1 ) {\r
8367                                                 tmp.push( results[i] );\r
8368                                         }\r
8369                                 }\r
8370 \r
8371                                 results = tmp;\r
8372                         }\r
8373 \r
8374                         return results;\r
8375                 };\r
8376         }\r
8377 \r
8378         // Check to see if an attribute returns normalized href attributes\r
8379         div.innerHTML = "<a href='#'></a>";\r
8380 \r
8381         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&\r
8382                         div.firstChild.getAttribute("href") !== "#" ) {\r
8383 \r
8384                 Expr.attrHandle.href = function( elem ) {\r
8385                         return elem.getAttribute( "href", 2 );\r
8386                 };\r
8387         }\r
8388 \r
8389         // release memory in IE\r
8390         div = null;\r
8391 })();\r
8392 \r
8393 if ( document.querySelectorAll ) {\r
8394         (function(){\r
8395                 var oldSizzle = Sizzle,\r
8396                         div = document.createElement("div"),\r
8397                         id = "__sizzle__";\r
8398 \r
8399                 div.innerHTML = "<p class='TEST'></p>";\r
8400 \r
8401                 // Safari can't handle uppercase or unicode characters when\r
8402                 // in quirks mode.\r
8403                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {\r
8404                         return;\r
8405                 }\r
8406 \r
8407                 Sizzle = function( query, context, extra, seed ) {\r
8408                         context = context || document;\r
8409 \r
8410                         // Only use querySelectorAll on non-XML documents\r
8411                         // (ID selectors don't work in non-HTML documents)\r
8412                         if ( !seed && !Sizzle.isXML(context) ) {\r
8413                                 // See if we find a selector to speed up\r
8414                                 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );\r
8415 \r
8416                                 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {\r
8417                                         // Speed-up: Sizzle("TAG")\r
8418                                         if ( match[1] ) {\r
8419                                                 return makeArray( context.getElementsByTagName( query ), extra );\r
8420 \r
8421                                         // Speed-up: Sizzle(".CLASS")\r
8422                                         } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {\r
8423                                                 return makeArray( context.getElementsByClassName( match[2] ), extra );\r
8424                                         }\r
8425                                 }\r
8426 \r
8427                                 if ( context.nodeType === 9 ) {\r
8428                                         // Speed-up: Sizzle("body")\r
8429                                         // The body element only exists once, optimize finding it\r
8430                                         if ( query === "body" && context.body ) {\r
8431                                                 return makeArray( [ context.body ], extra );\r
8432 \r
8433                                         // Speed-up: Sizzle("#ID")\r
8434                                         } else if ( match && match[3] ) {\r
8435                                                 var elem = context.getElementById( match[3] );\r
8436 \r
8437                                                 // Check parentNode to catch when Blackberry 4.6 returns\r
8438                                                 // nodes that are no longer in the document #6963\r
8439                                                 if ( elem && elem.parentNode ) {\r
8440                                                         // Handle the case where IE and Opera return items\r
8441                                                         // by name instead of ID\r
8442                                                         if ( elem.id === match[3] ) {\r
8443                                                                 return makeArray( [ elem ], extra );\r
8444                                                         }\r
8445 \r
8446                                                 } else {\r
8447                                                         return makeArray( [], extra );\r
8448                                                 }\r
8449                                         }\r
8450 \r
8451                                         try {\r
8452                                                 return makeArray( context.querySelectorAll(query), extra );\r
8453                                         } catch(qsaError) {}\r
8454 \r
8455                                 // qSA works strangely on Element-rooted queries\r
8456                                 // We can work around this by specifying an extra ID on the root\r
8457                                 // and working up from there (Thanks to Andrew Dupont for the technique)\r
8458                                 // IE 8 doesn't work on object elements\r
8459                                 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {\r
8460                                         var oldContext = context,\r
8461                                                 old = context.getAttribute( "id" ),\r
8462                                                 nid = old || id,\r
8463                                                 hasParent = context.parentNode,\r
8464                                                 relativeHierarchySelector = /^\s*[+~]/.test( query );\r
8465 \r
8466                                         if ( !old ) {\r
8467                                                 context.setAttribute( "id", nid );\r
8468                                         } else {\r
8469                                                 nid = nid.replace( /'/g, "\\$&" );\r
8470                                         }\r
8471                                         if ( relativeHierarchySelector && hasParent ) {\r
8472                                                 context = context.parentNode;\r
8473                                         }\r
8474 \r
8475                                         try {\r
8476                                                 if ( !relativeHierarchySelector || hasParent ) {\r
8477                                                         return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );\r
8478                                                 }\r
8479 \r
8480                                         } catch(pseudoError) {\r
8481                                         } finally {\r
8482                                                 if ( !old ) {\r
8483                                                         oldContext.removeAttribute( "id" );\r
8484                                                 }\r
8485                                         }\r
8486                                 }\r
8487                         }\r
8488 \r
8489                         return oldSizzle(query, context, extra, seed);\r
8490                 };\r
8491 \r
8492                 for ( var prop in oldSizzle ) {\r
8493                         Sizzle[ prop ] = oldSizzle[ prop ];\r
8494                 }\r
8495 \r
8496                 // release memory in IE\r
8497                 div = null;\r
8498         })();\r
8499 }\r
8500 \r
8501 (function(){\r
8502         var html = document.documentElement,\r
8503                 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;\r
8504 \r
8505         if ( matches ) {\r
8506                 // Check to see if it's possible to do matchesSelector\r
8507                 // on a disconnected node (IE 9 fails this)\r
8508                 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),\r
8509                         pseudoWorks = false;\r
8510 \r
8511                 try {\r
8512                         // This should fail with an exception\r
8513                         // Gecko does not error, returns false instead\r
8514                         matches.call( document.documentElement, "[test!='']:sizzle" );\r
8515 \r
8516                 } catch( pseudoError ) {\r
8517                         pseudoWorks = true;\r
8518                 }\r
8519 \r
8520                 Sizzle.matchesSelector = function( node, expr ) {\r
8521                         // Make sure that attribute selectors are quoted\r
8522                         expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");\r
8523 \r
8524                         if ( !Sizzle.isXML( node ) ) {\r
8525                                 try {\r
8526                                         if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {\r
8527                                                 var ret = matches.call( node, expr );\r
8528 \r
8529                                                 // IE 9's matchesSelector returns false on disconnected nodes\r
8530                                                 if ( ret || !disconnectedMatch ||\r
8531                                                                 // As well, disconnected nodes are said to be in a document\r
8532                                                                 // fragment in IE 9, so check for that\r
8533                                                                 node.document && node.document.nodeType !== 11 ) {\r
8534                                                         return ret;\r
8535                                                 }\r
8536                                         }\r
8537                                 } catch(e) {}\r
8538                         }\r
8539 \r
8540                         return Sizzle(expr, null, null, [node]).length > 0;\r
8541                 };\r
8542         }\r
8543 })();\r
8544 \r
8545 (function(){\r
8546         var div = document.createElement("div");\r
8547 \r
8548         div.innerHTML = "<div class='test e'></div><div class='test'></div>";\r
8549 \r
8550         // Opera can't find a second classname (in 9.6)\r
8551         // Also, make sure that getElementsByClassName actually exists\r
8552         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {\r
8553                 return;\r
8554         }\r
8555 \r
8556         // Safari caches class attributes, doesn't catch changes (in 3.2)\r
8557         div.lastChild.className = "e";\r
8558 \r
8559         if ( div.getElementsByClassName("e").length === 1 ) {\r
8560                 return;\r
8561         }\r
8562 \r
8563         Expr.order.splice(1, 0, "CLASS");\r
8564         Expr.find.CLASS = function( match, context, isXML ) {\r
8565                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {\r
8566                         return context.getElementsByClassName(match[1]);\r
8567                 }\r
8568         };\r
8569 \r
8570         // release memory in IE\r
8571         div = null;\r
8572 })();\r
8573 \r
8574 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
8575         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
8576                 var elem = checkSet[i];\r
8577 \r
8578                 if ( elem ) {\r
8579                         var match = false;\r
8580 \r
8581                         elem = elem[dir];\r
8582 \r
8583                         while ( elem ) {\r
8584                                 if ( elem[ expando ] === doneName ) {\r
8585                                         match = checkSet[elem.sizset];\r
8586                                         break;\r
8587                                 }\r
8588 \r
8589                                 if ( elem.nodeType === 1 && !isXML ){\r
8590                                         elem[ expando ] = doneName;\r
8591                                         elem.sizset = i;\r
8592                                 }\r
8593 \r
8594                                 if ( elem.nodeName.toLowerCase() === cur ) {\r
8595                                         match = elem;\r
8596                                         break;\r
8597                                 }\r
8598 \r
8599                                 elem = elem[dir];\r
8600                         }\r
8601 \r
8602                         checkSet[i] = match;\r
8603                 }\r
8604         }\r
8605 }\r
8606 \r
8607 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
8608         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
8609                 var elem = checkSet[i];\r
8610 \r
8611                 if ( elem ) {\r
8612                         var match = false;\r
8613 \r
8614                         elem = elem[dir];\r
8615 \r
8616                         while ( elem ) {\r
8617                                 if ( elem[ expando ] === doneName ) {\r
8618                                         match = checkSet[elem.sizset];\r
8619                                         break;\r
8620                                 }\r
8621 \r
8622                                 if ( elem.nodeType === 1 ) {\r
8623                                         if ( !isXML ) {\r
8624                                                 elem[ expando ] = doneName;\r
8625                                                 elem.sizset = i;\r
8626                                         }\r
8627 \r
8628                                         if ( typeof cur !== "string" ) {\r
8629                                                 if ( elem === cur ) {\r
8630                                                         match = true;\r
8631                                                         break;\r
8632                                                 }\r
8633 \r
8634                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {\r
8635                                                 match = elem;\r
8636                                                 break;\r
8637                                         }\r
8638                                 }\r
8639 \r
8640                                 elem = elem[dir];\r
8641                         }\r
8642 \r
8643                         checkSet[i] = match;\r
8644                 }\r
8645         }\r
8646 }\r
8647 \r
8648 if ( document.documentElement.contains ) {\r
8649         Sizzle.contains = function( a, b ) {\r
8650                 return a !== b && (a.contains ? a.contains(b) : true);\r
8651         };\r
8652 \r
8653 } else if ( document.documentElement.compareDocumentPosition ) {\r
8654         Sizzle.contains = function( a, b ) {\r
8655                 return !!(a.compareDocumentPosition(b) & 16);\r
8656         };\r
8657 \r
8658 } else {\r
8659         Sizzle.contains = function() {\r
8660                 return false;\r
8661         };\r
8662 }\r
8663 \r
8664 Sizzle.isXML = function( elem ) {\r
8665         // documentElement is verified for cases where it doesn't yet exist\r
8666         // (such as loading iframes in IE - #4833)\r
8667         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;\r
8668 \r
8669         return documentElement ? documentElement.nodeName !== "HTML" : false;\r
8670 };\r
8671 \r
8672 var posProcess = function( selector, context, seed ) {\r
8673         var match,\r
8674                 tmpSet = [],\r
8675                 later = "",\r
8676                 root = context.nodeType ? [context] : context;\r
8677 \r
8678         // Position selectors must be done after the filter\r
8679         // And so must :not(positional) so we move all PSEUDOs to the end\r
8680         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {\r
8681                 later += match[0];\r
8682                 selector = selector.replace( Expr.match.PSEUDO, "" );\r
8683         }\r
8684 \r
8685         selector = Expr.relative[selector] ? selector + "*" : selector;\r
8686 \r
8687         for ( var i = 0, l = root.length; i < l; i++ ) {\r
8688                 Sizzle( selector, root[i], tmpSet, seed );\r
8689         }\r
8690 \r
8691         return Sizzle.filter( later, tmpSet );\r
8692 };\r
8693 \r
8694 // EXPOSE\r
8695 \r
8696 window.tinymce.dom.Sizzle = Sizzle;\r
8697 \r
8698 })();\r
8699 \r
8700 \r
8701 (function(tinymce) {\r
8702         tinymce.dom.Element = function(id, settings) {\r
8703                 var t = this, dom, el;\r
8704 \r
8705                 t.settings = settings = settings || {};\r
8706                 t.id = id;\r
8707                 t.dom = dom = settings.dom || tinymce.DOM;\r
8708 \r
8709                 // Only IE leaks DOM references, this is a lot faster\r
8710                 if (!tinymce.isIE)\r
8711                         el = dom.get(t.id);\r
8712 \r
8713                 tinymce.each(\r
8714                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + \r
8715                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + \r
8716                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + \r
8717                                 'isHidden,setHTML,get').split(/,/), function(k) {\r
8718                                         t[k] = function() {\r
8719                                                 var a = [id], i;\r
8720 \r
8721                                                 for (i = 0; i < arguments.length; i++)\r
8722                                                         a.push(arguments[i]);\r
8723 \r
8724                                                 a = dom[k].apply(dom, a);\r
8725                                                 t.update(k);\r
8726 \r
8727                                                 return a;\r
8728                                         };\r
8729                         }\r
8730                 );\r
8731 \r
8732                 tinymce.extend(t, {\r
8733                         on : function(n, f, s) {\r
8734                                 return tinymce.dom.Event.add(t.id, n, f, s);\r
8735                         },\r
8736 \r
8737                         getXY : function() {\r
8738                                 return {\r
8739                                         x : parseInt(t.getStyle('left')),\r
8740                                         y : parseInt(t.getStyle('top'))\r
8741                                 };\r
8742                         },\r
8743 \r
8744                         getSize : function() {\r
8745                                 var n = dom.get(t.id);\r
8746 \r
8747                                 return {\r
8748                                         w : parseInt(t.getStyle('width') || n.clientWidth),\r
8749                                         h : parseInt(t.getStyle('height') || n.clientHeight)\r
8750                                 };\r
8751                         },\r
8752 \r
8753                         moveTo : function(x, y) {\r
8754                                 t.setStyles({left : x, top : y});\r
8755                         },\r
8756 \r
8757                         moveBy : function(x, y) {\r
8758                                 var p = t.getXY();\r
8759 \r
8760                                 t.moveTo(p.x + x, p.y + y);\r
8761                         },\r
8762 \r
8763                         resizeTo : function(w, h) {\r
8764                                 t.setStyles({width : w, height : h});\r
8765                         },\r
8766 \r
8767                         resizeBy : function(w, h) {\r
8768                                 var s = t.getSize();\r
8769 \r
8770                                 t.resizeTo(s.w + w, s.h + h);\r
8771                         },\r
8772 \r
8773                         update : function(k) {\r
8774                                 var b;\r
8775 \r
8776                                 if (tinymce.isIE6 && settings.blocker) {\r
8777                                         k = k || '';\r
8778 \r
8779                                         // Ignore getters\r
8780                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)\r
8781                                                 return;\r
8782 \r
8783                                         // Remove blocker on remove\r
8784                                         if (k == 'remove') {\r
8785                                                 dom.remove(t.blocker);\r
8786                                                 return;\r
8787                                         }\r
8788 \r
8789                                         if (!t.blocker) {\r
8790                                                 t.blocker = dom.uniqueId();\r
8791                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});\r
8792                                                 dom.setStyle(b, 'opacity', 0);\r
8793                                         } else\r
8794                                                 b = dom.get(t.blocker);\r
8795 \r
8796                                         dom.setStyles(b, {\r
8797                                                 left : t.getStyle('left', 1),\r
8798                                                 top : t.getStyle('top', 1),\r
8799                                                 width : t.getStyle('width', 1),\r
8800                                                 height : t.getStyle('height', 1),\r
8801                                                 display : t.getStyle('display', 1),\r
8802                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1\r
8803                                         });\r
8804                                 }\r
8805                         }\r
8806                 });\r
8807         };\r
8808 })(tinymce);\r
8809 \r
8810 (function(tinymce) {\r
8811         function trimNl(s) {\r
8812                 return s.replace(/[\n\r]+/g, '');\r
8813         };\r
8814 \r
8815         // Shorten names\r
8816         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;\r
8817 \r
8818         tinymce.create('tinymce.dom.Selection', {\r
8819                 Selection : function(dom, win, serializer) {\r
8820                         var t = this;\r
8821 \r
8822                         t.dom = dom;\r
8823                         t.win = win;\r
8824                         t.serializer = serializer;\r
8825 \r
8826                         // Add events\r
8827                         each([\r
8828                                 'onBeforeSetContent',\r
8829 \r
8830                                 'onBeforeGetContent',\r
8831 \r
8832                                 'onSetContent',\r
8833 \r
8834                                 'onGetContent'\r
8835                         ], function(e) {\r
8836                                 t[e] = new tinymce.util.Dispatcher(t);\r
8837                         });\r
8838 \r
8839                         // No W3C Range support\r
8840                         if (!t.win.getSelection)\r
8841                                 t.tridentSel = new tinymce.dom.TridentSelection(t);\r
8842 \r
8843                         if (tinymce.isIE && dom.boxModel)\r
8844                                 this._fixIESelection();\r
8845 \r
8846                         // Prevent leaks\r
8847                         tinymce.addUnload(t.destroy, t);\r
8848                 },\r
8849 \r
8850                 setCursorLocation: function(node, offset) {\r
8851                         var t = this; var r = t.dom.createRng();\r
8852                         r.setStart(node, offset);\r
8853                         r.setEnd(node, offset);\r
8854                         t.setRng(r);\r
8855                         t.collapse(false);\r
8856                 },\r
8857                 getContent : function(s) {\r
8858                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;\r
8859 \r
8860                         s = s || {};\r
8861                         wb = wa = '';\r
8862                         s.get = true;\r
8863                         s.format = s.format || 'html';\r
8864                         s.forced_root_block = '';\r
8865                         t.onBeforeGetContent.dispatch(t, s);\r
8866 \r
8867                         if (s.format == 'text')\r
8868                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));\r
8869 \r
8870                         if (r.cloneContents) {\r
8871                                 n = r.cloneContents();\r
8872 \r
8873                                 if (n)\r
8874                                         e.appendChild(n);\r
8875                         } else if (is(r.item) || is(r.htmlText)) {\r
8876                                 // IE will produce invalid markup if elements are present that\r
8877                                 // it doesn't understand like custom elements or HTML5 elements.\r
8878                                 // Adding a BR in front of the contents and then remoiving it seems to fix it though.\r
8879                                 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);\r
8880                                 e.removeChild(e.firstChild);\r
8881                         } else\r
8882                                 e.innerHTML = r.toString();\r
8883 \r
8884                         // Keep whitespace before and after\r
8885                         if (/^\s/.test(e.innerHTML))\r
8886                                 wb = ' ';\r
8887 \r
8888                         if (/\s+$/.test(e.innerHTML))\r
8889                                 wa = ' ';\r
8890 \r
8891                         s.getInner = true;\r
8892 \r
8893                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;\r
8894                         t.onGetContent.dispatch(t, s);\r
8895 \r
8896                         return s.content;\r
8897                 },\r
8898 \r
8899                 setContent : function(content, args) {\r
8900                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;\r
8901 \r
8902                         args = args || {format : 'html'};\r
8903                         args.set = true;\r
8904                         content = args.content = content;\r
8905 \r
8906                         // Dispatch before set content event\r
8907                         if (!args.no_events)\r
8908                                 self.onBeforeSetContent.dispatch(self, args);\r
8909 \r
8910                         content = args.content;\r
8911 \r
8912                         if (rng.insertNode) {\r
8913                                 // Make caret marker since insertNode places the caret in the beginning of text after insert\r
8914                                 content += '<span id="__caret">_</span>';\r
8915 \r
8916                                 // Delete and insert new node\r
8917                                 if (rng.startContainer == doc && rng.endContainer == doc) {\r
8918                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents\r
8919                                         doc.body.innerHTML = content;\r
8920                                 } else {\r
8921                                         rng.deleteContents();\r
8922 \r
8923                                         if (doc.body.childNodes.length === 0) {\r
8924                                                 doc.body.innerHTML = content;\r
8925                                         } else {\r
8926                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges\r
8927                                                 if (rng.createContextualFragment) {\r
8928                                                         rng.insertNode(rng.createContextualFragment(content));\r
8929                                                 } else {\r
8930                                                         // Fake createContextualFragment call in IE 9\r
8931                                                         frag = doc.createDocumentFragment();\r
8932                                                         temp = doc.createElement('div');\r
8933 \r
8934                                                         frag.appendChild(temp);\r
8935                                                         temp.outerHTML = content;\r
8936 \r
8937                                                         rng.insertNode(frag);\r
8938                                                 }\r
8939                                         }\r
8940                                 }\r
8941 \r
8942                                 // Move to caret marker\r
8943                                 caretNode = self.dom.get('__caret');\r
8944 \r
8945                                 // Make sure we wrap it compleatly, Opera fails with a simple select call\r
8946                                 rng = doc.createRange();\r
8947                                 rng.setStartBefore(caretNode);\r
8948                                 rng.setEndBefore(caretNode);\r
8949                                 self.setRng(rng);\r
8950 \r
8951                                 // Remove the caret position\r
8952                                 self.dom.remove('__caret');\r
8953 \r
8954                                 try {\r
8955                                         self.setRng(rng);\r
8956                                 } catch (ex) {\r
8957                                         // Might fail on Opera for some odd reason\r
8958                                 }\r
8959                         } else {\r
8960                                 if (rng.item) {\r
8961                                         // Delete content and get caret text selection\r
8962                                         doc.execCommand('Delete', false, null);\r
8963                                         rng = self.getRng();\r
8964                                 }\r
8965 \r
8966                                 // Explorer removes spaces from the beginning of pasted contents\r
8967                                 if (/^\s+/.test(content)) {\r
8968                                         rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);\r
8969                                         self.dom.remove('__mce_tmp');\r
8970                                 } else\r
8971                                         rng.pasteHTML(content);\r
8972                         }\r
8973 \r
8974                         // Dispatch set content event\r
8975                         if (!args.no_events)\r
8976                                 self.onSetContent.dispatch(self, args);\r
8977                 },\r
8978 \r
8979                 getStart : function() {\r
8980                         var rng = this.getRng(), startElement, parentElement, checkRng, node;\r
8981 \r
8982                         if (rng.duplicate || rng.item) {\r
8983                                 // Control selection, return first item\r
8984                                 if (rng.item)\r
8985                                         return rng.item(0);\r
8986 \r
8987                                 // Get start element\r
8988                                 checkRng = rng.duplicate();\r
8989                                 checkRng.collapse(1);\r
8990                                 startElement = checkRng.parentElement();\r
8991 \r
8992                                 // Check if range parent is inside the start element, then return the inner parent element\r
8993                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element\r
8994                                 parentElement = node = rng.parentElement();\r
8995                                 while (node = node.parentNode) {\r
8996                                         if (node == startElement) {\r
8997                                                 startElement = parentElement;\r
8998                                                 break;\r
8999                                         }\r
9000                                 }\r
9001 \r
9002                                 return startElement;\r
9003                         } else {\r
9004                                 startElement = rng.startContainer;\r
9005 \r
9006                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())\r
9007                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];\r
9008 \r
9009                                 if (startElement && startElement.nodeType == 3)\r
9010                                         return startElement.parentNode;\r
9011 \r
9012                                 return startElement;\r
9013                         }\r
9014                 },\r
9015 \r
9016                 getEnd : function() {\r
9017                         var t = this, r = t.getRng(), e, eo;\r
9018 \r
9019                         if (r.duplicate || r.item) {\r
9020                                 if (r.item)\r
9021                                         return r.item(0);\r
9022 \r
9023                                 r = r.duplicate();\r
9024                                 r.collapse(0);\r
9025                                 e = r.parentElement();\r
9026 \r
9027                                 if (e && e.nodeName == 'BODY')\r
9028                                         return e.lastChild || e;\r
9029 \r
9030                                 return e;\r
9031                         } else {\r
9032                                 e = r.endContainer;\r
9033                                 eo = r.endOffset;\r
9034 \r
9035                                 if (e.nodeType == 1 && e.hasChildNodes())\r
9036                                         e = e.childNodes[eo > 0 ? eo - 1 : eo];\r
9037 \r
9038                                 if (e && e.nodeType == 3)\r
9039                                         return e.parentNode;\r
9040 \r
9041                                 return e;\r
9042                         }\r
9043                 },\r
9044 \r
9045                 getBookmark : function(type, normalized) {\r
9046                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;\r
9047 \r
9048                         function findIndex(name, element) {\r
9049                                 var index = 0;\r
9050 \r
9051                                 each(dom.select(name), function(node, i) {\r
9052                                         if (node == element)\r
9053                                                 index = i;\r
9054                                 });\r
9055 \r
9056                                 return index;\r
9057                         };\r
9058 \r
9059                         function normalizeTableCellSelection(rng) {\r
9060                                 function moveEndPoint(start) {\r
9061                                         var container, offset, childNodes, prefix = start ? 'start' : 'end';\r
9062 \r
9063                                         container = rng[prefix + 'Container'];\r
9064                                         offset = rng[prefix + 'Offset'];\r
9065 \r
9066                                         if (container.nodeType == 1 && container.nodeName == "TR") {\r
9067                                                 childNodes = container.childNodes;\r
9068                                                 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];\r
9069                                                 if (container) {\r
9070                                                         offset = start ? 0 : container.childNodes.length;\r
9071                                                         rng['set' + (start ? 'Start' : 'End')](container, offset);\r
9072                                                 }\r
9073                                         }\r
9074                                 };\r
9075 \r
9076                                 moveEndPoint(true);\r
9077                                 moveEndPoint();\r
9078 \r
9079                                 return rng;\r
9080                         };\r
9081 \r
9082                         function getLocation() {\r
9083                                 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};\r
9084 \r
9085                                 function getPoint(rng, start) {\r
9086                                         var container = rng[start ? 'startContainer' : 'endContainer'],\r
9087                                                 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;\r
9088 \r
9089                                         if (container.nodeType == 3) {\r
9090                                                 if (normalized) {\r
9091                                                         for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)\r
9092                                                                 offset += node.nodeValue.length;\r
9093                                                 }\r
9094 \r
9095                                                 point.push(offset);\r
9096                                         } else {\r
9097                                                 childNodes = container.childNodes;\r
9098 \r
9099                                                 if (offset >= childNodes.length && childNodes.length) {\r
9100                                                         after = 1;\r
9101                                                         offset = Math.max(0, childNodes.length - 1);\r
9102                                                 }\r
9103 \r
9104                                                 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);\r
9105                                         }\r
9106 \r
9107                                         for (; container && container != root; container = container.parentNode)\r
9108                                                 point.push(t.dom.nodeIndex(container, normalized));\r
9109 \r
9110                                         return point;\r
9111                                 };\r
9112 \r
9113                                 bookmark.start = getPoint(rng, true);\r
9114 \r
9115                                 if (!t.isCollapsed())\r
9116                                         bookmark.end = getPoint(rng);\r
9117 \r
9118                                 return bookmark;\r
9119                         };\r
9120 \r
9121                         if (type == 2) {\r
9122                                 if (t.tridentSel)\r
9123                                         return t.tridentSel.getBookmark(type);\r
9124 \r
9125                                 return getLocation();\r
9126                         }\r
9127 \r
9128                         // Handle simple range\r
9129                         if (type)\r
9130                                 return {rng : t.getRng()};\r
9131 \r
9132                         rng = t.getRng();\r
9133                         id = dom.uniqueId();\r
9134                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();\r
9135                         styles = 'overflow:hidden;line-height:0px';\r
9136 \r
9137                         // Explorer method\r
9138                         if (rng.duplicate || rng.item) {\r
9139                                 // Text selection\r
9140                                 if (!rng.item) {\r
9141                                         rng2 = rng.duplicate();\r
9142 \r
9143                                         try {\r
9144                                                 // Insert start marker\r
9145                                                 rng.collapse();\r
9146                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');\r
9147 \r
9148                                                 // Insert end marker\r
9149                                                 if (!collapsed) {\r
9150                                                         rng2.collapse(false);\r
9151 \r
9152                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>\r
9153                                                         rng.moveToElementText(rng2.parentElement());\r
9154                                                         if (rng.compareEndPoints('StartToEnd', rng2) === 0)\r
9155                                                                 rng2.move('character', -1);\r
9156 \r
9157                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');\r
9158                                                 }\r
9159                                         } catch (ex) {\r
9160                                                 // IE might throw unspecified error so lets ignore it\r
9161                                                 return null;\r
9162                                         }\r
9163                                 } else {\r
9164                                         // Control selection\r
9165                                         element = rng.item(0);\r
9166                                         name = element.nodeName;\r
9167 \r
9168                                         return {name : name, index : findIndex(name, element)};\r
9169                                 }\r
9170                         } else {\r
9171                                 element = t.getNode();\r
9172                                 name = element.nodeName;\r
9173                                 if (name == 'IMG')\r
9174                                         return {name : name, index : findIndex(name, element)};\r
9175 \r
9176                                 // W3C method\r
9177                                 rng2 = normalizeTableCellSelection(rng.cloneRange());\r
9178 \r
9179                                 // Insert end marker\r
9180                                 if (!collapsed) {\r
9181                                         rng2.collapse(false);\r
9182                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));\r
9183                                 }\r
9184 \r
9185                                 rng = normalizeTableCellSelection(rng);\r
9186                                 rng.collapse(true);\r
9187                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));\r
9188                         }\r
9189 \r
9190                         t.moveToBookmark({id : id, keep : 1});\r
9191 \r
9192                         return {id : id};\r
9193                 },\r
9194 \r
9195                 moveToBookmark : function(bookmark) {\r
9196                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;\r
9197 \r
9198                         function setEndPoint(start) {\r
9199                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;\r
9200 \r
9201                                 if (point) {\r
9202                                         offset = point[0];\r
9203 \r
9204                                         // Find container node\r
9205                                         for (node = root, i = point.length - 1; i >= 1; i--) {\r
9206                                                 children = node.childNodes;\r
9207 \r
9208                                                 if (point[i] > children.length - 1)\r
9209                                                         return;\r
9210 \r
9211                                                 node = children[point[i]];\r
9212                                         }\r
9213 \r
9214                                         // Move text offset to best suitable location\r
9215                                         if (node.nodeType === 3)\r
9216                                                 offset = Math.min(point[0], node.nodeValue.length);\r
9217 \r
9218                                         // Move element offset to best suitable location\r
9219                                         if (node.nodeType === 1)\r
9220                                                 offset = Math.min(point[0], node.childNodes.length);\r
9221 \r
9222                                         // Set offset within container node\r
9223                                         if (start)\r
9224                                                 rng.setStart(node, offset);\r
9225                                         else\r
9226                                                 rng.setEnd(node, offset);\r
9227                                 }\r
9228 \r
9229                                 return true;\r
9230                         };\r
9231 \r
9232                         function restoreEndPoint(suffix) {\r
9233                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;\r
9234 \r
9235                                 if (marker) {\r
9236                                         node = marker.parentNode;\r
9237 \r
9238                                         if (suffix == 'start') {\r
9239                                                 if (!keep) {\r
9240                                                         idx = dom.nodeIndex(marker);\r
9241                                                 } else {\r
9242                                                         node = marker.firstChild;\r
9243                                                         idx = 1;\r
9244                                                 }\r
9245 \r
9246                                                 startContainer = endContainer = node;\r
9247                                                 startOffset = endOffset = idx;\r
9248                                         } else {\r
9249                                                 if (!keep) {\r
9250                                                         idx = dom.nodeIndex(marker);\r
9251                                                 } else {\r
9252                                                         node = marker.firstChild;\r
9253                                                         idx = 1;\r
9254                                                 }\r
9255 \r
9256                                                 endContainer = node;\r
9257                                                 endOffset = idx;\r
9258                                         }\r
9259 \r
9260                                         if (!keep) {\r
9261                                                 prev = marker.previousSibling;\r
9262                                                 next = marker.nextSibling;\r
9263 \r
9264                                                 // Remove all marker text nodes\r
9265                                                 each(tinymce.grep(marker.childNodes), function(node) {\r
9266                                                         if (node.nodeType == 3)\r
9267                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');\r
9268                                                 });\r
9269 \r
9270                                                 // Remove marker but keep children if for example contents where inserted into the marker\r
9271                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature\r
9272                                                 while (marker = dom.get(bookmark.id + '_' + suffix))\r
9273                                                         dom.remove(marker, 1);\r
9274 \r
9275                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node\r
9276                                                 // 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
9277                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {\r
9278                                                         idx = prev.nodeValue.length;\r
9279                                                         prev.appendData(next.nodeValue);\r
9280                                                         dom.remove(next);\r
9281 \r
9282                                                         if (suffix == 'start') {\r
9283                                                                 startContainer = endContainer = prev;\r
9284                                                                 startOffset = endOffset = idx;\r
9285                                                         } else {\r
9286                                                                 endContainer = prev;\r
9287                                                                 endOffset = idx;\r
9288                                                         }\r
9289                                                 }\r
9290                                         }\r
9291                                 }\r
9292                         };\r
9293 \r
9294                         function addBogus(node) {\r
9295                                 // Adds a bogus BR element for empty block elements\r
9296                                 if (dom.isBlock(node) && !node.innerHTML && !isIE)\r
9297                                         node.innerHTML = '<br data-mce-bogus="1" />';\r
9298 \r
9299                                 return node;\r
9300                         };\r
9301 \r
9302                         if (bookmark) {\r
9303                                 if (bookmark.start) {\r
9304                                         rng = dom.createRng();\r
9305                                         root = dom.getRoot();\r
9306 \r
9307                                         if (t.tridentSel)\r
9308                                                 return t.tridentSel.moveToBookmark(bookmark);\r
9309 \r
9310                                         if (setEndPoint(true) && setEndPoint()) {\r
9311                                                 t.setRng(rng);\r
9312                                         }\r
9313                                 } else if (bookmark.id) {\r
9314                                         // Restore start/end points\r
9315                                         restoreEndPoint('start');\r
9316                                         restoreEndPoint('end');\r
9317 \r
9318                                         if (startContainer) {\r
9319                                                 rng = dom.createRng();\r
9320                                                 rng.setStart(addBogus(startContainer), startOffset);\r
9321                                                 rng.setEnd(addBogus(endContainer), endOffset);\r
9322                                                 t.setRng(rng);\r
9323                                         }\r
9324                                 } else if (bookmark.name) {\r
9325                                         t.select(dom.select(bookmark.name)[bookmark.index]);\r
9326                                 } else if (bookmark.rng)\r
9327                                         t.setRng(bookmark.rng);\r
9328                         }\r
9329                 },\r
9330 \r
9331                 select : function(node, content) {\r
9332                         var t = this, dom = t.dom, rng = dom.createRng(), idx;\r
9333 \r
9334                         function setPoint(node, start) {\r
9335                                 var walker = new TreeWalker(node, node);\r
9336 \r
9337                                 do {\r
9338                                         // Text node\r
9339                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {\r
9340                                                 if (start)\r
9341                                                         rng.setStart(node, 0);\r
9342                                                 else\r
9343                                                         rng.setEnd(node, node.nodeValue.length);\r
9344 \r
9345                                                 return;\r
9346                                         }\r
9347 \r
9348                                         // BR element\r
9349                                         if (node.nodeName == 'BR') {\r
9350                                                 if (start)\r
9351                                                         rng.setStartBefore(node);\r
9352                                                 else\r
9353                                                         rng.setEndBefore(node);\r
9354 \r
9355                                                 return;\r
9356                                         }\r
9357                                 } while (node = (start ? walker.next() : walker.prev()));\r
9358                         };\r
9359 \r
9360                         if (node) {\r
9361                                 idx = dom.nodeIndex(node);\r
9362                                 rng.setStart(node.parentNode, idx);\r
9363                                 rng.setEnd(node.parentNode, idx + 1);\r
9364 \r
9365                                 // Find first/last text node or BR element\r
9366                                 if (content) {\r
9367                                         setPoint(node, 1);\r
9368                                         setPoint(node);\r
9369                                 }\r
9370 \r
9371                                 t.setRng(rng);\r
9372                         }\r
9373 \r
9374                         return node;\r
9375                 },\r
9376 \r
9377                 isCollapsed : function() {\r
9378                         var t = this, r = t.getRng(), s = t.getSel();\r
9379 \r
9380                         if (!r || r.item)\r
9381                                 return false;\r
9382 \r
9383                         if (r.compareEndPoints)\r
9384                                 return r.compareEndPoints('StartToEnd', r) === 0;\r
9385 \r
9386                         return !s || r.collapsed;\r
9387                 },\r
9388 \r
9389                 collapse : function(to_start) {\r
9390                         var self = this, rng = self.getRng(), node;\r
9391 \r
9392                         // Control range on IE\r
9393                         if (rng.item) {\r
9394                                 node = rng.item(0);\r
9395                                 rng = self.win.document.body.createTextRange();\r
9396                                 rng.moveToElementText(node);\r
9397                         }\r
9398 \r
9399                         rng.collapse(!!to_start);\r
9400                         self.setRng(rng);\r
9401                 },\r
9402 \r
9403                 getSel : function() {\r
9404                         var t = this, w = this.win;\r
9405 \r
9406                         return w.getSelection ? w.getSelection() : w.document.selection;\r
9407                 },\r
9408 \r
9409                 getRng : function(w3c) {\r
9410                         var self = this, selection, rng, elm, doc = self.win.document;\r
9411 \r
9412                         // Found tridentSel object then we need to use that one\r
9413                         if (w3c && self.tridentSel) {\r
9414                                 return self.tridentSel.getRangeAt(0);\r
9415                         }\r
9416 \r
9417                         try {\r
9418                                 if (selection = self.getSel()) {\r
9419                                         rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());\r
9420                                 }\r
9421                         } catch (ex) {\r
9422                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe\r
9423                         }\r
9424 \r
9425                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet\r
9426                         if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {\r
9427                                 elm = doc.selection.createRange().item(0);\r
9428                                 rng = doc.createRange();\r
9429                                 rng.setStartBefore(elm);\r
9430                                 rng.setEndAfter(elm);\r
9431                         }\r
9432 \r
9433                         // No range found then create an empty one\r
9434                         // This can occur when the editor is placed in a hidden container element on Gecko\r
9435                         // Or on IE when there was an exception\r
9436                         if (!rng) {\r
9437                                 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();\r
9438                         }\r
9439 \r
9440                         // If range is at start of document then move it to start of body\r
9441                         if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {\r
9442                                 elm = self.dom.getRoot();\r
9443                                 rng.setStart(elm, 0);\r
9444                                 rng.setEnd(elm, 0);\r
9445                         }\r
9446 \r
9447                         if (self.selectedRange && self.explicitRange) {\r
9448                                 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {\r
9449                                         // Safari, Opera and Chrome only ever select text which causes the range to change.\r
9450                                         // This lets us use the originally set range if the selection hasn't been changed by the user.\r
9451                                         rng = self.explicitRange;\r
9452                                 } else {\r
9453                                         self.selectedRange = null;\r
9454                                         self.explicitRange = null;\r
9455                                 }\r
9456                         }\r
9457 \r
9458                         return rng;\r
9459                 },\r
9460 \r
9461                 setRng : function(r, forward) {\r
9462                         var s, t = this;\r
9463 \r
9464                         if (!t.tridentSel) {\r
9465                                 s = t.getSel();\r
9466 \r
9467                                 if (s) {\r
9468                                         t.explicitRange = r;\r
9469 \r
9470                                         try {\r
9471                                                 s.removeAllRanges();\r
9472                                         } catch (ex) {\r
9473                                                 // IE9 might throw errors here don't know why\r
9474                                         }\r
9475 \r
9476                                         s.addRange(r);\r
9477 \r
9478                                         // Forward is set to false and we have an extend function\r
9479                                         if (forward === false && s.extend) {\r
9480                                                 s.collapse(r.endContainer, r.endOffset);\r
9481                                                 s.extend(r.startContainer, r.startOffset);\r
9482                                         }\r
9483 \r
9484                                         // adding range isn't always successful so we need to check range count otherwise an exception can occur\r
9485                                         t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;\r
9486                                 }\r
9487                         } else {\r
9488                                 // Is W3C Range\r
9489                                 if (r.cloneRange) {\r
9490                                         try {\r
9491                                                 t.tridentSel.addRange(r);\r
9492                                                 return;\r
9493                                         } catch (ex) {\r
9494                                                 //IE9 throws an error here if called before selection is placed in the editor\r
9495                                         }\r
9496                                 }\r
9497 \r
9498                                 // Is IE specific range\r
9499                                 try {\r
9500                                         r.select();\r
9501                                 } catch (ex) {\r
9502                                         // Needed for some odd IE bug #1843306\r
9503                                 }\r
9504                         }\r
9505                 },\r
9506 \r
9507                 setNode : function(n) {\r
9508                         var t = this;\r
9509 \r
9510                         t.setContent(t.dom.getOuterHTML(n));\r
9511 \r
9512                         return n;\r
9513                 },\r
9514 \r
9515                 getNode : function() {\r
9516                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;\r
9517 \r
9518                         function skipEmptyTextNodes(n, forwards) {\r
9519                                 var orig = n;\r
9520                                 while (n && n.nodeType === 3 && n.length === 0) {\r
9521                                         n = forwards ? n.nextSibling : n.previousSibling;\r
9522                                 }\r
9523                                 return n || orig;\r
9524                         };\r
9525 \r
9526                         // Range maybe lost after the editor is made visible again\r
9527                         if (!rng)\r
9528                                 return t.dom.getRoot();\r
9529 \r
9530                         if (rng.setStart) {\r
9531                                 elm = rng.commonAncestorContainer;\r
9532 \r
9533                                 // Handle selection a image or other control like element such as anchors\r
9534                                 if (!rng.collapsed) {\r
9535                                         if (rng.startContainer == rng.endContainer) {\r
9536                                                 if (rng.endOffset - rng.startOffset < 2) {\r
9537                                                         if (rng.startContainer.hasChildNodes())\r
9538                                                                 elm = rng.startContainer.childNodes[rng.startOffset];\r
9539                                                 }\r
9540                                         }\r
9541 \r
9542                                         // If the anchor node is a element instead of a text node then return this element\r
9543                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)\r
9544                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];\r
9545 \r
9546                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.\r
9547                                         // This happens when you double click an underlined word in FireFox.\r
9548                                         if (start.nodeType === 3 && end.nodeType === 3) {\r
9549                                                 if (start.length === rng.startOffset) {\r
9550                                                         start = skipEmptyTextNodes(start.nextSibling, true);\r
9551                                                 } else {\r
9552                                                         start = start.parentNode;\r
9553                                                 }\r
9554                                                 if (rng.endOffset === 0) {\r
9555                                                         end = skipEmptyTextNodes(end.previousSibling, false);\r
9556                                                 } else {\r
9557                                                         end = end.parentNode;\r
9558                                                 }\r
9559 \r
9560                                                 if (start && start === end)\r
9561                                                         return start;\r
9562                                         }\r
9563                                 }\r
9564 \r
9565                                 if (elm && elm.nodeType == 3)\r
9566                                         return elm.parentNode;\r
9567 \r
9568                                 return elm;\r
9569                         }\r
9570 \r
9571                         return rng.item ? rng.item(0) : rng.parentElement();\r
9572                 },\r
9573 \r
9574                 getSelectedBlocks : function(st, en) {\r
9575                         var t = this, dom = t.dom, sb, eb, n, bl = [];\r
9576 \r
9577                         sb = dom.getParent(st || t.getStart(), dom.isBlock);\r
9578                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);\r
9579 \r
9580                         if (sb)\r
9581                                 bl.push(sb);\r
9582 \r
9583                         if (sb && eb && sb != eb) {\r
9584                                 n = sb;\r
9585 \r
9586                                 var walker = new TreeWalker(sb, dom.getRoot());\r
9587                                 while ((n = walker.next()) && n != eb) {\r
9588                                         if (dom.isBlock(n))\r
9589                                                 bl.push(n);\r
9590                                 }\r
9591                         }\r
9592 \r
9593                         if (eb && sb != eb)\r
9594                                 bl.push(eb);\r
9595 \r
9596                         return bl;\r
9597                 },\r
9598 \r
9599                 isForward: function(){\r
9600                         var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;\r
9601 \r
9602                         // No support for selection direction then always return true\r
9603                         if (!sel || sel.anchorNode == null || sel.focusNode == null) {\r
9604                                 return true;\r
9605                         }\r
9606 \r
9607                         anchorRange = dom.createRng();\r
9608                         anchorRange.setStart(sel.anchorNode, sel.anchorOffset);\r
9609                         anchorRange.collapse(true);\r
9610 \r
9611                         focusRange = dom.createRng();\r
9612                         focusRange.setStart(sel.focusNode, sel.focusOffset);\r
9613                         focusRange.collapse(true);\r
9614 \r
9615                         return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;\r
9616                 },\r
9617 \r
9618                 normalize : function() {\r
9619                         var self = this, rng, normalized, collapsed, node, sibling;\r
9620 \r
9621                         function normalizeEndPoint(start) {\r
9622                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;\r
9623 \r
9624                                 function hasBrBeforeAfter(node, left) {\r
9625                                         var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);\r
9626 \r
9627                                         while (node = walker[left ? 'prev' : 'next']()) {\r
9628                                                 if (node.nodeName === "BR") {\r
9629                                                         return true;\r
9630                                                 }\r
9631                                         }\r
9632                                 };\r
9633 \r
9634                                 // Walks the dom left/right to find a suitable text node to move the endpoint into\r
9635                                 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG\r
9636                                 function findTextNodeRelative(left, startNode) {\r
9637                                         var walker, lastInlineElement;\r
9638 \r
9639                                         startNode = startNode || container;\r
9640                                         walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);\r
9641 \r
9642                                         // Walk left until we hit a text node we can move to or a block/br/img\r
9643                                         while (node = walker[left ? 'prev' : 'next']()) {\r
9644                                                 // Found text node that has a length\r
9645                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
9646                                                         container = node;\r
9647                                                         offset = left ? node.nodeValue.length : 0;\r
9648                                                         normalized = true;\r
9649                                                         return;\r
9650                                                 }\r
9651 \r
9652                                                 // Break if we find a block or a BR/IMG/INPUT etc\r
9653                                                 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
9654                                                         return;\r
9655                                                 }\r
9656 \r
9657                                                 lastInlineElement = node;\r
9658                                         }\r
9659 \r
9660                                         // Only fetch the last inline element when in caret mode for now\r
9661                                         if (collapsed && lastInlineElement) {\r
9662                                                 container = lastInlineElement;\r
9663                                                 normalized = true;\r
9664                                                 offset = 0;\r
9665                                         }\r
9666                                 };\r
9667 \r
9668                                 container = rng[(start ? 'start' : 'end') + 'Container'];\r
9669                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];\r
9670                                 nonEmptyElementsMap = dom.schema.getNonEmptyElements();\r
9671 \r
9672                                 // If the container is a document move it to the body element\r
9673                                 if (container.nodeType === 9) {\r
9674                                         container = dom.getRoot();\r
9675                                         offset = 0;\r
9676                                 }\r
9677 \r
9678                                 // If the container is body try move it into the closest text node or position\r
9679                                 if (container === body) {\r
9680                                         // If start is before/after a image, table etc\r
9681                                         if (start) {\r
9682                                                 node = container.childNodes[offset > 0 ? offset - 1 : 0];\r
9683                                                 if (node) {\r
9684                                                         nodeName = node.nodeName.toLowerCase();\r
9685                                                         if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {\r
9686                                                                 return;\r
9687                                                         }\r
9688                                                 }\r
9689                                         }\r
9690 \r
9691                                         // Resolve the index\r
9692                                         if (container.hasChildNodes()) {\r
9693                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];\r
9694                                                 offset = 0;\r
9695 \r
9696                                                 // Don't walk into elements that doesn't have any child nodes like a IMG\r
9697                                                 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {\r
9698                                                         // Walk the DOM to find a text node to place the caret at or a BR\r
9699                                                         node = container;\r
9700                                                         walker = new TreeWalker(container, body);\r
9701 \r
9702                                                         do {\r
9703                                                                 // Found a text node use that position\r
9704                                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
9705                                                                         offset = start ? 0 : node.nodeValue.length;\r
9706                                                                         container = node;\r
9707                                                                         normalized = true;\r
9708                                                                         break;\r
9709                                                                 }\r
9710 \r
9711                                                                 // Found a BR/IMG element that we can place the caret before\r
9712                                                                 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
9713                                                                         offset = dom.nodeIndex(node);\r
9714                                                                         container = node.parentNode;\r
9715 \r
9716                                                                         // Put caret after image when moving the end point\r
9717                                                                         if (node.nodeName ==  "IMG" && !start) {\r
9718                                                                                 offset++;\r
9719                                                                         }\r
9720 \r
9721                                                                         normalized = true;\r
9722                                                                         break;\r
9723                                                                 }\r
9724                                                         } while (node = (start ? walker.next() : walker.prev()));\r
9725                                                 }\r
9726                                         }\r
9727                                 }\r
9728 \r
9729                                 // Lean the caret to the left if possible\r
9730                                 if (collapsed) {\r
9731                                         // So this: <b>x</b><i>|x</i>\r
9732                                         // Becomes: <b>x|</b><i>x</i>\r
9733                                         // Seems that only gecko has issues with this\r
9734                                         if (container.nodeType === 3 && offset === 0) {\r
9735                                                 findTextNodeRelative(true);\r
9736                                         }\r
9737 \r
9738                                         // Lean left into empty inline elements when the caret is before a BR\r
9739                                         // So this: <i><b></b><i>|<br></i>\r
9740                                         // Becomes: <i><b>|</b><i><br></i>\r
9741                                         // Seems that only gecko has issues with this\r
9742                                         if (container.nodeType === 1) {\r
9743                                                 node = container.childNodes[offset];\r
9744                                                 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {\r
9745                                                         findTextNodeRelative(true, container.childNodes[offset]);\r
9746                                                 }\r
9747                                         }\r
9748                                 }\r
9749 \r
9750                                 // Lean the start of the selection right if possible\r
9751                                 // So this: x[<b>x]</b>\r
9752                                 // Becomes: x<b>[x]</b>\r
9753                                 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {\r
9754                                         findTextNodeRelative(false);\r
9755                                 }\r
9756 \r
9757                                 // Set endpoint if it was normalized\r
9758                                 if (normalized)\r
9759                                         rng['set' + (start ? 'Start' : 'End')](container, offset);\r
9760                         };\r
9761 \r
9762                         // Normalize only on non IE browsers for now\r
9763                         if (tinymce.isIE)\r
9764                                 return;\r
9765                         \r
9766                         rng = self.getRng();\r
9767                         collapsed = rng.collapsed;\r
9768 \r
9769                         // Normalize the end points\r
9770                         normalizeEndPoint(true);\r
9771 \r
9772                         if (!collapsed)\r
9773                                 normalizeEndPoint();\r
9774 \r
9775                         // Set the selection if it was normalized\r
9776                         if (normalized) {\r
9777                                 // If it was collapsed then make sure it still is\r
9778                                 if (collapsed) {\r
9779                                         rng.collapse(true);\r
9780                                 }\r
9781 \r
9782                                 //console.log(self.dom.dumpRng(rng));\r
9783                                 self.setRng(rng, self.isForward());\r
9784                         }\r
9785                 },\r
9786 \r
9787                 destroy : function(s) {\r
9788                         var t = this;\r
9789 \r
9790                         t.win = null;\r
9791 \r
9792                         // Manual destroy then remove unload handler\r
9793                         if (!s)\r
9794                                 tinymce.removeUnload(t.destroy);\r
9795                 },\r
9796 \r
9797                 // 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
9798                 _fixIESelection : function() {\r
9799                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;\r
9800 \r
9801                         // Return range from point or null if it failed\r
9802                         function rngFromPoint(x, y) {\r
9803                                 var rng = body.createTextRange();\r
9804 \r
9805                                 try {\r
9806                                         rng.moveToPoint(x, y);\r
9807                                 } catch (ex) {\r
9808                                         // IE sometimes throws and exception, so lets just ignore it\r
9809                                         rng = null;\r
9810                                 }\r
9811 \r
9812                                 return rng;\r
9813                         };\r
9814 \r
9815                         // Fires while the selection is changing\r
9816                         function selectionChange(e) {\r
9817                                 var pointRng;\r
9818 \r
9819                                 // Check if the button is down or not\r
9820                                 if (e.button) {\r
9821                                         // Create range from mouse position\r
9822                                         pointRng = rngFromPoint(e.x, e.y);\r
9823 \r
9824                                         if (pointRng) {\r
9825                                                 // Check if pointRange is before/after selection then change the endPoint\r
9826                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)\r
9827                                                         pointRng.setEndPoint('StartToStart', startRng);\r
9828                                                 else\r
9829                                                         pointRng.setEndPoint('EndToEnd', startRng);\r
9830 \r
9831                                                 pointRng.select();\r
9832                                         }\r
9833                                 } else\r
9834                                         endSelection();\r
9835                         }\r
9836 \r
9837                         // Removes listeners\r
9838                         function endSelection() {\r
9839                                 var rng = doc.selection.createRange();\r
9840 \r
9841                                 // If the range is collapsed then use the last start range\r
9842                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)\r
9843                                         startRng.select();\r
9844 \r
9845                                 dom.unbind(doc, 'mouseup', endSelection);\r
9846                                 dom.unbind(doc, 'mousemove', selectionChange);\r
9847                                 startRng = started = 0;\r
9848                         };\r
9849 \r
9850                         // Make HTML element unselectable since we are going to handle selection by hand\r
9851                         doc.documentElement.unselectable = true;\r
9852                         \r
9853                         // Detect when user selects outside BODY\r
9854                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {\r
9855                                 if (e.target.nodeName === 'HTML') {\r
9856                                         if (started)\r
9857                                                 endSelection();\r
9858 \r
9859                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML\r
9860                                         htmlElm = doc.documentElement;\r
9861                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)\r
9862                                                 return;\r
9863 \r
9864                                         started = 1;\r
9865                                         // Setup start position\r
9866                                         startRng = rngFromPoint(e.x, e.y);\r
9867                                         if (startRng) {\r
9868                                                 // Listen for selection change events\r
9869                                                 dom.bind(doc, 'mouseup', endSelection);\r
9870                                                 dom.bind(doc, 'mousemove', selectionChange);\r
9871 \r
9872                                                 dom.win.focus();\r
9873                                                 startRng.select();\r
9874                                         }\r
9875                                 }\r
9876                         });\r
9877                 }\r
9878         });\r
9879 })(tinymce);\r
9880 \r
9881 (function(tinymce) {\r
9882         tinymce.dom.Serializer = function(settings, dom, schema) {\r
9883                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;\r
9884 \r
9885                 // Support the old apply_source_formatting option\r
9886                 if (!settings.apply_source_formatting)\r
9887                         settings.indent = false;\r
9888 \r
9889                 // Default DOM and Schema if they are undefined\r
9890                 dom = dom || tinymce.DOM;\r
9891                 schema = schema || new tinymce.html.Schema(settings);\r
9892                 settings.entity_encoding = settings.entity_encoding || 'named';\r
9893                 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;\r
9894 \r
9895                 onPreProcess = new tinymce.util.Dispatcher(self);\r
9896 \r
9897                 onPostProcess = new tinymce.util.Dispatcher(self);\r
9898 \r
9899                 htmlParser = new tinymce.html.DomParser(settings, schema);\r
9900 \r
9901                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed\r
9902                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {\r
9903                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;\r
9904 \r
9905                         while (i--) {\r
9906                                 node = nodes[i];\r
9907 \r
9908                                 value = node.attributes.map[internalName];\r
9909                                 if (value !== undef) {\r
9910                                         // Set external name to internal value and remove internal\r
9911                                         node.attr(name, value.length > 0 ? value : null);\r
9912                                         node.attr(internalName, null);\r
9913                                 } else {\r
9914                                         // No internal attribute found then convert the value we have in the DOM\r
9915                                         value = node.attributes.map[name];\r
9916 \r
9917                                         if (name === "style")\r
9918                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);\r
9919                                         else if (urlConverter)\r
9920                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);\r
9921 \r
9922                                         node.attr(name, value.length > 0 ? value : null);\r
9923                                 }\r
9924                         }\r
9925                 });\r
9926 \r
9927                 // Remove internal classes mceItem<..> or mceSelected\r
9928                 htmlParser.addAttributeFilter('class', function(nodes, name) {\r
9929                         var i = nodes.length, node, value;\r
9930 \r
9931                         while (i--) {\r
9932                                 node = nodes[i];\r
9933                                 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');\r
9934                                 node.attr('class', value.length > 0 ? value : null);\r
9935                         }\r
9936                 });\r
9937 \r
9938                 // Remove bookmark elements\r
9939                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {\r
9940                         var i = nodes.length, node;\r
9941 \r
9942                         while (i--) {\r
9943                                 node = nodes[i];\r
9944 \r
9945                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)\r
9946                                         node.remove();\r
9947                         }\r
9948                 });\r
9949 \r
9950                 // Remove expando attributes\r
9951                 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {\r
9952                         var i = nodes.length;\r
9953 \r
9954                         while (i--) {\r
9955                                 nodes[i].attr(name, null);\r
9956                         }\r
9957                 });\r
9958 \r
9959                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles\r
9960                 htmlParser.addNodeFilter('script,style', function(nodes, name) {\r
9961                         var i = nodes.length, node, value;\r
9962 \r
9963                         function trim(value) {\r
9964                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')\r
9965                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')\r
9966                                                 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')\r
9967                                                 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');\r
9968                         };\r
9969 \r
9970                         while (i--) {\r
9971                                 node = nodes[i];\r
9972                                 value = node.firstChild ? node.firstChild.value : '';\r
9973 \r
9974                                 if (name === "script") {\r
9975                                         // Remove mce- prefix from script elements\r
9976                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));\r
9977 \r
9978                                         if (value.length > 0)\r
9979                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';\r
9980                                 } else {\r
9981                                         if (value.length > 0)\r
9982                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';\r
9983                                 }\r
9984                         }\r
9985                 });\r
9986 \r
9987                 // Convert comments to cdata and handle protected comments\r
9988                 htmlParser.addNodeFilter('#comment', function(nodes, name) {\r
9989                         var i = nodes.length, node;\r
9990 \r
9991                         while (i--) {\r
9992                                 node = nodes[i];\r
9993 \r
9994                                 if (node.value.indexOf('[CDATA[') === 0) {\r
9995                                         node.name = '#cdata';\r
9996                                         node.type = 4;\r
9997                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');\r
9998                                 } else if (node.value.indexOf('mce:protected ') === 0) {\r
9999                                         node.name = "#text";\r
10000                                         node.type = 3;\r
10001                                         node.raw = true;\r
10002                                         node.value = unescape(node.value).substr(14);\r
10003                                 }\r
10004                         }\r
10005                 });\r
10006 \r
10007                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {\r
10008                         var i = nodes.length, node;\r
10009 \r
10010                         while (i--) {\r
10011                                 node = nodes[i];\r
10012                                 if (node.type === 7)\r
10013                                         node.remove();\r
10014                                 else if (node.type === 1) {\r
10015                                         if (name === "input" && !("type" in node.attributes.map))\r
10016                                                 node.attr('type', 'text');\r
10017                                 }\r
10018                         }\r
10019                 });\r
10020 \r
10021                 // Fix list elements, TODO: Replace this later\r
10022                 if (settings.fix_list_elements) {\r
10023                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {\r
10024                                 var i = nodes.length, node, parentNode;\r
10025 \r
10026                                 while (i--) {\r
10027                                         node = nodes[i];\r
10028                                         parentNode = node.parent;\r
10029 \r
10030                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {\r
10031                                                 if (node.prev && node.prev.name === 'li') {\r
10032                                                         node.prev.append(node);\r
10033                                                 }\r
10034                                         }\r
10035                                 }\r
10036                         });\r
10037                 }\r
10038 \r
10039                 // Remove internal data attributes\r
10040                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {\r
10041                         var i = nodes.length;\r
10042 \r
10043                         while (i--) {\r
10044                                 nodes[i].attr(name, null);\r
10045                         }\r
10046                 });\r
10047 \r
10048                 // Return public methods\r
10049                 return {\r
10050                         schema : schema,\r
10051 \r
10052                         addNodeFilter : htmlParser.addNodeFilter,\r
10053 \r
10054                         addAttributeFilter : htmlParser.addAttributeFilter,\r
10055 \r
10056                         onPreProcess : onPreProcess,\r
10057 \r
10058                         onPostProcess : onPostProcess,\r
10059 \r
10060                         serialize : function(node, args) {\r
10061                                 var impl, doc, oldDoc, htmlSerializer, content;\r
10062 \r
10063                                 // Explorer won't clone contents of script and style and the\r
10064                                 // selected index of select elements are cleared on a clone operation.\r
10065                                 if (isIE && dom.select('script,style,select,map').length > 0) {\r
10066                                         content = node.innerHTML;\r
10067                                         node = node.cloneNode(false);\r
10068                                         dom.setHTML(node, content);\r
10069                                 } else\r
10070                                         node = node.cloneNode(true);\r
10071 \r
10072                                 // Nodes needs to be attached to something in WebKit/Opera\r
10073                                 // Older builds of Opera crashes if you attach the node to an document created dynamically\r
10074                                 // and since we can't feature detect a crash we need to sniff the acutal build number\r
10075                                 // This fix will make DOM ranges and make Sizzle happy!\r
10076                                 impl = node.ownerDocument.implementation;\r
10077                                 if (impl.createHTMLDocument) {\r
10078                                         // Create an empty HTML document\r
10079                                         doc = impl.createHTMLDocument("");\r
10080 \r
10081                                         // Add the element or it's children if it's a body element to the new document\r
10082                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {\r
10083                                                 doc.body.appendChild(doc.importNode(node, true));\r
10084                                         });\r
10085 \r
10086                                         // Grab first child or body element for serialization\r
10087                                         if (node.nodeName != 'BODY')\r
10088                                                 node = doc.body.firstChild;\r
10089                                         else\r
10090                                                 node = doc.body;\r
10091 \r
10092                                         // set the new document in DOMUtils so createElement etc works\r
10093                                         oldDoc = dom.doc;\r
10094                                         dom.doc = doc;\r
10095                                 }\r
10096 \r
10097                                 args = args || {};\r
10098                                 args.format = args.format || 'html';\r
10099 \r
10100                                 // Pre process\r
10101                                 if (!args.no_events) {\r
10102                                         args.node = node;\r
10103                                         onPreProcess.dispatch(self, args);\r
10104                                 }\r
10105 \r
10106                                 // Setup serializer\r
10107                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);\r
10108 \r
10109                                 // Parse and serialize HTML\r
10110                                 args.content = htmlSerializer.serialize(\r
10111                                         htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)\r
10112                                 );\r
10113 \r
10114                                 // Replace all BOM characters for now until we can find a better solution\r
10115                                 if (!args.cleanup)\r
10116                                         args.content = args.content.replace(/\uFEFF|\u200B/g, '');\r
10117 \r
10118                                 // Post process\r
10119                                 if (!args.no_events)\r
10120                                         onPostProcess.dispatch(self, args);\r
10121 \r
10122                                 // Restore the old document if it was changed\r
10123                                 if (oldDoc)\r
10124                                         dom.doc = oldDoc;\r
10125 \r
10126                                 args.node = null;\r
10127 \r
10128                                 return args.content;\r
10129                         },\r
10130 \r
10131                         addRules : function(rules) {\r
10132                                 schema.addValidElements(rules);\r
10133                         },\r
10134 \r
10135                         setRules : function(rules) {\r
10136                                 schema.setValidElements(rules);\r
10137                         }\r
10138                 };\r
10139         };\r
10140 })(tinymce);\r
10141 (function(tinymce) {\r
10142         tinymce.dom.ScriptLoader = function(settings) {\r
10143                 var QUEUED = 0,\r
10144                         LOADING = 1,\r
10145                         LOADED = 2,\r
10146                         states = {},\r
10147                         queue = [],\r
10148                         scriptLoadedCallbacks = {},\r
10149                         queueLoadedCallbacks = [],\r
10150                         loading = 0,\r
10151                         undef;\r
10152 \r
10153                 function loadScript(url, callback) {\r
10154                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;\r
10155 \r
10156                         // Execute callback when script is loaded\r
10157                         function done() {\r
10158                                 dom.remove(id);\r
10159 \r
10160                                 if (elm)\r
10161                                         elm.onreadystatechange = elm.onload = elm = null;\r
10162 \r
10163                                 callback();\r
10164                         };\r
10165                         \r
10166                         function error() {\r
10167                                 // Report the error so it's easier for people to spot loading errors\r
10168                                 if (typeof(console) !== "undefined" && console.log)\r
10169                                         console.log("Failed to load: " + url);\r
10170 \r
10171                                 // We can't mark it as done if there is a load error since\r
10172                                 // A) We don't want to produce 404 errors on the server and\r
10173                                 // B) the onerror event won't fire on all browsers.\r
10174                                 // done();\r
10175                         };\r
10176 \r
10177                         id = dom.uniqueId();\r
10178 \r
10179                         if (tinymce.isIE6) {\r
10180                                 uri = new tinymce.util.URI(url);\r
10181                                 loc = location;\r
10182 \r
10183                                 // If script is from same domain and we\r
10184                                 // use IE 6 then use XHR since it's more reliable\r
10185                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {\r
10186                                         tinymce.util.XHR.send({\r
10187                                                 url : tinymce._addVer(uri.getURI()),\r
10188                                                 success : function(content) {\r
10189                                                         // Create new temp script element\r
10190                                                         var script = dom.create('script', {\r
10191                                                                 type : 'text/javascript'\r
10192                                                         });\r
10193 \r
10194                                                         // Evaluate script in global scope\r
10195                                                         script.text = content;\r
10196                                                         document.getElementsByTagName('head')[0].appendChild(script);\r
10197                                                         dom.remove(script);\r
10198 \r
10199                                                         done();\r
10200                                                 },\r
10201                                                 \r
10202                                                 error : error\r
10203                                         });\r
10204 \r
10205                                         return;\r
10206                                 }\r
10207                         }\r
10208 \r
10209                         // Create new script element\r
10210                         elm = dom.create('script', {\r
10211                                 id : id,\r
10212                                 type : 'text/javascript',\r
10213                                 src : tinymce._addVer(url)\r
10214                         });\r
10215 \r
10216                         // Add onload listener for non IE browsers since IE9\r
10217                         // fires onload event before the script is parsed and executed\r
10218                         if (!tinymce.isIE)\r
10219                                 elm.onload = done;\r
10220 \r
10221                         // Add onerror event will get fired on some browsers but not all of them\r
10222                         elm.onerror = error;\r
10223 \r
10224                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly\r
10225                         if (!tinymce.isOpera) {\r
10226                                 elm.onreadystatechange = function() {\r
10227                                         var state = elm.readyState;\r
10228 \r
10229                                         // Loaded state is passed on IE 6 however there\r
10230                                         // are known issues with this method but we can't use\r
10231                                         // XHR in a cross domain loading\r
10232                                         if (state == 'complete' || state == 'loaded')\r
10233                                                 done();\r
10234                                 };\r
10235                         }\r
10236 \r
10237                         // Most browsers support this feature so we report errors\r
10238                         // for those at least to help users track their missing plugins etc\r
10239                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option\r
10240                         /*elm.onerror = function() {\r
10241                                 alert('Failed to load: ' + url);\r
10242                         };*/\r
10243 \r
10244                         // Add script to document\r
10245                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);\r
10246                 };\r
10247 \r
10248                 this.isDone = function(url) {\r
10249                         return states[url] == LOADED;\r
10250                 };\r
10251 \r
10252                 this.markDone = function(url) {\r
10253                         states[url] = LOADED;\r
10254                 };\r
10255 \r
10256                 this.add = this.load = function(url, callback, scope) {\r
10257                         var item, state = states[url];\r
10258 \r
10259                         // Add url to load queue\r
10260                         if (state == undef) {\r
10261                                 queue.push(url);\r
10262                                 states[url] = QUEUED;\r
10263                         }\r
10264 \r
10265                         if (callback) {\r
10266                                 // Store away callback for later execution\r
10267                                 if (!scriptLoadedCallbacks[url])\r
10268                                         scriptLoadedCallbacks[url] = [];\r
10269 \r
10270                                 scriptLoadedCallbacks[url].push({\r
10271                                         func : callback,\r
10272                                         scope : scope || this\r
10273                                 });\r
10274                         }\r
10275                 };\r
10276 \r
10277                 this.loadQueue = function(callback, scope) {\r
10278                         this.loadScripts(queue, callback, scope);\r
10279                 };\r
10280 \r
10281                 this.loadScripts = function(scripts, callback, scope) {\r
10282                         var loadScripts;\r
10283 \r
10284                         function execScriptLoadedCallbacks(url) {\r
10285                                 // Execute URL callback functions\r
10286                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {\r
10287                                         callback.func.call(callback.scope);\r
10288                                 });\r
10289 \r
10290                                 scriptLoadedCallbacks[url] = undef;\r
10291                         };\r
10292 \r
10293                         queueLoadedCallbacks.push({\r
10294                                 func : callback,\r
10295                                 scope : scope || this\r
10296                         });\r
10297 \r
10298                         loadScripts = function() {\r
10299                                 var loadingScripts = tinymce.grep(scripts);\r
10300 \r
10301                                 // Current scripts has been handled\r
10302                                 scripts.length = 0;\r
10303 \r
10304                                 // Load scripts that needs to be loaded\r
10305                                 tinymce.each(loadingScripts, function(url) {\r
10306                                         // Script is already loaded then execute script callbacks directly\r
10307                                         if (states[url] == LOADED) {\r
10308                                                 execScriptLoadedCallbacks(url);\r
10309                                                 return;\r
10310                                         }\r
10311 \r
10312                                         // Is script not loading then start loading it\r
10313                                         if (states[url] != LOADING) {\r
10314                                                 states[url] = LOADING;\r
10315                                                 loading++;\r
10316 \r
10317                                                 loadScript(url, function() {\r
10318                                                         states[url] = LOADED;\r
10319                                                         loading--;\r
10320 \r
10321                                                         execScriptLoadedCallbacks(url);\r
10322 \r
10323                                                         // Load more scripts if they where added by the recently loaded script\r
10324                                                         loadScripts();\r
10325                                                 });\r
10326                                         }\r
10327                                 });\r
10328 \r
10329                                 // No scripts are currently loading then execute all pending queue loaded callbacks\r
10330                                 if (!loading) {\r
10331                                         tinymce.each(queueLoadedCallbacks, function(callback) {\r
10332                                                 callback.func.call(callback.scope);\r
10333                                         });\r
10334 \r
10335                                         queueLoadedCallbacks.length = 0;\r
10336                                 }\r
10337                         };\r
10338 \r
10339                         loadScripts();\r
10340                 };\r
10341         };\r
10342 \r
10343         // Global script loader\r
10344         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();\r
10345 })(tinymce);\r
10346 \r
10347 (function(tinymce) {\r
10348         tinymce.dom.RangeUtils = function(dom) {\r
10349                 var INVISIBLE_CHAR = '\uFEFF';\r
10350 \r
10351                 this.walk = function(rng, callback) {\r
10352                         var startContainer = rng.startContainer,\r
10353                                 startOffset = rng.startOffset,\r
10354                                 endContainer = rng.endContainer,\r
10355                                 endOffset = rng.endOffset,\r
10356                                 ancestor, startPoint,\r
10357                                 endPoint, node, parent, siblings, nodes;\r
10358 \r
10359                         // Handle table cell selection the table plugin enables\r
10360                         // you to fake select table cells and perform formatting actions on them\r
10361                         nodes = dom.select('td.mceSelected,th.mceSelected');\r
10362                         if (nodes.length > 0) {\r
10363                                 tinymce.each(nodes, function(node) {\r
10364                                         callback([node]);\r
10365                                 });\r
10366 \r
10367                                 return;\r
10368                         }\r
10369 \r
10370                         function exclude(nodes) {\r
10371                                 var node;\r
10372 \r
10373                                 // First node is excluded\r
10374                                 node = nodes[0];\r
10375                                 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {\r
10376                                         nodes.splice(0, 1);\r
10377                                 }\r
10378 \r
10379                                 // Last node is excluded\r
10380                                 node = nodes[nodes.length - 1];\r
10381                                 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {\r
10382                                         nodes.splice(nodes.length - 1, 1);\r
10383                                 }\r
10384 \r
10385                                 return nodes;\r
10386                         };\r
10387 \r
10388                         function collectSiblings(node, name, end_node) {\r
10389                                 var siblings = [];\r
10390 \r
10391                                 for (; node && node != end_node; node = node[name])\r
10392                                         siblings.push(node);\r
10393 \r
10394                                 return siblings;\r
10395                         };\r
10396 \r
10397                         function findEndPoint(node, root) {\r
10398                                 do {\r
10399                                         if (node.parentNode == root)\r
10400                                                 return node;\r
10401 \r
10402                                         node = node.parentNode;\r
10403                                 } while(node);\r
10404                         };\r
10405 \r
10406                         function walkBoundary(start_node, end_node, next) {\r
10407                                 var siblingName = next ? 'nextSibling' : 'previousSibling';\r
10408 \r
10409                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {\r
10410                                         parent = node.parentNode;\r
10411                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);\r
10412 \r
10413                                         if (siblings.length) {\r
10414                                                 if (!next)\r
10415                                                         siblings.reverse();\r
10416 \r
10417                                                 callback(exclude(siblings));\r
10418                                         }\r
10419                                 }\r
10420                         };\r
10421 \r
10422                         // If index based start position then resolve it\r
10423                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())\r
10424                                 startContainer = startContainer.childNodes[startOffset];\r
10425 \r
10426                         // If index based end position then resolve it\r
10427                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())\r
10428                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];\r
10429 \r
10430                         // Same container\r
10431                         if (startContainer == endContainer)\r
10432                                 return callback(exclude([startContainer]));\r
10433 \r
10434                         // Find common ancestor and end points\r
10435                         ancestor = dom.findCommonAncestor(startContainer, endContainer);\r
10436                                 \r
10437                         // Process left side\r
10438                         for (node = startContainer; node; node = node.parentNode) {\r
10439                                 if (node === endContainer)\r
10440                                         return walkBoundary(startContainer, ancestor, true);\r
10441 \r
10442                                 if (node === ancestor)\r
10443                                         break;\r
10444                         }\r
10445 \r
10446                         // Process right side\r
10447                         for (node = endContainer; node; node = node.parentNode) {\r
10448                                 if (node === startContainer)\r
10449                                         return walkBoundary(endContainer, ancestor);\r
10450 \r
10451                                 if (node === ancestor)\r
10452                                         break;\r
10453                         }\r
10454 \r
10455                         // Find start/end point\r
10456                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;\r
10457                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;\r
10458 \r
10459                         // Walk left leaf\r
10460                         walkBoundary(startContainer, startPoint, true);\r
10461 \r
10462                         // Walk the middle from start to end point\r
10463                         siblings = collectSiblings(\r
10464                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,\r
10465                                 'nextSibling',\r
10466                                 endPoint == endContainer ? endPoint.nextSibling : endPoint\r
10467                         );\r
10468 \r
10469                         if (siblings.length)\r
10470                                 callback(exclude(siblings));\r
10471 \r
10472                         // Walk right leaf\r
10473                         walkBoundary(endContainer, endPoint);\r
10474                 };\r
10475 \r
10476                 this.split = function(rng) {\r
10477                         var startContainer = rng.startContainer,\r
10478                                 startOffset = rng.startOffset,\r
10479                                 endContainer = rng.endContainer,\r
10480                                 endOffset = rng.endOffset;\r
10481 \r
10482                         function splitText(node, offset) {\r
10483                                 return node.splitText(offset);\r
10484                         };\r
10485 \r
10486                         // Handle single text node\r
10487                         if (startContainer == endContainer && startContainer.nodeType == 3) {\r
10488                                 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
10489                                         endContainer = splitText(startContainer, startOffset);\r
10490                                         startContainer = endContainer.previousSibling;\r
10491 \r
10492                                         if (endOffset > startOffset) {\r
10493                                                 endOffset = endOffset - startOffset;\r
10494                                                 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;\r
10495                                                 endOffset = endContainer.nodeValue.length;\r
10496                                                 startOffset = 0;\r
10497                                         } else {\r
10498                                                 endOffset = 0;\r
10499                                         }\r
10500                                 }\r
10501                         } else {\r
10502                                 // Split startContainer text node if needed\r
10503                                 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
10504                                         startContainer = splitText(startContainer, startOffset);\r
10505                                         startOffset = 0;\r
10506                                 }\r
10507 \r
10508                                 // Split endContainer text node if needed\r
10509                                 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {\r
10510                                         endContainer = splitText(endContainer, endOffset).previousSibling;\r
10511                                         endOffset = endContainer.nodeValue.length;\r
10512                                 }\r
10513                         }\r
10514 \r
10515                         return {\r
10516                                 startContainer : startContainer,\r
10517                                 startOffset : startOffset,\r
10518                                 endContainer : endContainer,\r
10519                                 endOffset : endOffset\r
10520                         };\r
10521                 };\r
10522 \r
10523         };\r
10524 \r
10525         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {\r
10526                 if (rng1 && rng2) {\r
10527                         // Compare native IE ranges\r
10528                         if (rng1.item || rng1.duplicate) {\r
10529                                 // Both are control ranges and the selected element matches\r
10530                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))\r
10531                                         return true;\r
10532 \r
10533                                 // Both are text ranges and the range matches\r
10534                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))\r
10535                                         return true;\r
10536                         } else {\r
10537                                 // Compare w3c ranges\r
10538                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;\r
10539                         }\r
10540                 }\r
10541 \r
10542                 return false;\r
10543         };\r
10544 })(tinymce);\r
10545 \r
10546 (function(tinymce) {\r
10547         var Event = tinymce.dom.Event, each = tinymce.each;\r
10548 \r
10549         tinymce.create('tinymce.ui.KeyboardNavigation', {\r
10550                 KeyboardNavigation: function(settings, dom) {\r
10551                         var t = this, root = settings.root, items = settings.items,\r
10552                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,\r
10553                                         excludeFromTabOrder = settings.excludeFromTabOrder,\r
10554                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;\r
10555 \r
10556                         dom = dom || tinymce.DOM;\r
10557 \r
10558                         itemFocussed = function(evt) {\r
10559                                 focussedId = evt.target.id;\r
10560                         };\r
10561                         \r
10562                         itemBlurred = function(evt) {\r
10563                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');\r
10564                         };\r
10565                         \r
10566                         rootFocussed = function(evt) {\r
10567                                 var item = dom.get(focussedId);\r
10568                                 dom.setAttrib(item, 'tabindex', '0');\r
10569                                 item.focus();\r
10570                         };\r
10571                         \r
10572                         t.focus = function() {\r
10573                                 dom.get(focussedId).focus();\r
10574                         };\r
10575 \r
10576                         t.destroy = function() {\r
10577                                 each(items, function(item) {\r
10578                                         dom.unbind(dom.get(item.id), 'focus', itemFocussed);\r
10579                                         dom.unbind(dom.get(item.id), 'blur', itemBlurred);\r
10580                                 });\r
10581 \r
10582                                 dom.unbind(dom.get(root), 'focus', rootFocussed);\r
10583                                 dom.unbind(dom.get(root), 'keydown', rootKeydown);\r
10584 \r
10585                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;\r
10586                                 t.destroy = function() {};\r
10587                         };\r
10588                         \r
10589                         t.moveFocus = function(dir, evt) {\r
10590                                 var idx = -1, controls = t.controls, newFocus;\r
10591 \r
10592                                 if (!focussedId)\r
10593                                         return;\r
10594 \r
10595                                 each(items, function(item, index) {\r
10596                                         if (item.id === focussedId) {\r
10597                                                 idx = index;\r
10598                                                 return false;\r
10599                                         }\r
10600                                 });\r
10601 \r
10602                                 idx += dir;\r
10603                                 if (idx < 0) {\r
10604                                         idx = items.length - 1;\r
10605                                 } else if (idx >= items.length) {\r
10606                                         idx = 0;\r
10607                                 }\r
10608                                 \r
10609                                 newFocus = items[idx];\r
10610                                 dom.setAttrib(focussedId, 'tabindex', '-1');\r
10611                                 dom.setAttrib(newFocus.id, 'tabindex', '0');\r
10612                                 dom.get(newFocus.id).focus();\r
10613 \r
10614                                 if (settings.actOnFocus) {\r
10615                                         settings.onAction(newFocus.id);\r
10616                                 }\r
10617 \r
10618                                 if (evt)\r
10619                                         Event.cancel(evt);\r
10620                         };\r
10621                         \r
10622                         rootKeydown = function(evt) {\r
10623                                 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
10624                                 \r
10625                                 switch (evt.keyCode) {\r
10626                                         case DOM_VK_LEFT:\r
10627                                                 if (enableLeftRight) t.moveFocus(-1);\r
10628                                                 break;\r
10629         \r
10630                                         case DOM_VK_RIGHT:\r
10631                                                 if (enableLeftRight) t.moveFocus(1);\r
10632                                                 break;\r
10633         \r
10634                                         case DOM_VK_UP:\r
10635                                                 if (enableUpDown) t.moveFocus(-1);\r
10636                                                 break;\r
10637 \r
10638                                         case DOM_VK_DOWN:\r
10639                                                 if (enableUpDown) t.moveFocus(1);\r
10640                                                 break;\r
10641 \r
10642                                         case DOM_VK_ESCAPE:\r
10643                                                 if (settings.onCancel) {\r
10644                                                         settings.onCancel();\r
10645                                                         Event.cancel(evt);\r
10646                                                 }\r
10647                                                 break;\r
10648 \r
10649                                         case DOM_VK_ENTER:\r
10650                                         case DOM_VK_RETURN:\r
10651                                         case DOM_VK_SPACE:\r
10652                                                 if (settings.onAction) {\r
10653                                                         settings.onAction(focussedId);\r
10654                                                         Event.cancel(evt);\r
10655                                                 }\r
10656                                                 break;\r
10657                                 }\r
10658                         };\r
10659 \r
10660                         // Set up state and listeners for each item.\r
10661                         each(items, function(item, idx) {\r
10662                                 var tabindex;\r
10663 \r
10664                                 if (!item.id) {\r
10665                                         item.id = dom.uniqueId('_mce_item_');\r
10666                                 }\r
10667 \r
10668                                 if (excludeFromTabOrder) {\r
10669                                         dom.bind(item.id, 'blur', itemBlurred);\r
10670                                         tabindex = '-1';\r
10671                                 } else {\r
10672                                         tabindex = (idx === 0 ? '0' : '-1');\r
10673                                 }\r
10674 \r
10675                                 dom.setAttrib(item.id, 'tabindex', tabindex);\r
10676                                 dom.bind(dom.get(item.id), 'focus', itemFocussed);\r
10677                         });\r
10678                         \r
10679                         // Setup initial state for root element.\r
10680                         if (items[0]){\r
10681                                 focussedId = items[0].id;\r
10682                         }\r
10683 \r
10684                         dom.setAttrib(root, 'tabindex', '-1');\r
10685                         \r
10686                         // Setup listeners for root element.\r
10687                         dom.bind(dom.get(root), 'focus', rootFocussed);\r
10688                         dom.bind(dom.get(root), 'keydown', rootKeydown);\r
10689                 }\r
10690         });\r
10691 })(tinymce);\r
10692 \r
10693 (function(tinymce) {\r
10694         // Shorten class names\r
10695         var DOM = tinymce.DOM, is = tinymce.is;\r
10696 \r
10697         tinymce.create('tinymce.ui.Control', {\r
10698                 Control : function(id, s, editor) {\r
10699                         this.id = id;\r
10700                         this.settings = s = s || {};\r
10701                         this.rendered = false;\r
10702                         this.onRender = new tinymce.util.Dispatcher(this);\r
10703                         this.classPrefix = '';\r
10704                         this.scope = s.scope || this;\r
10705                         this.disabled = 0;\r
10706                         this.active = 0;\r
10707                         this.editor = editor;\r
10708                 },\r
10709                 \r
10710                 setAriaProperty : function(property, value) {\r
10711                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);\r
10712                         if (element) {\r
10713                                 DOM.setAttrib(element, 'aria-' + property, !!value);\r
10714                         }\r
10715                 },\r
10716                 \r
10717                 focus : function() {\r
10718                         DOM.get(this.id).focus();\r
10719                 },\r
10720 \r
10721                 setDisabled : function(s) {\r
10722                         if (s != this.disabled) {\r
10723                                 this.setAriaProperty('disabled', s);\r
10724 \r
10725                                 this.setState('Disabled', s);\r
10726                                 this.setState('Enabled', !s);\r
10727                                 this.disabled = s;\r
10728                         }\r
10729                 },\r
10730 \r
10731                 isDisabled : function() {\r
10732                         return this.disabled;\r
10733                 },\r
10734 \r
10735                 setActive : function(s) {\r
10736                         if (s != this.active) {\r
10737                                 this.setState('Active', s);\r
10738                                 this.active = s;\r
10739                                 this.setAriaProperty('pressed', s);\r
10740                         }\r
10741                 },\r
10742 \r
10743                 isActive : function() {\r
10744                         return this.active;\r
10745                 },\r
10746 \r
10747                 setState : function(c, s) {\r
10748                         var n = DOM.get(this.id);\r
10749 \r
10750                         c = this.classPrefix + c;\r
10751 \r
10752                         if (s)\r
10753                                 DOM.addClass(n, c);\r
10754                         else\r
10755                                 DOM.removeClass(n, c);\r
10756                 },\r
10757 \r
10758                 isRendered : function() {\r
10759                         return this.rendered;\r
10760                 },\r
10761 \r
10762                 renderHTML : function() {\r
10763                 },\r
10764 \r
10765                 renderTo : function(n) {\r
10766                         DOM.setHTML(n, this.renderHTML());\r
10767                 },\r
10768 \r
10769                 postRender : function() {\r
10770                         var t = this, b;\r
10771 \r
10772                         // Set pending states\r
10773                         if (is(t.disabled)) {\r
10774                                 b = t.disabled;\r
10775                                 t.disabled = -1;\r
10776                                 t.setDisabled(b);\r
10777                         }\r
10778 \r
10779                         if (is(t.active)) {\r
10780                                 b = t.active;\r
10781                                 t.active = -1;\r
10782                                 t.setActive(b);\r
10783                         }\r
10784                 },\r
10785 \r
10786                 remove : function() {\r
10787                         DOM.remove(this.id);\r
10788                         this.destroy();\r
10789                 },\r
10790 \r
10791                 destroy : function() {\r
10792                         tinymce.dom.Event.clear(this.id);\r
10793                 }\r
10794         });\r
10795 })(tinymce);\r
10796 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {\r
10797         Container : function(id, s, editor) {\r
10798                 this.parent(id, s, editor);\r
10799 \r
10800                 this.controls = [];\r
10801 \r
10802                 this.lookup = {};\r
10803         },\r
10804 \r
10805         add : function(c) {\r
10806                 this.lookup[c.id] = c;\r
10807                 this.controls.push(c);\r
10808 \r
10809                 return c;\r
10810         },\r
10811 \r
10812         get : function(n) {\r
10813                 return this.lookup[n];\r
10814         }\r
10815 });\r
10816 \r
10817 \r
10818 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {\r
10819         Separator : function(id, s) {\r
10820                 this.parent(id, s);\r
10821                 this.classPrefix = 'mceSeparator';\r
10822                 this.setDisabled(true);\r
10823         },\r
10824 \r
10825         renderHTML : function() {\r
10826                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});\r
10827         }\r
10828 });\r
10829 \r
10830 (function(tinymce) {\r
10831         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
10832 \r
10833         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {\r
10834                 MenuItem : function(id, s) {\r
10835                         this.parent(id, s);\r
10836                         this.classPrefix = 'mceMenuItem';\r
10837                 },\r
10838 \r
10839                 setSelected : function(s) {\r
10840                         this.setState('Selected', s);\r
10841                         this.setAriaProperty('checked', !!s);\r
10842                         this.selected = s;\r
10843                 },\r
10844 \r
10845                 isSelected : function() {\r
10846                         return this.selected;\r
10847                 },\r
10848 \r
10849                 postRender : function() {\r
10850                         var t = this;\r
10851                         \r
10852                         t.parent();\r
10853 \r
10854                         // Set pending state\r
10855                         if (is(t.selected))\r
10856                                 t.setSelected(t.selected);\r
10857                 }\r
10858         });\r
10859 })(tinymce);\r
10860 \r
10861 (function(tinymce) {\r
10862         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
10863 \r
10864         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {\r
10865                 Menu : function(id, s) {\r
10866                         var t = this;\r
10867 \r
10868                         t.parent(id, s);\r
10869                         t.items = {};\r
10870                         t.collapsed = false;\r
10871                         t.menuCount = 0;\r
10872                         t.onAddItem = new tinymce.util.Dispatcher(this);\r
10873                 },\r
10874 \r
10875                 expand : function(d) {\r
10876                         var t = this;\r
10877 \r
10878                         if (d) {\r
10879                                 walk(t, function(o) {\r
10880                                         if (o.expand)\r
10881                                                 o.expand();\r
10882                                 }, 'items', t);\r
10883                         }\r
10884 \r
10885                         t.collapsed = false;\r
10886                 },\r
10887 \r
10888                 collapse : function(d) {\r
10889                         var t = this;\r
10890 \r
10891                         if (d) {\r
10892                                 walk(t, function(o) {\r
10893                                         if (o.collapse)\r
10894                                                 o.collapse();\r
10895                                 }, 'items', t);\r
10896                         }\r
10897 \r
10898                         t.collapsed = true;\r
10899                 },\r
10900 \r
10901                 isCollapsed : function() {\r
10902                         return this.collapsed;\r
10903                 },\r
10904 \r
10905                 add : function(o) {\r
10906                         if (!o.settings)\r
10907                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);\r
10908 \r
10909                         this.onAddItem.dispatch(this, o);\r
10910 \r
10911                         return this.items[o.id] = o;\r
10912                 },\r
10913 \r
10914                 addSeparator : function() {\r
10915                         return this.add({separator : true});\r
10916                 },\r
10917 \r
10918                 addMenu : function(o) {\r
10919                         if (!o.collapse)\r
10920                                 o = this.createMenu(o);\r
10921 \r
10922                         this.menuCount++;\r
10923 \r
10924                         return this.add(o);\r
10925                 },\r
10926 \r
10927                 hasMenus : function() {\r
10928                         return this.menuCount !== 0;\r
10929                 },\r
10930 \r
10931                 remove : function(o) {\r
10932                         delete this.items[o.id];\r
10933                 },\r
10934 \r
10935                 removeAll : function() {\r
10936                         var t = this;\r
10937 \r
10938                         walk(t, function(o) {\r
10939                                 if (o.removeAll)\r
10940                                         o.removeAll();\r
10941                                 else\r
10942                                         o.remove();\r
10943 \r
10944                                 o.destroy();\r
10945                         }, 'items', t);\r
10946 \r
10947                         t.items = {};\r
10948                 },\r
10949 \r
10950                 createMenu : function(o) {\r
10951                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);\r
10952 \r
10953                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);\r
10954 \r
10955                         return m;\r
10956                 }\r
10957         });\r
10958 })(tinymce);\r
10959 (function(tinymce) {\r
10960         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;\r
10961 \r
10962         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {\r
10963                 DropMenu : function(id, s) {\r
10964                         s = s || {};\r
10965                         s.container = s.container || DOM.doc.body;\r
10966                         s.offset_x = s.offset_x || 0;\r
10967                         s.offset_y = s.offset_y || 0;\r
10968                         s.vp_offset_x = s.vp_offset_x || 0;\r
10969                         s.vp_offset_y = s.vp_offset_y || 0;\r
10970 \r
10971                         if (is(s.icons) && !s.icons)\r
10972                                 s['class'] += ' mceNoIcons';\r
10973 \r
10974                         this.parent(id, s);\r
10975                         this.onShowMenu = new tinymce.util.Dispatcher(this);\r
10976                         this.onHideMenu = new tinymce.util.Dispatcher(this);\r
10977                         this.classPrefix = 'mceMenu';\r
10978                 },\r
10979 \r
10980                 createMenu : function(s) {\r
10981                         var t = this, cs = t.settings, m;\r
10982 \r
10983                         s.container = s.container || cs.container;\r
10984                         s.parent = t;\r
10985                         s.constrain = s.constrain || cs.constrain;\r
10986                         s['class'] = s['class'] || cs['class'];\r
10987                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;\r
10988                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;\r
10989                         s.keyboard_focus = cs.keyboard_focus;\r
10990                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);\r
10991 \r
10992                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);\r
10993 \r
10994                         return m;\r
10995                 },\r
10996                 \r
10997                 focus : function() {\r
10998                         var t = this;\r
10999                         if (t.keyboardNav) {\r
11000                                 t.keyboardNav.focus();\r
11001                         }\r
11002                 },\r
11003 \r
11004                 update : function() {\r
11005                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;\r
11006 \r
11007                         tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;\r
11008                         th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;\r
11009 \r
11010                         if (!DOM.boxModel)\r
11011                                 t.element.setStyles({width : tw + 2, height : th + 2});\r
11012                         else\r
11013                                 t.element.setStyles({width : tw, height : th});\r
11014 \r
11015                         if (s.max_width)\r
11016                                 DOM.setStyle(co, 'width', tw);\r
11017 \r
11018                         if (s.max_height) {\r
11019                                 DOM.setStyle(co, 'height', th);\r
11020 \r
11021                                 if (tb.clientHeight < s.max_height)\r
11022                                         DOM.setStyle(co, 'overflow', 'hidden');\r
11023                         }\r
11024                 },\r
11025 \r
11026                 showMenu : function(x, y, px) {\r
11027                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;\r
11028 \r
11029                         t.collapse(1);\r
11030 \r
11031                         if (t.isMenuVisible)\r
11032                                 return;\r
11033 \r
11034                         if (!t.rendered) {\r
11035                                 co = DOM.add(t.settings.container, t.renderNode());\r
11036 \r
11037                                 each(t.items, function(o) {\r
11038                                         o.postRender();\r
11039                                 });\r
11040 \r
11041                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
11042                         } else\r
11043                                 co = DOM.get('menu_' + t.id);\r
11044 \r
11045                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug\r
11046                         if (!tinymce.isOpera)\r
11047                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});\r
11048 \r
11049                         DOM.show(co);\r
11050                         t.update();\r
11051 \r
11052                         x += s.offset_x || 0;\r
11053                         y += s.offset_y || 0;\r
11054                         vp.w -= 4;\r
11055                         vp.h -= 4;\r
11056 \r
11057                         // Move inside viewport if not submenu\r
11058                         if (s.constrain) {\r
11059                                 w = co.clientWidth - ot;\r
11060                                 h = co.clientHeight - ot;\r
11061                                 mx = vp.x + vp.w;\r
11062                                 my = vp.y + vp.h;\r
11063 \r
11064                                 if ((x + s.vp_offset_x + w) > mx)\r
11065                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);\r
11066 \r
11067                                 if ((y + s.vp_offset_y + h) > my)\r
11068                                         y = Math.max(0, (my - s.vp_offset_y) - h);\r
11069                         }\r
11070 \r
11071                         DOM.setStyles(co, {left : x , top : y});\r
11072                         t.element.update();\r
11073 \r
11074                         t.isMenuVisible = 1;\r
11075                         t.mouseClickFunc = Event.add(co, 'click', function(e) {\r
11076                                 var m;\r
11077 \r
11078                                 e = e.target;\r
11079 \r
11080                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {\r
11081                                         m = t.items[e.id];\r
11082 \r
11083                                         if (m.isDisabled())\r
11084                                                 return;\r
11085 \r
11086                                         dm = t;\r
11087 \r
11088                                         while (dm) {\r
11089                                                 if (dm.hideMenu)\r
11090                                                         dm.hideMenu();\r
11091 \r
11092                                                 dm = dm.settings.parent;\r
11093                                         }\r
11094 \r
11095                                         if (m.settings.onclick)\r
11096                                                 m.settings.onclick(e);\r
11097 \r
11098                                         return false; // Cancel to fix onbeforeunload problem\r
11099                                 }\r
11100                         });\r
11101 \r
11102                         if (t.hasMenus()) {\r
11103                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {\r
11104                                         var m, r, mi;\r
11105 \r
11106                                         e = e.target;\r
11107                                         if (e && (e = DOM.getParent(e, 'tr'))) {\r
11108                                                 m = t.items[e.id];\r
11109 \r
11110                                                 if (t.lastMenu)\r
11111                                                         t.lastMenu.collapse(1);\r
11112 \r
11113                                                 if (m.isDisabled())\r
11114                                                         return;\r
11115 \r
11116                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {\r
11117                                                         //p = DOM.getPos(s.container);\r
11118                                                         r = DOM.getRect(e);\r
11119                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);\r
11120                                                         t.lastMenu = m;\r
11121                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');\r
11122                                                 }\r
11123                                         }\r
11124                                 });\r
11125                         }\r
11126                         \r
11127                         Event.add(co, 'keydown', t._keyHandler, t);\r
11128 \r
11129                         t.onShowMenu.dispatch(t);\r
11130 \r
11131                         if (s.keyboard_focus) { \r
11132                                 t._setupKeyboardNav(); \r
11133                         }\r
11134                 },\r
11135 \r
11136                 hideMenu : function(c) {\r
11137                         var t = this, co = DOM.get('menu_' + t.id), e;\r
11138 \r
11139                         if (!t.isMenuVisible)\r
11140                                 return;\r
11141 \r
11142                         if (t.keyboardNav) t.keyboardNav.destroy();\r
11143                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
11144                         Event.remove(co, 'click', t.mouseClickFunc);\r
11145                         Event.remove(co, 'keydown', t._keyHandler);\r
11146                         DOM.hide(co);\r
11147                         t.isMenuVisible = 0;\r
11148 \r
11149                         if (!c)\r
11150                                 t.collapse(1);\r
11151 \r
11152                         if (t.element)\r
11153                                 t.element.hide();\r
11154 \r
11155                         if (e = DOM.get(t.id))\r
11156                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');\r
11157 \r
11158                         t.onHideMenu.dispatch(t);\r
11159                 },\r
11160 \r
11161                 add : function(o) {\r
11162                         var t = this, co;\r
11163 \r
11164                         o = t.parent(o);\r
11165 \r
11166                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))\r
11167                                 t._add(DOM.select('tbody', co)[0], o);\r
11168 \r
11169                         return o;\r
11170                 },\r
11171 \r
11172                 collapse : function(d) {\r
11173                         this.parent(d);\r
11174                         this.hideMenu(1);\r
11175                 },\r
11176 \r
11177                 remove : function(o) {\r
11178                         DOM.remove(o.id);\r
11179                         this.destroy();\r
11180 \r
11181                         return this.parent(o);\r
11182                 },\r
11183 \r
11184                 destroy : function() {\r
11185                         var t = this, co = DOM.get('menu_' + t.id);\r
11186 \r
11187                         if (t.keyboardNav) t.keyboardNav.destroy();\r
11188                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
11189                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);\r
11190                         Event.remove(co, 'click', t.mouseClickFunc);\r
11191                         Event.remove(co, 'keydown', t._keyHandler);\r
11192 \r
11193                         if (t.element)\r
11194                                 t.element.remove();\r
11195 \r
11196                         DOM.remove(co);\r
11197                 },\r
11198 \r
11199                 renderNode : function() {\r
11200                         var t = this, s = t.settings, n, tb, co, w;\r
11201 \r
11202                         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
11203                         if (t.settings.parent) {\r
11204                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);\r
11205                         }\r
11206                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});\r
11207                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
11208 \r
11209                         if (s.menu_line)\r
11210                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});\r
11211 \r
11212 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});\r
11213                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});\r
11214                         tb = DOM.add(n, 'tbody');\r
11215 \r
11216                         each(t.items, function(o) {\r
11217                                 t._add(tb, o);\r
11218                         });\r
11219 \r
11220                         t.rendered = true;\r
11221 \r
11222                         return w;\r
11223                 },\r
11224 \r
11225                 // Internal functions\r
11226                 _setupKeyboardNav : function(){\r
11227                         var contextMenu, menuItems, t=this; \r
11228                         contextMenu = DOM.get('menu_' + t.id);\r
11229                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);\r
11230                         menuItems.splice(0,0,contextMenu);\r
11231                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({\r
11232                                 root: 'menu_' + t.id,\r
11233                                 items: menuItems,\r
11234                                 onCancel: function() {\r
11235                                         t.hideMenu();\r
11236                                 },\r
11237                                 enableUpDown: true\r
11238                         });\r
11239                         contextMenu.focus();\r
11240                 },\r
11241 \r
11242                 _keyHandler : function(evt) {\r
11243                         var t = this, e;\r
11244                         switch (evt.keyCode) {\r
11245                                 case 37: // Left\r
11246                                         if (t.settings.parent) {\r
11247                                                 t.hideMenu();\r
11248                                                 t.settings.parent.focus();\r
11249                                                 Event.cancel(evt);\r
11250                                         }\r
11251                                         break;\r
11252                                 case 39: // Right\r
11253                                         if (t.mouseOverFunc)\r
11254                                                 t.mouseOverFunc(evt);\r
11255                                         break;\r
11256                         }\r
11257                 },\r
11258 \r
11259                 _add : function(tb, o) {\r
11260                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;\r
11261 \r
11262                         if (s.separator) {\r
11263                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});\r
11264                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});\r
11265 \r
11266                                 if (n = ro.previousSibling)\r
11267                                         DOM.addClass(n, 'mceLast');\r
11268 \r
11269                                 return;\r
11270                         }\r
11271 \r
11272                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});\r
11273                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');\r
11274                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});\r
11275 \r
11276                         if (s.parent) {\r
11277                                 DOM.setAttrib(a, 'aria-haspopup', 'true');\r
11278                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);\r
11279                         }\r
11280 \r
11281                         DOM.addClass(it, s['class']);\r
11282 //                      n = DOM.add(n, 'span', {'class' : 'item'});\r
11283 \r
11284                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});\r
11285 \r
11286                         if (s.icon_src)\r
11287                                 DOM.add(ic, 'img', {src : s.icon_src});\r
11288 \r
11289                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);\r
11290 \r
11291                         if (o.settings.style) {\r
11292                                 if (typeof o.settings.style == "function")\r
11293                                         o.settings.style = o.settings.style();\r
11294 \r
11295                                 DOM.setAttrib(n, 'style', o.settings.style);\r
11296                         }\r
11297 \r
11298                         if (tb.childNodes.length == 1)\r
11299                                 DOM.addClass(ro, 'mceFirst');\r
11300 \r
11301                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))\r
11302                                 DOM.addClass(ro, 'mceFirst');\r
11303 \r
11304                         if (o.collapse)\r
11305                                 DOM.addClass(ro, cp + 'ItemSub');\r
11306 \r
11307                         if (n = ro.previousSibling)\r
11308                                 DOM.removeClass(n, 'mceLast');\r
11309 \r
11310                         DOM.addClass(ro, 'mceLast');\r
11311                 }\r
11312         });\r
11313 })(tinymce);\r
11314 (function(tinymce) {\r
11315         var DOM = tinymce.DOM;\r
11316 \r
11317         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {\r
11318                 Button : function(id, s, ed) {\r
11319                         this.parent(id, s, ed);\r
11320                         this.classPrefix = 'mceButton';\r
11321                 },\r
11322 \r
11323                 renderHTML : function() {\r
11324                         var cp = this.classPrefix, s = this.settings, h, l;\r
11325 \r
11326                         l = DOM.encode(s.label || '');\r
11327                         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
11328                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )\r
11329                                 h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;\r
11330                         else\r
11331                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
11332 \r
11333                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; \r
11334                         h += '</a>';\r
11335                         return h;\r
11336                 },\r
11337 \r
11338                 postRender : function() {\r
11339                         var t = this, s = t.settings, imgBookmark;\r
11340 \r
11341                         // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so\r
11342                         // need to keep the selection in case the selection is lost\r
11343                         if (tinymce.isIE && t.editor) {\r
11344                                 tinymce.dom.Event.add(t.id, 'mousedown', function(e) {\r
11345                                         var nodeName = t.editor.selection.getNode().nodeName;\r
11346                                         imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;\r
11347                                 });\r
11348                         }\r
11349                         tinymce.dom.Event.add(t.id, 'click', function(e) {\r
11350                                 if (!t.isDisabled()) {\r
11351                                         // restore the selection in case the selection is lost in IE\r
11352                                         if (tinymce.isIE && t.editor && imgBookmark !== null) {\r
11353                                                 t.editor.selection.moveToBookmark(imgBookmark);\r
11354                                         }\r
11355                                         return s.onclick.call(s.scope, e);\r
11356                                 }\r
11357                         });\r
11358                         tinymce.dom.Event.add(t.id, 'keyup', function(e) {\r
11359                                 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)\r
11360                                         return s.onclick.call(s.scope, e);\r
11361                         });\r
11362                 }\r
11363         });\r
11364 })(tinymce);\r
11365 \r
11366 (function(tinymce) {\r
11367         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;\r
11368 \r
11369         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {\r
11370                 ListBox : function(id, s, ed) {\r
11371                         var t = this;\r
11372 \r
11373                         t.parent(id, s, ed);\r
11374 \r
11375                         t.items = [];\r
11376 \r
11377                         t.onChange = new Dispatcher(t);\r
11378 \r
11379                         t.onPostRender = new Dispatcher(t);\r
11380 \r
11381                         t.onAdd = new Dispatcher(t);\r
11382 \r
11383                         t.onRenderMenu = new tinymce.util.Dispatcher(this);\r
11384 \r
11385                         t.classPrefix = 'mceListBox';\r
11386                         t.marked = {};\r
11387                 },\r
11388 \r
11389                 select : function(va) {\r
11390                         var t = this, fv, f;\r
11391 \r
11392                         t.marked = {};\r
11393 \r
11394                         if (va == undef)\r
11395                                 return t.selectByIndex(-1);\r
11396 \r
11397                         // Is string or number make function selector\r
11398                         if (va && typeof(va)=="function")\r
11399                                 f = va;\r
11400                         else {\r
11401                                 f = function(v) {\r
11402                                         return v == va;\r
11403                                 };\r
11404                         }\r
11405 \r
11406                         // Do we need to do something?\r
11407                         if (va != t.selectedValue) {\r
11408                                 // Find item\r
11409                                 each(t.items, function(o, i) {\r
11410                                         if (f(o.value)) {\r
11411                                                 fv = 1;\r
11412                                                 t.selectByIndex(i);\r
11413                                                 return false;\r
11414                                         }\r
11415                                 });\r
11416 \r
11417                                 if (!fv)\r
11418                                         t.selectByIndex(-1);\r
11419                         }\r
11420                 },\r
11421 \r
11422                 selectByIndex : function(idx) {\r
11423                         var t = this, e, o, label;\r
11424 \r
11425                         t.marked = {};\r
11426 \r
11427                         if (idx != t.selectedIndex) {\r
11428                                 e = DOM.get(t.id + '_text');\r
11429                                 label = DOM.get(t.id + '_voiceDesc');\r
11430                                 o = t.items[idx];\r
11431 \r
11432                                 if (o) {\r
11433                                         t.selectedValue = o.value;\r
11434                                         t.selectedIndex = idx;\r
11435                                         DOM.setHTML(e, DOM.encode(o.title));\r
11436                                         DOM.setHTML(label, t.settings.title + " - " + o.title);\r
11437                                         DOM.removeClass(e, 'mceTitle');\r
11438                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);\r
11439                                 } else {\r
11440                                         DOM.setHTML(e, DOM.encode(t.settings.title));\r
11441                                         DOM.setHTML(label, DOM.encode(t.settings.title));\r
11442                                         DOM.addClass(e, 'mceTitle');\r
11443                                         t.selectedValue = t.selectedIndex = null;\r
11444                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);\r
11445                                 }\r
11446                                 e = 0;\r
11447                         }\r
11448                 },\r
11449 \r
11450                 mark : function(value) {\r
11451                         this.marked[value] = true;\r
11452                 },\r
11453 \r
11454                 add : function(n, v, o) {\r
11455                         var t = this;\r
11456 \r
11457                         o = o || {};\r
11458                         o = tinymce.extend(o, {\r
11459                                 title : n,\r
11460                                 value : v\r
11461                         });\r
11462 \r
11463                         t.items.push(o);\r
11464                         t.onAdd.dispatch(t, o);\r
11465                 },\r
11466 \r
11467                 getLength : function() {\r
11468                         return this.items.length;\r
11469                 },\r
11470 \r
11471                 renderHTML : function() {\r
11472                         var h = '', t = this, s = t.settings, cp = t.classPrefix;\r
11473 \r
11474                         h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';\r
11475                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); \r
11476                         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
11477                         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
11478                         h += '</tr></tbody></table></span>';\r
11479 \r
11480                         return h;\r
11481                 },\r
11482 \r
11483                 showMenu : function() {\r
11484                         var t = this, p2, e = DOM.get(this.id), m;\r
11485 \r
11486                         if (t.isDisabled() || t.items.length === 0)\r
11487                                 return;\r
11488 \r
11489                         if (t.menu && t.menu.isMenuVisible)\r
11490                                 return t.hideMenu();\r
11491 \r
11492                         if (!t.isMenuRendered) {\r
11493                                 t.renderMenu();\r
11494                                 t.isMenuRendered = true;\r
11495                         }\r
11496 \r
11497                         p2 = DOM.getPos(e);\r
11498 \r
11499                         m = t.menu;\r
11500                         m.settings.offset_x = p2.x;\r
11501                         m.settings.offset_y = p2.y;\r
11502                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus\r
11503 \r
11504                         // Select in menu\r
11505                         each(t.items, function(o) {\r
11506                                 if (m.items[o.id]) {\r
11507                                         m.items[o.id].setSelected(0);\r
11508                                 }\r
11509                         });\r
11510 \r
11511                         each(t.items, function(o) {\r
11512                                 if (m.items[o.id] && t.marked[o.value]) {\r
11513                                         m.items[o.id].setSelected(1);\r
11514                                 }\r
11515 \r
11516                                 if (o.value === t.selectedValue) {\r
11517                                         m.items[o.id].setSelected(1);\r
11518                                 }\r
11519                         });\r
11520 \r
11521                         m.showMenu(0, e.clientHeight);\r
11522 \r
11523                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
11524                         DOM.addClass(t.id, t.classPrefix + 'Selected');\r
11525 \r
11526                         //DOM.get(t.id + '_text').focus();\r
11527                 },\r
11528 \r
11529                 hideMenu : function(e) {\r
11530                         var t = this;\r
11531 \r
11532                         if (t.menu && t.menu.isMenuVisible) {\r
11533                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
11534 \r
11535                                 // Prevent double toogles by canceling the mouse click event to the button\r
11536                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))\r
11537                                         return;\r
11538 \r
11539                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
11540                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
11541                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
11542                                         t.menu.hideMenu();\r
11543                                 }\r
11544                         }\r
11545                 },\r
11546 \r
11547                 renderMenu : function() {\r
11548                         var t = this, m;\r
11549 \r
11550                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
11551                                 menu_line : 1,\r
11552                                 'class' : t.classPrefix + 'Menu mceNoIcons',\r
11553                                 max_width : 250,\r
11554                                 max_height : 150\r
11555                         });\r
11556 \r
11557                         m.onHideMenu.add(function() {\r
11558                                 t.hideMenu();\r
11559                                 t.focus();\r
11560                         });\r
11561 \r
11562                         m.add({\r
11563                                 title : t.settings.title,\r
11564                                 'class' : 'mceMenuItemTitle',\r
11565                                 onclick : function() {\r
11566                                         if (t.settings.onselect('') !== false)\r
11567                                                 t.select(''); // Must be runned after\r
11568                                 }\r
11569                         });\r
11570 \r
11571                         each(t.items, function(o) {\r
11572                                 // No value then treat it as a title\r
11573                                 if (o.value === undef) {\r
11574                                         m.add({\r
11575                                                 title : o.title,\r
11576                                                 role : "option",\r
11577                                                 'class' : 'mceMenuItemTitle',\r
11578                                                 onclick : function() {\r
11579                                                         if (t.settings.onselect('') !== false)\r
11580                                                                 t.select(''); // Must be runned after\r
11581                                                 }\r
11582                                         });\r
11583                                 } else {\r
11584                                         o.id = DOM.uniqueId();\r
11585                                         o.role= "option";\r
11586                                         o.onclick = function() {\r
11587                                                 if (t.settings.onselect(o.value) !== false)\r
11588                                                         t.select(o.value); // Must be runned after\r
11589                                         };\r
11590 \r
11591                                         m.add(o);\r
11592                                 }\r
11593                         });\r
11594 \r
11595                         t.onRenderMenu.dispatch(t, m);\r
11596                         t.menu = m;\r
11597                 },\r
11598 \r
11599                 postRender : function() {\r
11600                         var t = this, cp = t.classPrefix;\r
11601 \r
11602                         Event.add(t.id, 'click', t.showMenu, t);\r
11603                         Event.add(t.id, 'keydown', function(evt) {\r
11604                                 if (evt.keyCode == 32) { // Space\r
11605                                         t.showMenu(evt);\r
11606                                         Event.cancel(evt);\r
11607                                 }\r
11608                         });\r
11609                         Event.add(t.id, 'focus', function() {\r
11610                                 if (!t._focused) {\r
11611                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {\r
11612                                                 if (e.keyCode == 40) {\r
11613                                                         t.showMenu();\r
11614                                                         Event.cancel(e);\r
11615                                                 }\r
11616                                         });\r
11617                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {\r
11618                                                 var v;\r
11619                                                 if (e.keyCode == 13) {\r
11620                                                         // Fake select on enter\r
11621                                                         v = t.selectedValue;\r
11622                                                         t.selectedValue = null; // Needs to be null to fake change\r
11623                                                         Event.cancel(e);\r
11624                                                         t.settings.onselect(v);\r
11625                                                 }\r
11626                                         });\r
11627                                 }\r
11628 \r
11629                                 t._focused = 1;\r
11630                         });\r
11631                         Event.add(t.id, 'blur', function() {\r
11632                                 Event.remove(t.id, 'keydown', t.keyDownHandler);\r
11633                                 Event.remove(t.id, 'keypress', t.keyPressHandler);\r
11634                                 t._focused = 0;\r
11635                         });\r
11636 \r
11637                         // Old IE doesn't have hover on all elements\r
11638                         if (tinymce.isIE6 || !DOM.boxModel) {\r
11639                                 Event.add(t.id, 'mouseover', function() {\r
11640                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
11641                                                 DOM.addClass(t.id, cp + 'Hover');\r
11642                                 });\r
11643 \r
11644                                 Event.add(t.id, 'mouseout', function() {\r
11645                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
11646                                                 DOM.removeClass(t.id, cp + 'Hover');\r
11647                                 });\r
11648                         }\r
11649 \r
11650                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
11651                 },\r
11652 \r
11653                 destroy : function() {\r
11654                         this.parent();\r
11655 \r
11656                         Event.clear(this.id + '_text');\r
11657                         Event.clear(this.id + '_open');\r
11658                 }\r
11659         });\r
11660 })(tinymce);\r
11661 \r
11662 (function(tinymce) {\r
11663         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;\r
11664 \r
11665         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {\r
11666                 NativeListBox : function(id, s) {\r
11667                         this.parent(id, s);\r
11668                         this.classPrefix = 'mceNativeListBox';\r
11669                 },\r
11670 \r
11671                 setDisabled : function(s) {\r
11672                         DOM.get(this.id).disabled = s;\r
11673                         this.setAriaProperty('disabled', s);\r
11674                 },\r
11675 \r
11676                 isDisabled : function() {\r
11677                         return DOM.get(this.id).disabled;\r
11678                 },\r
11679 \r
11680                 select : function(va) {\r
11681                         var t = this, fv, f;\r
11682 \r
11683                         if (va == undef)\r
11684                                 return t.selectByIndex(-1);\r
11685 \r
11686                         // Is string or number make function selector\r
11687                         if (va && typeof(va)=="function")\r
11688                                 f = va;\r
11689                         else {\r
11690                                 f = function(v) {\r
11691                                         return v == va;\r
11692                                 };\r
11693                         }\r
11694 \r
11695                         // Do we need to do something?\r
11696                         if (va != t.selectedValue) {\r
11697                                 // Find item\r
11698                                 each(t.items, function(o, i) {\r
11699                                         if (f(o.value)) {\r
11700                                                 fv = 1;\r
11701                                                 t.selectByIndex(i);\r
11702                                                 return false;\r
11703                                         }\r
11704                                 });\r
11705 \r
11706                                 if (!fv)\r
11707                                         t.selectByIndex(-1);\r
11708                         }\r
11709                 },\r
11710 \r
11711                 selectByIndex : function(idx) {\r
11712                         DOM.get(this.id).selectedIndex = idx + 1;\r
11713                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;\r
11714                 },\r
11715 \r
11716                 add : function(n, v, a) {\r
11717                         var o, t = this;\r
11718 \r
11719                         a = a || {};\r
11720                         a.value = v;\r
11721 \r
11722                         if (t.isRendered())\r
11723                                 DOM.add(DOM.get(this.id), 'option', a, n);\r
11724 \r
11725                         o = {\r
11726                                 title : n,\r
11727                                 value : v,\r
11728                                 attribs : a\r
11729                         };\r
11730 \r
11731                         t.items.push(o);\r
11732                         t.onAdd.dispatch(t, o);\r
11733                 },\r
11734 \r
11735                 getLength : function() {\r
11736                         return this.items.length;\r
11737                 },\r
11738 \r
11739                 renderHTML : function() {\r
11740                         var h, t = this;\r
11741 \r
11742                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');\r
11743 \r
11744                         each(t.items, function(it) {\r
11745                                 h += DOM.createHTML('option', {value : it.value}, it.title);\r
11746                         });\r
11747 \r
11748                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);\r
11749                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);\r
11750                         return h;\r
11751                 },\r
11752 \r
11753                 postRender : function() {\r
11754                         var t = this, ch, changeListenerAdded = true;\r
11755 \r
11756                         t.rendered = true;\r
11757 \r
11758                         function onChange(e) {\r
11759                                 var v = t.items[e.target.selectedIndex - 1];\r
11760 \r
11761                                 if (v && (v = v.value)) {\r
11762                                         t.onChange.dispatch(t, v);\r
11763 \r
11764                                         if (t.settings.onselect)\r
11765                                                 t.settings.onselect(v);\r
11766                                 }\r
11767                         };\r
11768 \r
11769                         Event.add(t.id, 'change', onChange);\r
11770 \r
11771                         // Accessibility keyhandler\r
11772                         Event.add(t.id, 'keydown', function(e) {\r
11773                                 var bf;\r
11774 \r
11775                                 Event.remove(t.id, 'change', ch);\r
11776                                 changeListenerAdded = false;\r
11777 \r
11778                                 bf = Event.add(t.id, 'blur', function() {\r
11779                                         if (changeListenerAdded) return;\r
11780                                         changeListenerAdded = true;\r
11781                                         Event.add(t.id, 'change', onChange);\r
11782                                         Event.remove(t.id, 'blur', bf);\r
11783                                 });\r
11784 \r
11785                                 //prevent default left and right keys on chrome - so that the keyboard navigation is used.\r
11786                                 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {\r
11787                                         return Event.prevent(e);\r
11788                                 }\r
11789                                 \r
11790                                 if (e.keyCode == 13 || e.keyCode == 32) {\r
11791                                         onChange(e);\r
11792                                         return Event.cancel(e);\r
11793                                 }\r
11794                         });\r
11795 \r
11796                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
11797                 }\r
11798         });\r
11799 })(tinymce);\r
11800 \r
11801 (function(tinymce) {\r
11802         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
11803 \r
11804         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {\r
11805                 MenuButton : function(id, s, ed) {\r
11806                         this.parent(id, s, ed);\r
11807 \r
11808                         this.onRenderMenu = new tinymce.util.Dispatcher(this);\r
11809 \r
11810                         s.menu_container = s.menu_container || DOM.doc.body;\r
11811                 },\r
11812 \r
11813                 showMenu : function() {\r
11814                         var t = this, p1, p2, e = DOM.get(t.id), m;\r
11815 \r
11816                         if (t.isDisabled())\r
11817                                 return;\r
11818 \r
11819                         if (!t.isMenuRendered) {\r
11820                                 t.renderMenu();\r
11821                                 t.isMenuRendered = true;\r
11822                         }\r
11823 \r
11824                         if (t.isMenuVisible)\r
11825                                 return t.hideMenu();\r
11826 \r
11827                         p1 = DOM.getPos(t.settings.menu_container);\r
11828                         p2 = DOM.getPos(e);\r
11829 \r
11830                         m = t.menu;\r
11831                         m.settings.offset_x = p2.x;\r
11832                         m.settings.offset_y = p2.y;\r
11833                         m.settings.vp_offset_x = p2.x;\r
11834                         m.settings.vp_offset_y = p2.y;\r
11835                         m.settings.keyboard_focus = t._focused;\r
11836                         m.showMenu(0, e.firstChild.clientHeight);\r
11837 \r
11838                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
11839                         t.setState('Selected', 1);\r
11840 \r
11841                         t.isMenuVisible = 1;\r
11842                 },\r
11843 \r
11844                 renderMenu : function() {\r
11845                         var t = this, m;\r
11846 \r
11847                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
11848                                 menu_line : 1,\r
11849                                 'class' : this.classPrefix + 'Menu',\r
11850                                 icons : t.settings.icons\r
11851                         });\r
11852 \r
11853                         m.onHideMenu.add(function() {\r
11854                                 t.hideMenu();\r
11855                                 t.focus();\r
11856                         });\r
11857 \r
11858                         t.onRenderMenu.dispatch(t, m);\r
11859                         t.menu = m;\r
11860                 },\r
11861 \r
11862                 hideMenu : function(e) {\r
11863                         var t = this;\r
11864 \r
11865                         // Prevent double toogles by canceling the mouse click event to the button\r
11866                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))\r
11867                                 return;\r
11868 \r
11869                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
11870                                 t.setState('Selected', 0);\r
11871                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
11872                                 if (t.menu)\r
11873                                         t.menu.hideMenu();\r
11874                         }\r
11875 \r
11876                         t.isMenuVisible = 0;\r
11877                 },\r
11878 \r
11879                 postRender : function() {\r
11880                         var t = this, s = t.settings;\r
11881 \r
11882                         Event.add(t.id, 'click', function() {\r
11883                                 if (!t.isDisabled()) {\r
11884                                         if (s.onclick)\r
11885                                                 s.onclick(t.value);\r
11886 \r
11887                                         t.showMenu();\r
11888                                 }\r
11889                         });\r
11890                 }\r
11891         });\r
11892 })(tinymce);\r
11893 \r
11894 (function(tinymce) {\r
11895         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
11896 \r
11897         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {\r
11898                 SplitButton : function(id, s, ed) {\r
11899                         this.parent(id, s, ed);\r
11900                         this.classPrefix = 'mceSplitButton';\r
11901                 },\r
11902 \r
11903                 renderHTML : function() {\r
11904                         var h, t = this, s = t.settings, h1;\r
11905 \r
11906                         h = '<tbody><tr>';\r
11907 \r
11908                         if (s.image)\r
11909                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});\r
11910                         else\r
11911                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');\r
11912 \r
11913                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);\r
11914                         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
11915         \r
11916                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');\r
11917                         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
11918 \r
11919                         h += '</tr></tbody>';\r
11920                         h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);\r
11921                         return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);\r
11922                 },\r
11923 \r
11924                 postRender : function() {\r
11925                         var t = this, s = t.settings, activate;\r
11926 \r
11927                         if (s.onclick) {\r
11928                                 activate = function(evt) {\r
11929                                         if (!t.isDisabled()) {\r
11930                                                 s.onclick(t.value);\r
11931                                                 Event.cancel(evt);\r
11932                                         }\r
11933                                 };\r
11934                                 Event.add(t.id + '_action', 'click', activate);\r
11935                                 Event.add(t.id, ['click', 'keydown'], function(evt) {\r
11936                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;\r
11937                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {\r
11938                                                 activate();\r
11939                                                 Event.cancel(evt);\r
11940                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {\r
11941                                                 t.showMenu();\r
11942                                                 Event.cancel(evt);\r
11943                                         }\r
11944                                 });\r
11945                         }\r
11946 \r
11947                         Event.add(t.id + '_open', 'click', function (evt) {\r
11948                                 t.showMenu();\r
11949                                 Event.cancel(evt);\r
11950                         });\r
11951                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});\r
11952                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});\r
11953 \r
11954                         // Old IE doesn't have hover on all elements\r
11955                         if (tinymce.isIE6 || !DOM.boxModel) {\r
11956                                 Event.add(t.id, 'mouseover', function() {\r
11957                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
11958                                                 DOM.addClass(t.id, 'mceSplitButtonHover');\r
11959                                 });\r
11960 \r
11961                                 Event.add(t.id, 'mouseout', function() {\r
11962                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
11963                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');\r
11964                                 });\r
11965                         }\r
11966                 },\r
11967 \r
11968                 destroy : function() {\r
11969                         this.parent();\r
11970 \r
11971                         Event.clear(this.id + '_action');\r
11972                         Event.clear(this.id + '_open');\r
11973                         Event.clear(this.id);\r
11974                 }\r
11975         });\r
11976 })(tinymce);\r
11977 \r
11978 (function(tinymce) {\r
11979         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;\r
11980 \r
11981         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {\r
11982                 ColorSplitButton : function(id, s, ed) {\r
11983                         var t = this;\r
11984 \r
11985                         t.parent(id, s, ed);\r
11986 \r
11987                         t.settings = s = tinymce.extend({\r
11988                                 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
11989                                 grid_width : 8,\r
11990                                 default_color : '#888888'\r
11991                         }, t.settings);\r
11992 \r
11993                         t.onShowMenu = new tinymce.util.Dispatcher(t);\r
11994 \r
11995                         t.onHideMenu = new tinymce.util.Dispatcher(t);\r
11996 \r
11997                         t.value = s.default_color;\r
11998                 },\r
11999 \r
12000                 showMenu : function() {\r
12001                         var t = this, r, p, e, p2;\r
12002 \r
12003                         if (t.isDisabled())\r
12004                                 return;\r
12005 \r
12006                         if (!t.isMenuRendered) {\r
12007                                 t.renderMenu();\r
12008                                 t.isMenuRendered = true;\r
12009                         }\r
12010 \r
12011                         if (t.isMenuVisible)\r
12012                                 return t.hideMenu();\r
12013 \r
12014                         e = DOM.get(t.id);\r
12015                         DOM.show(t.id + '_menu');\r
12016                         DOM.addClass(e, 'mceSplitButtonSelected');\r
12017                         p2 = DOM.getPos(e);\r
12018                         DOM.setStyles(t.id + '_menu', {\r
12019                                 left : p2.x,\r
12020                                 top : p2.y + e.firstChild.clientHeight,\r
12021                                 zIndex : 200000\r
12022                         });\r
12023                         e = 0;\r
12024 \r
12025                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
12026                         t.onShowMenu.dispatch(t);\r
12027 \r
12028                         if (t._focused) {\r
12029                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {\r
12030                                         if (e.keyCode == 27)\r
12031                                                 t.hideMenu();\r
12032                                 });\r
12033 \r
12034                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link\r
12035                         }\r
12036 \r
12037                         t.isMenuVisible = 1;\r
12038                 },\r
12039 \r
12040                 hideMenu : function(e) {\r
12041                         var t = this;\r
12042 \r
12043                         if (t.isMenuVisible) {\r
12044                                 // Prevent double toogles by canceling the mouse click event to the button\r
12045                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))\r
12046                                         return;\r
12047 \r
12048                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {\r
12049                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');\r
12050                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
12051                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);\r
12052                                         DOM.hide(t.id + '_menu');\r
12053                                 }\r
12054 \r
12055                                 t.isMenuVisible = 0;\r
12056                                 t.onHideMenu.dispatch();\r
12057                         }\r
12058                 },\r
12059 \r
12060                 renderMenu : function() {\r
12061                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;\r
12062 \r
12063                         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
12064                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});\r
12065                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});\r
12066 \r
12067                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});\r
12068                         tb = DOM.add(n, 'tbody');\r
12069 \r
12070                         // Generate color grid\r
12071                         i = 0;\r
12072                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {\r
12073                                 c = c.replace(/^#/, '');\r
12074 \r
12075                                 if (!i--) {\r
12076                                         tr = DOM.add(tb, 'tr');\r
12077                                         i = s.grid_width - 1;\r
12078                                 }\r
12079 \r
12080                                 n = DOM.add(tr, 'td');\r
12081                                 var settings = {\r
12082                                         href : 'javascript:;',\r
12083                                         style : {\r
12084                                                 backgroundColor : '#' + c\r
12085                                         },\r
12086                                         'title': t.editor.getLang('colors.' + c, c),\r
12087                                         'data-mce-color' : '#' + c\r
12088                                 };\r
12089 \r
12090                                 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.\r
12091                                 if (!tinymce.isIE ) {\r
12092                                         settings.role = 'option';\r
12093                                 }\r
12094 \r
12095                                 n = DOM.add(n, 'a', settings);\r
12096 \r
12097                                 if (t.editor.forcedHighContrastMode) {\r
12098                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });\r
12099                                         if (n.getContext && (context = n.getContext("2d"))) {\r
12100                                                 context.fillStyle = '#' + c;\r
12101                                                 context.fillRect(0, 0, 16, 16);\r
12102                                         } else {\r
12103                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.\r
12104                                                 DOM.remove(n);\r
12105                                         }\r
12106                                 }\r
12107                         });\r
12108 \r
12109                         if (s.more_colors_func) {\r
12110                                 n = DOM.add(tb, 'tr');\r
12111                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});\r
12112                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);\r
12113 \r
12114                                 Event.add(n, 'click', function(e) {\r
12115                                         s.more_colors_func.call(s.more_colors_scope || this);\r
12116                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
12117                                 });\r
12118                         }\r
12119 \r
12120                         DOM.addClass(m, 'mceColorSplitMenu');\r
12121                         \r
12122                         new tinymce.ui.KeyboardNavigation({\r
12123                                 root: t.id + '_menu',\r
12124                                 items: DOM.select('a', t.id + '_menu'),\r
12125                                 onCancel: function() {\r
12126                                         t.hideMenu();\r
12127                                         t.focus();\r
12128                                 }\r
12129                         });\r
12130 \r
12131                         // Prevent IE from scrolling and hindering click to occur #4019\r
12132                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});\r
12133 \r
12134                         Event.add(t.id + '_menu', 'click', function(e) {\r
12135                                 var c;\r
12136 \r
12137                                 e = DOM.getParent(e.target, 'a', tb);\r
12138 \r
12139                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))\r
12140                                         t.setColor(c);\r
12141 \r
12142                                 return false; // Prevent IE auto save warning\r
12143                         });\r
12144 \r
12145                         return w;\r
12146                 },\r
12147 \r
12148                 setColor : function(c) {\r
12149                         this.displayColor(c);\r
12150                         this.hideMenu();\r
12151                         this.settings.onselect(c);\r
12152                 },\r
12153                 \r
12154                 displayColor : function(c) {\r
12155                         var t = this;\r
12156 \r
12157                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);\r
12158 \r
12159                         t.value = c;\r
12160                 },\r
12161 \r
12162                 postRender : function() {\r
12163                         var t = this, id = t.id;\r
12164 \r
12165                         t.parent();\r
12166                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});\r
12167                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);\r
12168                 },\r
12169 \r
12170                 destroy : function() {\r
12171                         this.parent();\r
12172 \r
12173                         Event.clear(this.id + '_menu');\r
12174                         Event.clear(this.id + '_more');\r
12175                         DOM.remove(this.id + '_menu');\r
12176                 }\r
12177         });\r
12178 })(tinymce);\r
12179 \r
12180 (function(tinymce) {\r
12181 // Shorten class names\r
12182 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;\r
12183 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {\r
12184         renderHTML : function() {\r
12185                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;\r
12186 \r
12187                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');\r
12188                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.\r
12189                 h.push("<span role='application'>");\r
12190                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');\r
12191                 each(controls, function(toolbar) {\r
12192                         h.push(toolbar.renderHTML());\r
12193                 });\r
12194                 h.push("</span>");\r
12195                 h.push('</div>');\r
12196 \r
12197                 return h.join('');\r
12198         },\r
12199         \r
12200         focus : function() {\r
12201                 var t = this;\r
12202                 dom.get(t.id).focus();\r
12203         },\r
12204         \r
12205         postRender : function() {\r
12206                 var t = this, items = [];\r
12207 \r
12208                 each(t.controls, function(toolbar) {\r
12209                         each (toolbar.controls, function(control) {\r
12210                                 if (control.id) {\r
12211                                         items.push(control);\r
12212                                 }\r
12213                         });\r
12214                 });\r
12215 \r
12216                 t.keyNav = new tinymce.ui.KeyboardNavigation({\r
12217                         root: t.id,\r
12218                         items: items,\r
12219                         onCancel: function() {\r
12220                                 //Move focus if webkit so that navigation back will read the item.\r
12221                                 if (tinymce.isWebKit) {\r
12222                                         dom.get(t.editor.id+"_ifr").focus();\r
12223                                 }\r
12224                                 t.editor.focus();\r
12225                         },\r
12226                         excludeFromTabOrder: !t.settings.tab_focus_toolbar\r
12227                 });\r
12228         },\r
12229         \r
12230         destroy : function() {\r
12231                 var self = this;\r
12232 \r
12233                 self.parent();\r
12234                 self.keyNav.destroy();\r
12235                 Event.clear(self.id);\r
12236         }\r
12237 });\r
12238 })(tinymce);\r
12239 \r
12240 (function(tinymce) {\r
12241 // Shorten class names\r
12242 var dom = tinymce.DOM, each = tinymce.each;\r
12243 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {\r
12244         renderHTML : function() {\r
12245                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;\r
12246 \r
12247                 cl = t.controls;\r
12248                 for (i=0; i<cl.length; i++) {\r
12249                         // Get current control, prev control, next control and if the control is a list box or not\r
12250                         co = cl[i];\r
12251                         pr = cl[i - 1];\r
12252                         nx = cl[i + 1];\r
12253 \r
12254                         // Add toolbar start\r
12255                         if (i === 0) {\r
12256                                 c = 'mceToolbarStart';\r
12257 \r
12258                                 if (co.Button)\r
12259                                         c += ' mceToolbarStartButton';\r
12260                                 else if (co.SplitButton)\r
12261                                         c += ' mceToolbarStartSplitButton';\r
12262                                 else if (co.ListBox)\r
12263                                         c += ' mceToolbarStartListBox';\r
12264 \r
12265                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
12266                         }\r
12267 \r
12268                         // Add toolbar end before list box and after the previous button\r
12269                         // This is to fix the o2k7 editor skins\r
12270                         if (pr && co.ListBox) {\r
12271                                 if (pr.Button || pr.SplitButton)\r
12272                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));\r
12273                         }\r
12274 \r
12275                         // Render control HTML\r
12276 \r
12277                         // IE 8 quick fix, needed to propertly generate a hit area for anchors\r
12278                         if (dom.stdMode)\r
12279                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';\r
12280                         else\r
12281                                 h += '<td>' + co.renderHTML() + '</td>';\r
12282 \r
12283                         // Add toolbar start after list box and before the next button\r
12284                         // This is to fix the o2k7 editor skins\r
12285                         if (nx && co.ListBox) {\r
12286                                 if (nx.Button || nx.SplitButton)\r
12287                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));\r
12288                         }\r
12289                 }\r
12290 \r
12291                 c = 'mceToolbarEnd';\r
12292 \r
12293                 if (co.Button)\r
12294                         c += ' mceToolbarEndButton';\r
12295                 else if (co.SplitButton)\r
12296                         c += ' mceToolbarEndSplitButton';\r
12297                 else if (co.ListBox)\r
12298                         c += ' mceToolbarEndListBox';\r
12299 \r
12300                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
12301 \r
12302                 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
12303         }\r
12304 });\r
12305 })(tinymce);\r
12306 \r
12307 (function(tinymce) {\r
12308         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;\r
12309 \r
12310         tinymce.create('tinymce.AddOnManager', {\r
12311                 AddOnManager : function() {\r
12312                         var self = this;\r
12313 \r
12314                         self.items = [];\r
12315                         self.urls = {};\r
12316                         self.lookup = {};\r
12317                         self.onAdd = new Dispatcher(self);\r
12318                 },\r
12319 \r
12320                 get : function(n) {\r
12321                         if (this.lookup[n]) {\r
12322                                 return this.lookup[n].instance;\r
12323                         } else {\r
12324                                 return undefined;\r
12325                         }\r
12326                 },\r
12327 \r
12328                 dependencies : function(n) {\r
12329                         var result;\r
12330                         if (this.lookup[n]) {\r
12331                                 result = this.lookup[n].dependencies;\r
12332                         }\r
12333                         return result || [];\r
12334                 },\r
12335 \r
12336                 requireLangPack : function(n) {\r
12337                         var s = tinymce.settings;\r
12338 \r
12339                         if (s && s.language && s.language_load !== false)\r
12340                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');\r
12341                 },\r
12342 \r
12343                 add : function(id, o, dependencies) {\r
12344                         this.items.push(o);\r
12345                         this.lookup[id] = {instance:o, dependencies:dependencies};\r
12346                         this.onAdd.dispatch(this, id, o);\r
12347 \r
12348                         return o;\r
12349                 },\r
12350                 createUrl: function(baseUrl, dep) {\r
12351                         if (typeof dep === "object") {\r
12352                                 return dep\r
12353                         } else {\r
12354                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};\r
12355                         }\r
12356                 },\r
12357 \r
12358                 addComponents: function(pluginName, scripts) {\r
12359                         var pluginUrl = this.urls[pluginName];\r
12360                         tinymce.each(scripts, function(script){\r
12361                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); \r
12362                         });\r
12363                 },\r
12364 \r
12365                 load : function(n, u, cb, s) {\r
12366                         var t = this, url = u;\r
12367 \r
12368                         function loadDependencies() {\r
12369                                 var dependencies = t.dependencies(n);\r
12370                                 tinymce.each(dependencies, function(dep) {\r
12371                                         var newUrl = t.createUrl(u, dep);\r
12372                                         t.load(newUrl.resource, newUrl, undefined, undefined);\r
12373                                 });\r
12374                                 if (cb) {\r
12375                                         if (s) {\r
12376                                                 cb.call(s);\r
12377                                         } else {\r
12378                                                 cb.call(tinymce.ScriptLoader);\r
12379                                         }\r
12380                                 }\r
12381                         }\r
12382 \r
12383                         if (t.urls[n])\r
12384                                 return;\r
12385                         if (typeof u === "object")\r
12386                                 url = u.prefix + u.resource + u.suffix;\r
12387 \r
12388                         if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)\r
12389                                 url = tinymce.baseURL + '/' + url;\r
12390 \r
12391                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));\r
12392 \r
12393                         if (t.lookup[n]) {\r
12394                                 loadDependencies();\r
12395                         } else {\r
12396                                 tinymce.ScriptLoader.add(url, loadDependencies, s);\r
12397                         }\r
12398                 }\r
12399         });\r
12400 \r
12401         // Create plugin and theme managers\r
12402         tinymce.PluginManager = new tinymce.AddOnManager();\r
12403         tinymce.ThemeManager = new tinymce.AddOnManager();\r
12404 }(tinymce));\r
12405 \r
12406 (function(tinymce) {\r
12407         // Shorten names\r
12408         var each = tinymce.each, extend = tinymce.extend,\r
12409                 DOM = tinymce.DOM, Event = tinymce.dom.Event,\r
12410                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
12411                 explode = tinymce.explode,\r
12412                 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;\r
12413 \r
12414         // Setup some URLs where the editor API is located and where the document is\r
12415         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');\r
12416         if (!/[\/\\]$/.test(tinymce.documentBaseURL))\r
12417                 tinymce.documentBaseURL += '/';\r
12418 \r
12419         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);\r
12420 \r
12421         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);\r
12422 \r
12423         // Add before unload listener\r
12424         // This was required since IE was leaking memory if you added and removed beforeunload listeners\r
12425         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event\r
12426         tinymce.onBeforeUnload = new Dispatcher(tinymce);\r
12427 \r
12428         // Must be on window or IE will leak if the editor is placed in frame or iframe\r
12429         Event.add(window, 'beforeunload', function(e) {\r
12430                 tinymce.onBeforeUnload.dispatch(tinymce, e);\r
12431         });\r
12432 \r
12433         tinymce.onAddEditor = new Dispatcher(tinymce);\r
12434 \r
12435         tinymce.onRemoveEditor = new Dispatcher(tinymce);\r
12436 \r
12437         tinymce.EditorManager = extend(tinymce, {\r
12438                 editors : [],\r
12439 \r
12440                 i18n : {},\r
12441 \r
12442                 activeEditor : null,\r
12443 \r
12444                 init : function(s) {\r
12445                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;\r
12446 \r
12447                         function createId(elm) {\r
12448                                 var id = elm.id;\r
12449         \r
12450                                 // Use element id, or unique name or generate a unique id\r
12451                                 if (!id) {\r
12452                                         id = elm.name;\r
12453         \r
12454                                         if (id && !DOM.get(id)) {\r
12455                                                 id = elm.name;\r
12456                                         } else {\r
12457                                                 // Generate unique name\r
12458                                                 id = DOM.uniqueId();\r
12459                                         }\r
12460 \r
12461                                         elm.setAttribute('id', id);\r
12462                                 }\r
12463 \r
12464                                 return id;\r
12465                         };\r
12466 \r
12467                         function execCallback(se, n, s) {\r
12468                                 var f = se[n];\r
12469 \r
12470                                 if (!f)\r
12471                                         return;\r
12472 \r
12473                                 if (tinymce.is(f, 'string')) {\r
12474                                         s = f.replace(/\.\w+$/, '');\r
12475                                         s = s ? tinymce.resolve(s) : 0;\r
12476                                         f = tinymce.resolve(f);\r
12477                                 }\r
12478 \r
12479                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));\r
12480                         };\r
12481 \r
12482                         function hasClass(n, c) {\r
12483                                 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);\r
12484                         };\r
12485 \r
12486                         s = extend({\r
12487                                 theme : "simple",\r
12488                                 language : "en"\r
12489                         }, s);\r
12490 \r
12491                         t.settings = s;\r
12492 \r
12493                         // Legacy call\r
12494                         Event.bind(window, 'ready', function() {\r
12495                                 var l, co;\r
12496 \r
12497                                 execCallback(s, 'onpageload');\r
12498 \r
12499                                 switch (s.mode) {\r
12500                                         case "exact":\r
12501                                                 l = s.elements || '';\r
12502 \r
12503                                                 if(l.length > 0) {\r
12504                                                         each(explode(l), function(v) {\r
12505                                                                 if (DOM.get(v)) {\r
12506                                                                         ed = new tinymce.Editor(v, s);\r
12507                                                                         el.push(ed);\r
12508                                                                         ed.render(1);\r
12509                                                                 } else {\r
12510                                                                         each(document.forms, function(f) {\r
12511                                                                                 each(f.elements, function(e) {\r
12512                                                                                         if (e.name === v) {\r
12513                                                                                                 v = 'mce_editor_' + instanceCounter++;\r
12514                                                                                                 DOM.setAttrib(e, 'id', v);\r
12515 \r
12516                                                                                                 ed = new tinymce.Editor(v, s);\r
12517                                                                                                 el.push(ed);\r
12518                                                                                                 ed.render(1);\r
12519                                                                                         }\r
12520                                                                                 });\r
12521                                                                         });\r
12522                                                                 }\r
12523                                                         });\r
12524                                                 }\r
12525                                                 break;\r
12526 \r
12527                                         case "textareas":\r
12528                                         case "specific_textareas":\r
12529                                                 each(DOM.select('textarea'), function(elm) {\r
12530                                                         if (s.editor_deselector && hasClass(elm, s.editor_deselector))\r
12531                                                                 return;\r
12532 \r
12533                                                         if (!s.editor_selector || hasClass(elm, s.editor_selector)) {\r
12534                                                                 ed = new tinymce.Editor(createId(elm), s);\r
12535                                                                 el.push(ed);\r
12536                                                                 ed.render(1);\r
12537                                                         }\r
12538                                                 });\r
12539                                                 break;\r
12540                                         \r
12541                                         default:\r
12542                                                 if (s.types) {\r
12543                                                         // Process type specific selector\r
12544                                                         each(s.types, function(type) {\r
12545                                                                 each(DOM.select(type.selector), function(elm) {\r
12546                                                                         var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));\r
12547                                                                         el.push(editor);\r
12548                                                                         editor.render(1);\r
12549                                                                 });\r
12550                                                         });\r
12551                                                 } else if (s.selector) {\r
12552                                                         // Process global selector\r
12553                                                         each(DOM.select(s.selector), function(elm) {\r
12554                                                                 var editor = new tinymce.Editor(createId(elm), s);\r
12555                                                                 el.push(editor);\r
12556                                                                 editor.render(1);\r
12557                                                         });\r
12558                                                 }\r
12559                                 }\r
12560 \r
12561                                 // Call onInit when all editors are initialized\r
12562                                 if (s.oninit) {\r
12563                                         l = co = 0;\r
12564 \r
12565                                         each(el, function(ed) {\r
12566                                                 co++;\r
12567 \r
12568                                                 if (!ed.initialized) {\r
12569                                                         // Wait for it\r
12570                                                         ed.onInit.add(function() {\r
12571                                                                 l++;\r
12572 \r
12573                                                                 // All done\r
12574                                                                 if (l == co)\r
12575                                                                         execCallback(s, 'oninit');\r
12576                                                         });\r
12577                                                 } else\r
12578                                                         l++;\r
12579 \r
12580                                                 // All done\r
12581                                                 if (l == co)\r
12582                                                         execCallback(s, 'oninit');                                      \r
12583                                         });\r
12584                                 }\r
12585                         });\r
12586                 },\r
12587 \r
12588                 get : function(id) {\r
12589                         if (id === undef)\r
12590                                 return this.editors;\r
12591 \r
12592                         return this.editors[id];\r
12593                 },\r
12594 \r
12595                 getInstanceById : function(id) {\r
12596                         return this.get(id);\r
12597                 },\r
12598 \r
12599                 add : function(editor) {\r
12600                         var self = this, editors = self.editors;\r
12601 \r
12602                         // Add named and index editor instance\r
12603                         editors[editor.id] = editor;\r
12604                         editors.push(editor);\r
12605 \r
12606                         self._setActive(editor);\r
12607                         self.onAddEditor.dispatch(self, editor);\r
12608 \r
12609 \r
12610                         return editor;\r
12611                 },\r
12612 \r
12613                 remove : function(editor) {\r
12614                         var t = this, i, editors = t.editors;\r
12615 \r
12616                         // Not in the collection\r
12617                         if (!editors[editor.id])\r
12618                                 return null;\r
12619 \r
12620                         delete editors[editor.id];\r
12621 \r
12622                         for (i = 0; i < editors.length; i++) {\r
12623                                 if (editors[i] == editor) {\r
12624                                         editors.splice(i, 1);\r
12625                                         break;\r
12626                                 }\r
12627                         }\r
12628 \r
12629                         // Select another editor since the active one was removed\r
12630                         if (t.activeEditor == editor)\r
12631                                 t._setActive(editors[0]);\r
12632 \r
12633                         editor.destroy();\r
12634                         t.onRemoveEditor.dispatch(t, editor);\r
12635 \r
12636                         return editor;\r
12637                 },\r
12638 \r
12639                 execCommand : function(c, u, v) {\r
12640                         var t = this, ed = t.get(v), w;\r
12641 \r
12642                         function clr() {\r
12643                                 ed.destroy();\r
12644                                 w.detachEvent('onunload', clr);\r
12645                                 w = w.tinyMCE = w.tinymce = null; // IE leak\r
12646                         };\r
12647 \r
12648                         // Manager commands\r
12649                         switch (c) {\r
12650                                 case "mceFocus":\r
12651                                         ed.focus();\r
12652                                         return true;\r
12653 \r
12654                                 case "mceAddEditor":\r
12655                                 case "mceAddControl":\r
12656                                         if (!t.get(v))\r
12657                                                 new tinymce.Editor(v, t.settings).render();\r
12658 \r
12659                                         return true;\r
12660 \r
12661                                 case "mceAddFrameControl":\r
12662                                         w = v.window;\r
12663 \r
12664                                         // Add tinyMCE global instance and tinymce namespace to specified window\r
12665                                         w.tinyMCE = tinyMCE;\r
12666                                         w.tinymce = tinymce;\r
12667 \r
12668                                         tinymce.DOM.doc = w.document;\r
12669                                         tinymce.DOM.win = w;\r
12670 \r
12671                                         ed = new tinymce.Editor(v.element_id, v);\r
12672                                         ed.render();\r
12673 \r
12674                                         // Fix IE memory leaks\r
12675                                         if (tinymce.isIE) {\r
12676                                                 w.attachEvent('onunload', clr);\r
12677                                         }\r
12678 \r
12679                                         v.page_window = null;\r
12680 \r
12681                                         return true;\r
12682 \r
12683                                 case "mceRemoveEditor":\r
12684                                 case "mceRemoveControl":\r
12685                                         if (ed)\r
12686                                                 ed.remove();\r
12687 \r
12688                                         return true;\r
12689 \r
12690                                 case 'mceToggleEditor':\r
12691                                         if (!ed) {\r
12692                                                 t.execCommand('mceAddControl', 0, v);\r
12693                                                 return true;\r
12694                                         }\r
12695 \r
12696                                         if (ed.isHidden())\r
12697                                                 ed.show();\r
12698                                         else\r
12699                                                 ed.hide();\r
12700 \r
12701                                         return true;\r
12702                         }\r
12703 \r
12704                         // Run command on active editor\r
12705                         if (t.activeEditor)\r
12706                                 return t.activeEditor.execCommand(c, u, v);\r
12707 \r
12708                         return false;\r
12709                 },\r
12710 \r
12711                 execInstanceCommand : function(id, c, u, v) {\r
12712                         var ed = this.get(id);\r
12713 \r
12714                         if (ed)\r
12715                                 return ed.execCommand(c, u, v);\r
12716 \r
12717                         return false;\r
12718                 },\r
12719 \r
12720                 triggerSave : function() {\r
12721                         each(this.editors, function(e) {\r
12722                                 e.save();\r
12723                         });\r
12724                 },\r
12725 \r
12726                 addI18n : function(p, o) {\r
12727                         var lo, i18n = this.i18n;\r
12728 \r
12729                         if (!tinymce.is(p, 'string')) {\r
12730                                 each(p, function(o, lc) {\r
12731                                         each(o, function(o, g) {\r
12732                                                 each(o, function(o, k) {\r
12733                                                         if (g === 'common')\r
12734                                                                 i18n[lc + '.' + k] = o;\r
12735                                                         else\r
12736                                                                 i18n[lc + '.' + g + '.' + k] = o;\r
12737                                                 });\r
12738                                         });\r
12739                                 });\r
12740                         } else {\r
12741                                 each(o, function(o, k) {\r
12742                                         i18n[p + '.' + k] = o;\r
12743                                 });\r
12744                         }\r
12745                 },\r
12746 \r
12747                 // Private methods\r
12748 \r
12749                 _setActive : function(editor) {\r
12750                         this.selectedInstance = this.activeEditor = editor;\r
12751                 }\r
12752         });\r
12753 })(tinymce);\r
12754 \r
12755 (function(tinymce) {\r
12756         // Shorten these names\r
12757         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,\r
12758                 each = tinymce.each, isGecko = tinymce.isGecko,\r
12759                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,\r
12760                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
12761                 explode = tinymce.explode;\r
12762 \r
12763         tinymce.create('tinymce.Editor', {\r
12764                 Editor : function(id, settings) {\r
12765                         var self = this, TRUE = true;\r
12766 \r
12767                         self.settings = settings = extend({\r
12768                                 id : id,\r
12769                                 language : 'en',\r
12770                                 theme : 'simple',\r
12771                                 skin : 'default',\r
12772                                 delta_width : 0,\r
12773                                 delta_height : 0,\r
12774                                 popup_css : '',\r
12775                                 plugins : '',\r
12776                                 document_base_url : tinymce.documentBaseURL,\r
12777                                 add_form_submit_trigger : TRUE,\r
12778                                 submit_patch : TRUE,\r
12779                                 add_unload_trigger : TRUE,\r
12780                                 convert_urls : TRUE,\r
12781                                 relative_urls : TRUE,\r
12782                                 remove_script_host : TRUE,\r
12783                                 table_inline_editing : false,\r
12784                                 object_resizing : TRUE,\r
12785                                 accessibility_focus : TRUE,\r
12786                                 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
12787                                 visual : TRUE,\r
12788                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',\r
12789                                 font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size\r
12790                                 apply_source_formatting : TRUE,\r
12791                                 directionality : 'ltr',\r
12792                                 forced_root_block : 'p',\r
12793                                 hidden_input : TRUE,\r
12794                                 padd_empty_editor : TRUE,\r
12795                                 render_ui : TRUE,\r
12796                                 indentation : '30px',\r
12797                                 fix_table_elements : TRUE,\r
12798                                 inline_styles : TRUE,\r
12799                                 convert_fonts_to_spans : TRUE,\r
12800                                 indent : 'simple',\r
12801                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',\r
12802                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure',\r
12803                                 validate : TRUE,\r
12804                                 entity_encoding : 'named',\r
12805                                 url_converter : self.convertURL,\r
12806                                 url_converter_scope : self,\r
12807                                 ie7_compat : TRUE\r
12808                         }, settings);\r
12809 \r
12810                         self.id = self.editorId = id;\r
12811 \r
12812                         self.isNotDirty = false;\r
12813 \r
12814                         self.plugins = {};\r
12815 \r
12816                         self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {\r
12817                                 base_uri : tinyMCE.baseURI\r
12818                         });\r
12819 \r
12820                         self.baseURI = tinymce.baseURI;\r
12821 \r
12822                         self.contentCSS = [];\r
12823 \r
12824                         // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic\r
12825                         self.setupEvents();\r
12826 \r
12827                         // Internal command handler objects\r
12828                         self.execCommands = {};\r
12829                         self.queryStateCommands = {};\r
12830                         self.queryValueCommands = {};\r
12831 \r
12832                         // Call setup\r
12833                         self.execCallback('setup', self);\r
12834                 },\r
12835 \r
12836                 render : function(nst) {\r
12837                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;\r
12838 \r
12839                         // Page is not loaded yet, wait for it\r
12840                         if (!Event.domLoaded) {\r
12841                                 Event.add(window, 'ready', function() {\r
12842                                         t.render();\r
12843                                 });\r
12844                                 return;\r
12845                         }\r
12846 \r
12847                         tinyMCE.settings = s;\r
12848 \r
12849                         // Element not found, then skip initialization\r
12850                         if (!t.getElement())\r
12851                                 return;\r
12852 \r
12853                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff \r
12854                         // here since the browser says it has contentEditable support but there is no visible caret.\r
12855                         if (tinymce.isIDevice && !tinymce.isIOS5)\r
12856                                 return;\r
12857 \r
12858                         // Add hidden input for non input elements inside form elements\r
12859                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))\r
12860                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);\r
12861 \r
12862                         if (tinymce.WindowManager)\r
12863                                 t.windowManager = new tinymce.WindowManager(t);\r
12864 \r
12865                         if (s.encoding == 'xml') {\r
12866                                 t.onGetContent.add(function(ed, o) {\r
12867                                         if (o.save)\r
12868                                                 o.content = DOM.encode(o.content);\r
12869                                 });\r
12870                         }\r
12871 \r
12872                         if (s.add_form_submit_trigger) {\r
12873                                 t.onSubmit.addToTop(function() {\r
12874                                         if (t.initialized) {\r
12875                                                 t.save();\r
12876                                                 t.isNotDirty = 1;\r
12877                                         }\r
12878                                 });\r
12879                         }\r
12880 \r
12881                         if (s.add_unload_trigger) {\r
12882                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {\r
12883                                         if (t.initialized && !t.destroyed && !t.isHidden())\r
12884                                                 t.save({format : 'raw', no_events : true});\r
12885                                 });\r
12886                         }\r
12887 \r
12888                         tinymce.addUnload(t.destroy, t);\r
12889 \r
12890                         if (s.submit_patch) {\r
12891                                 t.onBeforeRenderUI.add(function() {\r
12892                                         var n = t.getElement().form;\r
12893 \r
12894                                         if (!n)\r
12895                                                 return;\r
12896 \r
12897                                         // Already patched\r
12898                                         if (n._mceOldSubmit)\r
12899                                                 return;\r
12900 \r
12901                                         // Check page uses id="submit" or name="submit" for it's submit button\r
12902                                         if (!n.submit.nodeType && !n.submit.length) {\r
12903                                                 t.formElement = n;\r
12904                                                 n._mceOldSubmit = n.submit;\r
12905                                                 n.submit = function() {\r
12906                                                         // Save all instances\r
12907                                                         tinymce.triggerSave();\r
12908                                                         t.isNotDirty = 1;\r
12909 \r
12910                                                         return t.formElement._mceOldSubmit(t.formElement);\r
12911                                                 };\r
12912                                         }\r
12913 \r
12914                                         n = null;\r
12915                                 });\r
12916                         }\r
12917 \r
12918                         // Load scripts\r
12919                         function loadScripts() {\r
12920                                 if (s.language && s.language_load !== false)\r
12921                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');\r
12922 \r
12923                                 if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])\r
12924                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');\r
12925 \r
12926                                 each(explode(s.plugins), function(p) {\r
12927                                         if (p &&!PluginManager.urls[p]) {\r
12928                                                 if (p.charAt(0) == '-') {\r
12929                                                         p = p.substr(1, p.length);\r
12930                                                         var dependencies = PluginManager.dependencies(p);\r
12931                                                         each(dependencies, function(dep) {\r
12932                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};\r
12933                                                                 dep = PluginManager.createUrl(defaultSettings, dep);\r
12934                                                                 PluginManager.load(dep.resource, dep);\r
12935                                                         });\r
12936                                                 } else {\r
12937                                                         // Skip safari plugin, since it is removed as of 3.3b1\r
12938                                                         if (p == 'safari') {\r
12939                                                                 return;\r
12940                                                         }\r
12941                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});\r
12942                                                 }\r
12943                                         }\r
12944                                 });\r
12945 \r
12946                                 // Init when que is loaded\r
12947                                 sl.loadQueue(function() {\r
12948                                         if (!t.removed)\r
12949                                                 t.init();\r
12950                                 });\r
12951                         };\r
12952 \r
12953                         loadScripts();\r
12954                 },\r
12955 \r
12956                 init : function() {\r
12957                         var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];\r
12958 \r
12959                         tinymce.add(t);\r
12960 \r
12961                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));\r
12962 \r
12963                         if (s.theme) {\r
12964                                 s.theme = s.theme.replace(/-/, '');\r
12965                                 o = ThemeManager.get(s.theme);\r
12966                                 t.theme = new o();\r
12967 \r
12968                                 if (t.theme.init)\r
12969                                         t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));\r
12970                         }\r
12971                         function initPlugin(p) {\r
12972                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;\r
12973                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {\r
12974                                         each(PluginManager.dependencies(p), function(dep){\r
12975                                                 initPlugin(dep);\r
12976                                         });\r
12977                                         po = new c(t, u);\r
12978 \r
12979                                         t.plugins[p] = po;\r
12980 \r
12981                                         if (po.init) {\r
12982                                                 po.init(t, u);\r
12983                                                 initializedPlugins.push(p);\r
12984                                         }\r
12985                                 }\r
12986                         }\r
12987                         \r
12988                         // Create all plugins\r
12989                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);\r
12990 \r
12991                         // Setup popup CSS path(s)\r
12992                         if (s.popup_css !== false) {\r
12993                                 if (s.popup_css)\r
12994                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);\r
12995                                 else\r
12996                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");\r
12997                         }\r
12998 \r
12999                         if (s.popup_css_add)\r
13000                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);\r
13001 \r
13002                         t.controlManager = new tinymce.ControlManager(t);\r
13003 \r
13004                         t.onExecCommand.add(function(ed, c) {\r
13005                                 // Don't refresh the select lists until caret move\r
13006                                 if (!/^(FontName|FontSize)$/.test(c))\r
13007                                         t.nodeChanged();\r
13008                         });\r
13009 \r
13010                         // Enables users to override the control factory\r
13011                         t.onBeforeRenderUI.dispatch(t, t.controlManager);\r
13012 \r
13013                         // Measure box\r
13014                         if (s.render_ui && t.theme) {\r
13015                                 w = s.width || e.style.width || e.offsetWidth;\r
13016                                 h = s.height || e.style.height || e.offsetHeight;\r
13017                                 t.orgDisplay = e.style.display;\r
13018                                 re = /^[0-9\.]+(|px)$/i;\r
13019 \r
13020                                 if (re.test('' + w))\r
13021                                         w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);\r
13022 \r
13023                                 if (re.test('' + h))\r
13024                                         h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), 100);\r
13025 \r
13026                                 // Render UI\r
13027                                 o = t.theme.renderUI({\r
13028                                         targetNode : e,\r
13029                                         width : w,\r
13030                                         height : h,\r
13031                                         deltaWidth : s.delta_width,\r
13032                                         deltaHeight : s.delta_height\r
13033                                 });\r
13034 \r
13035                                 t.editorContainer = o.editorContainer;\r
13036                         }\r
13037 \r
13038                         // Load specified content CSS last\r
13039                         if (s.content_css) {\r
13040                                 each(explode(s.content_css), function(u) {\r
13041                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));\r
13042                                 });\r
13043                         }\r
13044 \r
13045                         // Content editable mode ends here\r
13046                         if (s.content_editable) {\r
13047                                 e = n = o = null; // Fix IE leak\r
13048                                 return t.initContentBody();\r
13049                         }\r
13050 \r
13051                         // User specified a document.domain value\r
13052                         if (document.domain && location.hostname != document.domain)\r
13053                                 tinymce.relaxedDomain = document.domain;\r
13054 \r
13055                         // Resize editor\r
13056                         DOM.setStyles(o.sizeContainer || o.editorContainer, {\r
13057                                 width : w,\r
13058                                 height : h\r
13059                         });\r
13060 \r
13061                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');\r
13062                         if (h < 100)\r
13063                                 h = 100;\r
13064 \r
13065                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';\r
13066 \r
13067                         // We only need to override paths if we have to\r
13068                         // IE has a bug where it remove site absolute urls to relative ones if this is specified\r
13069                         if (s.document_base_url != tinymce.documentBaseURL)\r
13070                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';\r
13071 \r
13072                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.\r
13073                         if (s.ie7_compat)\r
13074                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';\r
13075                         else\r
13076                                 t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';\r
13077 \r
13078                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';\r
13079 \r
13080                         // Load the CSS by injecting them into the HTML this will reduce "flicker"\r
13081                         for (i = 0; i < t.contentCSS.length; i++) {\r
13082                                 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';\r
13083                         }\r
13084 \r
13085                         t.contentCSS = [];\r
13086 \r
13087                         bi = s.body_id || 'tinymce';\r
13088                         if (bi.indexOf('=') != -1) {\r
13089                                 bi = t.getParam('body_id', '', 'hash');\r
13090                                 bi = bi[t.id] || bi;\r
13091                         }\r
13092 \r
13093                         bc = s.body_class || '';\r
13094                         if (bc.indexOf('=') != -1) {\r
13095                                 bc = t.getParam('body_class', '', 'hash');\r
13096                                 bc = bc[t.id] || '';\r
13097                         }\r
13098 \r
13099                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';\r
13100 \r
13101                         // Domain relaxing enabled, then set document domain\r
13102                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {\r
13103                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button\r
13104                                 u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';\r
13105                         }\r
13106 \r
13107                         // Create iframe\r
13108                         // TODO: ACC add the appropriate description on this.\r
13109                         n = DOM.add(o.iframeContainer, 'iframe', { \r
13110                                 id : t.id + "_ifr",\r
13111                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7\r
13112                                 frameBorder : '0',\r
13113                                 allowTransparency : "true",\r
13114                                 title : s.aria_label,\r
13115                                 style : {\r
13116                                         width : '100%',\r
13117                                         height : h,\r
13118                                         display : 'block' // Important for Gecko to render the iframe correctly\r
13119                                 }\r
13120                         });\r
13121 \r
13122                         t.contentAreaContainer = o.iframeContainer;\r
13123                         DOM.get(o.editorContainer).style.display = t.orgDisplay;\r
13124                         DOM.get(t.id).style.display = 'none';\r
13125                         DOM.setAttrib(t.id, 'aria-hidden', true);\r
13126 \r
13127                         if (!tinymce.relaxedDomain || !u)\r
13128                                 t.initContentBody();\r
13129 \r
13130                         e = n = o = null; // Cleanup\r
13131                 },\r
13132 \r
13133                 initContentBody : function() {\r
13134                         var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body;\r
13135 \r
13136                         // Setup iframe body\r
13137                         if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {\r
13138                                 doc.open();\r
13139                                 doc.write(self.iframeHTML);\r
13140                                 doc.close();\r
13141 \r
13142                                 if (tinymce.relaxedDomain)\r
13143                                         doc.domain = tinymce.relaxedDomain;\r
13144                         }\r
13145 \r
13146                         if (settings.content_editable) {\r
13147                                 DOM.addClass(targetElm, 'mceContentBody');\r
13148                                 self.contentDocument = doc = settings.content_document || document;\r
13149                                 self.contentWindow = settings.content_window || window;\r
13150                                 self.bodyElement = targetElm;\r
13151 \r
13152                                 // Prevent leak in IE\r
13153                                 settings.content_document = settings.content_window = null;\r
13154                         }\r
13155 \r
13156                         // It will not steal focus while setting contentEditable\r
13157                         body = self.getBody();\r
13158                         body.disabled = true;\r
13159 \r
13160                         if (!settings.readonly)\r
13161                                 body.contentEditable = self.getParam('content_editable_state', true);\r
13162 \r
13163                         body.disabled = false;\r
13164 \r
13165                         self.schema = new tinymce.html.Schema(settings);\r
13166 \r
13167                         self.dom = new tinymce.dom.DOMUtils(doc, {\r
13168                                 keep_values : true,\r
13169                                 url_converter : self.convertURL,\r
13170                                 url_converter_scope : self,\r
13171                                 hex_colors : settings.force_hex_style_colors,\r
13172                                 class_filter : settings.class_filter,\r
13173                                 update_styles : true,\r
13174                                 root_element : settings.content_editable ? self.id : null,\r
13175                                 schema : self.schema\r
13176                         });\r
13177 \r
13178                         self.parser = new tinymce.html.DomParser(settings, self.schema);\r
13179 \r
13180                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style\r
13181                         self.parser.addAttributeFilter('src,href,style', function(nodes, name) {\r
13182                                 var i = nodes.length, node, dom = self.dom, value, internalName;\r
13183 \r
13184                                 while (i--) {\r
13185                                         node = nodes[i];\r
13186                                         value = node.attr(name);\r
13187                                         internalName = 'data-mce-' + name;\r
13188 \r
13189                                         // Add internal attribute if we need to we don't on a refresh of the document\r
13190                                         if (!node.attributes.map[internalName]) {       \r
13191                                                 if (name === "style")\r
13192                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));\r
13193                                                 else\r
13194                                                         node.attr(internalName, self.convertURL(value, name, node.name));\r
13195                                         }\r
13196                                 }\r
13197                         });\r
13198 \r
13199                         // Keep scripts from executing\r
13200                         self.parser.addNodeFilter('script', function(nodes, name) {\r
13201                                 var i = nodes.length, node;\r
13202 \r
13203                                 while (i--) {\r
13204                                         node = nodes[i];\r
13205                                         node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));\r
13206                                 }\r
13207                         });\r
13208 \r
13209                         self.parser.addNodeFilter('#cdata', function(nodes, name) {\r
13210                                 var i = nodes.length, node;\r
13211 \r
13212                                 while (i--) {\r
13213                                         node = nodes[i];\r
13214                                         node.type = 8;\r
13215                                         node.name = '#comment';\r
13216                                         node.value = '[CDATA[' + node.value + ']]';\r
13217                                 }\r
13218                         });\r
13219 \r
13220                         self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {\r
13221                                 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();\r
13222 \r
13223                                 while (i--) {\r
13224                                         node = nodes[i];\r
13225 \r
13226                                         if (node.isEmpty(nonEmptyElements))\r
13227                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;\r
13228                                 }\r
13229                         });\r
13230 \r
13231                         self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);\r
13232 \r
13233                         self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer);\r
13234 \r
13235                         self.formatter = new tinymce.Formatter(self);\r
13236 \r
13237                         self.undoManager = new tinymce.UndoManager(self);\r
13238 \r
13239                         self.forceBlocks = new tinymce.ForceBlocks(self);\r
13240                         self.enterKey = new tinymce.EnterKey(self);\r
13241                         self.editorCommands = new tinymce.EditorCommands(self);\r
13242 \r
13243                         // Pass through\r
13244                         self.serializer.onPreProcess.add(function(se, o) {\r
13245                                 return self.onPreProcess.dispatch(self, o, se);\r
13246                         });\r
13247 \r
13248                         self.serializer.onPostProcess.add(function(se, o) {\r
13249                                 return self.onPostProcess.dispatch(self, o, se);\r
13250                         });\r
13251 \r
13252                         self.onPreInit.dispatch(self);\r
13253 \r
13254                         if (!settings.gecko_spellcheck)\r
13255                                 doc.body.spellcheck = false;\r
13256 \r
13257                         if (!settings.readonly) {\r
13258                                 self.bindNativeEvents();\r
13259                         }\r
13260 \r
13261                         self.controlManager.onPostRender.dispatch(self, self.controlManager);\r
13262                         self.onPostRender.dispatch(self);\r
13263 \r
13264                         self.quirks = tinymce.util.Quirks(self);\r
13265 \r
13266                         if (settings.directionality)\r
13267                                 body.dir = settings.directionality;\r
13268 \r
13269                         if (settings.nowrap)\r
13270                                 body.style.whiteSpace = "nowrap";\r
13271 \r
13272                         if (settings.protect) {\r
13273                                 self.onBeforeSetContent.add(function(ed, o) {\r
13274                                         each(settings.protect, function(pattern) {\r
13275                                                 o.content = o.content.replace(pattern, function(str) {\r
13276                                                         return '<!--mce:protected ' + escape(str) + '-->';\r
13277                                                 });\r
13278                                         });\r
13279                                 });\r
13280                         }\r
13281 \r
13282                         // Add visual aids when new contents is added\r
13283                         self.onSetContent.add(function() {\r
13284                                 self.addVisual(self.getBody());\r
13285                         });\r
13286 \r
13287                         // Remove empty contents\r
13288                         if (settings.padd_empty_editor) {\r
13289                                 self.onPostProcess.add(function(ed, o) {\r
13290                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');\r
13291                                 });\r
13292                         }\r
13293 \r
13294                         self.load({initial : true, format : 'html'});\r
13295                         self.startContent = self.getContent({format : 'raw'});\r
13296 \r
13297                         self.initialized = true;\r
13298 \r
13299                         self.onInit.dispatch(self);\r
13300                         self.execCallback('setupcontent_callback', self.id, body, doc);\r
13301                         self.execCallback('init_instance_callback', self);\r
13302                         self.focus(true);\r
13303                         self.nodeChanged({initial : true});\r
13304 \r
13305                         // Load specified content CSS last\r
13306                         each(self.contentCSS, function(url) {\r
13307                                 self.dom.loadCSS(url);\r
13308                         });\r
13309 \r
13310                         // Handle auto focus\r
13311                         if (settings.auto_focus) {\r
13312                                 setTimeout(function () {\r
13313                                         var ed = tinymce.get(settings.auto_focus);\r
13314 \r
13315                                         ed.selection.select(ed.getBody(), 1);\r
13316                                         ed.selection.collapse(1);\r
13317                                         ed.getBody().focus();\r
13318                                         ed.getWin().focus();\r
13319                                 }, 100);\r
13320                         }\r
13321 \r
13322                         // Clean up references for IE\r
13323                         targetElm = doc = body = null;\r
13324                 },\r
13325 \r
13326                 focus : function(skip_focus) {\r
13327                         var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;\r
13328 \r
13329                         if (!skip_focus) {\r
13330                                 // Get selected control element\r
13331                                 ieRng = selection.getRng();\r
13332                                 if (ieRng.item) {\r
13333                                         controlElm = ieRng.item(0);\r
13334                                 }\r
13335 \r
13336                                 self._refreshContentEditable();\r
13337 \r
13338                                 // Focus the window iframe\r
13339                                 if (!contentEditable) {\r
13340                                         self.getWin().focus();\r
13341                                 }\r
13342 \r
13343                                 // Focus the body as well since it's contentEditable\r
13344                                 if (tinymce.isGecko || contentEditable) {\r
13345                                         body = self.getBody();\r
13346 \r
13347                                         // Check for setActive since it doesn't scroll to the element\r
13348                                         if (body.setActive) {\r
13349                                                 body.setActive();\r
13350                                         } else {\r
13351                                                 body.focus();\r
13352                                         }\r
13353 \r
13354                                         if (contentEditable) {\r
13355                                                 selection.normalize();\r
13356                                         }\r
13357                                 }\r
13358 \r
13359                                 // Restore selected control element\r
13360                                 // This is needed when for example an image is selected within a\r
13361                                 // layer a call to focus will then remove the control selection\r
13362                                 if (controlElm && controlElm.ownerDocument == doc) {\r
13363                                         ieRng = doc.body.createControlRange();\r
13364                                         ieRng.addElement(controlElm);\r
13365                                         ieRng.select();\r
13366                                 }\r
13367                         }\r
13368 \r
13369                         if (tinymce.activeEditor != self) {\r
13370                                 if ((oed = tinymce.activeEditor) != null)\r
13371                                         oed.onDeactivate.dispatch(oed, self);\r
13372 \r
13373                                 self.onActivate.dispatch(self, oed);\r
13374                         }\r
13375 \r
13376                         tinymce._setActive(self);\r
13377                 },\r
13378 \r
13379                 execCallback : function(n) {\r
13380                         var t = this, f = t.settings[n], s;\r
13381 \r
13382                         if (!f)\r
13383                                 return;\r
13384 \r
13385                         // Look through lookup\r
13386                         if (t.callbackLookup && (s = t.callbackLookup[n])) {\r
13387                                 f = s.func;\r
13388                                 s = s.scope;\r
13389                         }\r
13390 \r
13391                         if (is(f, 'string')) {\r
13392                                 s = f.replace(/\.\w+$/, '');\r
13393                                 s = s ? tinymce.resolve(s) : 0;\r
13394                                 f = tinymce.resolve(f);\r
13395                                 t.callbackLookup = t.callbackLookup || {};\r
13396                                 t.callbackLookup[n] = {func : f, scope : s};\r
13397                         }\r
13398 \r
13399                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));\r
13400                 },\r
13401 \r
13402                 translate : function(s) {\r
13403                         var c = this.settings.language || 'en', i18n = tinymce.i18n;\r
13404 \r
13405                         if (!s)\r
13406                                 return '';\r
13407 \r
13408                         return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {\r
13409                                 return i18n[c + '.' + b] || '{#' + b + '}';\r
13410                         });\r
13411                 },\r
13412 \r
13413                 getLang : function(n, dv) {\r
13414                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');\r
13415                 },\r
13416 \r
13417                 getParam : function(n, dv, ty) {\r
13418                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;\r
13419 \r
13420                         if (ty === 'hash') {\r
13421                                 o = {};\r
13422 \r
13423                                 if (is(v, 'string')) {\r
13424                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {\r
13425                                                 v = v.split('=');\r
13426 \r
13427                                                 if (v.length > 1)\r
13428                                                         o[tr(v[0])] = tr(v[1]);\r
13429                                                 else\r
13430                                                         o[tr(v[0])] = tr(v);\r
13431                                         });\r
13432                                 } else\r
13433                                         o = v;\r
13434 \r
13435                                 return o;\r
13436                         }\r
13437 \r
13438                         return v;\r
13439                 },\r
13440 \r
13441                 nodeChanged : function(o) {\r
13442                         var self = this, selection = self.selection, node;\r
13443 \r
13444                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading\r
13445                         if (self.initialized) {\r
13446                                 o = o || {};\r
13447 \r
13448                                 // Get start node\r
13449                                 node = selection.getStart() || self.getBody();\r
13450                                 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state\r
13451 \r
13452                                 // Get parents and add them to object\r
13453                                 o.parents = [];\r
13454                                 self.dom.getParent(node, function(node) {\r
13455                                         if (node.nodeName == 'BODY')\r
13456                                                 return true;\r
13457 \r
13458                                         o.parents.push(node);\r
13459                                 });\r
13460 \r
13461                                 self.onNodeChange.dispatch(\r
13462                                         self,\r
13463                                         o ? o.controlManager || self.controlManager : self.controlManager,\r
13464                                         node,\r
13465                                         selection.isCollapsed(),\r
13466                                         o\r
13467                                 );\r
13468                         }\r
13469                 },\r
13470 \r
13471                 addButton : function(name, settings) {\r
13472                         var self = this;\r
13473 \r
13474                         self.buttons = self.buttons || {};\r
13475                         self.buttons[name] = settings;\r
13476                 },\r
13477 \r
13478                 addCommand : function(name, callback, scope) {\r
13479                         this.execCommands[name] = {func : callback, scope : scope || this};\r
13480                 },\r
13481 \r
13482                 addQueryStateHandler : function(name, callback, scope) {\r
13483                         this.queryStateCommands[name] = {func : callback, scope : scope || this};\r
13484                 },\r
13485 \r
13486                 addQueryValueHandler : function(name, callback, scope) {\r
13487                         this.queryValueCommands[name] = {func : callback, scope : scope || this};\r
13488                 },\r
13489 \r
13490                 addShortcut : function(pa, desc, cmd_func, sc) {\r
13491                         var t = this, c;\r
13492 \r
13493                         if (t.settings.custom_shortcuts === false)\r
13494                                 return false;\r
13495 \r
13496                         t.shortcuts = t.shortcuts || {};\r
13497 \r
13498                         if (is(cmd_func, 'string')) {\r
13499                                 c = cmd_func;\r
13500 \r
13501                                 cmd_func = function() {\r
13502                                         t.execCommand(c, false, null);\r
13503                                 };\r
13504                         }\r
13505 \r
13506                         if (is(cmd_func, 'object')) {\r
13507                                 c = cmd_func;\r
13508 \r
13509                                 cmd_func = function() {\r
13510                                         t.execCommand(c[0], c[1], c[2]);\r
13511                                 };\r
13512                         }\r
13513 \r
13514                         each(explode(pa), function(pa) {\r
13515                                 var o = {\r
13516                                         func : cmd_func,\r
13517                                         scope : sc || this,\r
13518                                         desc : t.translate(desc),\r
13519                                         alt : false,\r
13520                                         ctrl : false,\r
13521                                         shift : false\r
13522                                 };\r
13523 \r
13524                                 each(explode(pa, '+'), function(v) {\r
13525                                         switch (v) {\r
13526                                                 case 'alt':\r
13527                                                 case 'ctrl':\r
13528                                                 case 'shift':\r
13529                                                         o[v] = true;\r
13530                                                         break;\r
13531 \r
13532                                                 default:\r
13533                                                         o.charCode = v.charCodeAt(0);\r
13534                                                         o.keyCode = v.toUpperCase().charCodeAt(0);\r
13535                                         }\r
13536                                 });\r
13537 \r
13538                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;\r
13539                         });\r
13540 \r
13541                         return true;\r
13542                 },\r
13543 \r
13544                 execCommand : function(cmd, ui, val, a) {\r
13545                         var t = this, s = 0, o, st;\r
13546 \r
13547                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))\r
13548                                 t.focus();\r
13549 \r
13550                         a = extend({}, a);\r
13551                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);\r
13552                         if (a.terminate)\r
13553                                 return false;\r
13554 \r
13555                         // Command callback\r
13556                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {\r
13557                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13558                                 return true;\r
13559                         }\r
13560 \r
13561                         // Registred commands\r
13562                         if (o = t.execCommands[cmd]) {\r
13563                                 st = o.func.call(o.scope, ui, val);\r
13564 \r
13565                                 // Fall through on true\r
13566                                 if (st !== true) {\r
13567                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13568                                         return st;\r
13569                                 }\r
13570                         }\r
13571 \r
13572                         // Plugin commands\r
13573                         each(t.plugins, function(p) {\r
13574                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {\r
13575                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13576                                         s = 1;\r
13577                                         return false;\r
13578                                 }\r
13579                         });\r
13580 \r
13581                         if (s)\r
13582                                 return true;\r
13583 \r
13584                         // Theme commands\r
13585                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {\r
13586                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13587                                 return true;\r
13588                         }\r
13589 \r
13590                         // Editor commands\r
13591                         if (t.editorCommands.execCommand(cmd, ui, val)) {\r
13592                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13593                                 return true;\r
13594                         }\r
13595 \r
13596                         // Browser commands\r
13597                         t.getDoc().execCommand(cmd, ui, val);\r
13598                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
13599                 },\r
13600 \r
13601                 queryCommandState : function(cmd) {\r
13602                         var t = this, o, s;\r
13603 \r
13604                         // Is hidden then return undefined\r
13605                         if (t._isHidden())\r
13606                                 return;\r
13607 \r
13608                         // Registred commands\r
13609                         if (o = t.queryStateCommands[cmd]) {\r
13610                                 s = o.func.call(o.scope);\r
13611 \r
13612                                 // Fall though on true\r
13613                                 if (s !== true)\r
13614                                         return s;\r
13615                         }\r
13616 \r
13617                         // Registred commands\r
13618                         o = t.editorCommands.queryCommandState(cmd);\r
13619                         if (o !== -1)\r
13620                                 return o;\r
13621 \r
13622                         // Browser commands\r
13623                         try {\r
13624                                 return this.getDoc().queryCommandState(cmd);\r
13625                         } catch (ex) {\r
13626                                 // Fails sometimes see bug: 1896577\r
13627                         }\r
13628                 },\r
13629 \r
13630                 queryCommandValue : function(c) {\r
13631                         var t = this, o, s;\r
13632 \r
13633                         // Is hidden then return undefined\r
13634                         if (t._isHidden())\r
13635                                 return;\r
13636 \r
13637                         // Registred commands\r
13638                         if (o = t.queryValueCommands[c]) {\r
13639                                 s = o.func.call(o.scope);\r
13640 \r
13641                                 // Fall though on true\r
13642                                 if (s !== true)\r
13643                                         return s;\r
13644                         }\r
13645 \r
13646                         // Registred commands\r
13647                         o = t.editorCommands.queryCommandValue(c);\r
13648                         if (is(o))\r
13649                                 return o;\r
13650 \r
13651                         // Browser commands\r
13652                         try {\r
13653                                 return this.getDoc().queryCommandValue(c);\r
13654                         } catch (ex) {\r
13655                                 // Fails sometimes see bug: 1896577\r
13656                         }\r
13657                 },\r
13658 \r
13659                 show : function() {\r
13660                         var self = this;\r
13661 \r
13662                         DOM.show(self.getContainer());\r
13663                         DOM.hide(self.id);\r
13664                         self.load();\r
13665                 },\r
13666 \r
13667                 hide : function() {\r
13668                         var self = this, doc = self.getDoc();\r
13669 \r
13670                         // Fixed bug where IE has a blinking cursor left from the editor\r
13671                         if (isIE && doc)\r
13672                                 doc.execCommand('SelectAll');\r
13673 \r
13674                         // We must save before we hide so Safari doesn't crash\r
13675                         self.save();\r
13676                         DOM.hide(self.getContainer());\r
13677                         DOM.setStyle(self.id, 'display', self.orgDisplay);\r
13678                 },\r
13679 \r
13680                 isHidden : function() {\r
13681                         return !DOM.isHidden(this.id);\r
13682                 },\r
13683 \r
13684                 setProgressState : function(b, ti, o) {\r
13685                         this.onSetProgressState.dispatch(this, b, ti, o);\r
13686 \r
13687                         return b;\r
13688                 },\r
13689 \r
13690                 load : function(o) {\r
13691                         var t = this, e = t.getElement(), h;\r
13692 \r
13693                         if (e) {\r
13694                                 o = o || {};\r
13695                                 o.load = true;\r
13696 \r
13697                                 // Double encode existing entities in the value\r
13698                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);\r
13699                                 o.element = e;\r
13700 \r
13701                                 if (!o.no_events)\r
13702                                         t.onLoadContent.dispatch(t, o);\r
13703 \r
13704                                 o.element = e = null;\r
13705 \r
13706                                 return h;\r
13707                         }\r
13708                 },\r
13709 \r
13710                 save : function(o) {\r
13711                         var t = this, e = t.getElement(), h, f;\r
13712 \r
13713                         if (!e || !t.initialized)\r
13714                                 return;\r
13715 \r
13716                         o = o || {};\r
13717                         o.save = true;\r
13718 \r
13719                         o.element = e;\r
13720                         h = o.content = t.getContent(o);\r
13721 \r
13722                         if (!o.no_events)\r
13723                                 t.onSaveContent.dispatch(t, o);\r
13724 \r
13725                         h = o.content;\r
13726 \r
13727                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {\r
13728                                 e.innerHTML = h;\r
13729 \r
13730                                 // Update hidden form element\r
13731                                 if (f = DOM.getParent(t.id, 'form')) {\r
13732                                         each(f.elements, function(e) {\r
13733                                                 if (e.name == t.id) {\r
13734                                                         e.value = h;\r
13735                                                         return false;\r
13736                                                 }\r
13737                                         });\r
13738                                 }\r
13739                         } else\r
13740                                 e.value = h;\r
13741 \r
13742                         o.element = e = null;\r
13743 \r
13744                         return h;\r
13745                 },\r
13746 \r
13747                 setContent : function(content, args) {\r
13748                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;\r
13749 \r
13750                         // Setup args object\r
13751                         args = args || {};\r
13752                         args.format = args.format || 'html';\r
13753                         args.set = true;\r
13754                         args.content = content;\r
13755 \r
13756                         // Do preprocessing\r
13757                         if (!args.no_events)\r
13758                                 self.onBeforeSetContent.dispatch(self, args);\r
13759 \r
13760                         content = args.content;\r
13761 \r
13762                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content\r
13763                         // It will also be impossible to place the caret in the editor unless there is a BR element present\r
13764                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {\r
13765                                 forcedRootBlockName = self.settings.forced_root_block;\r
13766                                 if (forcedRootBlockName)\r
13767                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';\r
13768                                 else\r
13769                                         content = '<br data-mce-bogus="1">';\r
13770 \r
13771                                 body.innerHTML = content;\r
13772                                 self.selection.select(body, true);\r
13773                                 self.selection.collapse(true);\r
13774                                 return;\r
13775                         }\r
13776 \r
13777                         // Parse and serialize the html\r
13778                         if (args.format !== 'raw') {\r
13779                                 content = new tinymce.html.Serializer({}, self.schema).serialize(\r
13780                                         self.parser.parse(content)\r
13781                                 );\r
13782                         }\r
13783 \r
13784                         // Set the new cleaned contents to the editor\r
13785                         args.content = tinymce.trim(content);\r
13786                         self.dom.setHTML(body, args.content);\r
13787 \r
13788                         // Do post processing\r
13789                         if (!args.no_events)\r
13790                                 self.onSetContent.dispatch(self, args);\r
13791 \r
13792                         self.selection.normalize();\r
13793 \r
13794                         return args.content;\r
13795                 },\r
13796 \r
13797                 getContent : function(args) {\r
13798                         var self = this, content;\r
13799 \r
13800                         // Setup args object\r
13801                         args = args || {};\r
13802                         args.format = args.format || 'html';\r
13803                         args.get = true;\r
13804                         args.getInner = true;\r
13805 \r
13806                         // Do preprocessing\r
13807                         if (!args.no_events)\r
13808                                 self.onBeforeGetContent.dispatch(self, args);\r
13809 \r
13810                         // Get raw contents or by default the cleaned contents\r
13811                         if (args.format == 'raw')\r
13812                                 content = self.getBody().innerHTML;\r
13813                         else\r
13814                                 content = self.serializer.serialize(self.getBody(), args);\r
13815 \r
13816                         args.content = tinymce.trim(content);\r
13817 \r
13818                         // Do post processing\r
13819                         if (!args.no_events)\r
13820                                 self.onGetContent.dispatch(self, args);\r
13821 \r
13822                         return args.content;\r
13823                 },\r
13824 \r
13825                 isDirty : function() {\r
13826                         var self = this;\r
13827 \r
13828                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;\r
13829                 },\r
13830 \r
13831                 getContainer : function() {\r
13832                         var self = this;\r
13833 \r
13834                         if (!self.container)\r
13835                                 self.container = DOM.get(self.editorContainer || self.id + '_parent');\r
13836 \r
13837                         return self.container;\r
13838                 },\r
13839 \r
13840                 getContentAreaContainer : function() {\r
13841                         return this.contentAreaContainer;\r
13842                 },\r
13843 \r
13844                 getElement : function() {\r
13845                         return DOM.get(this.settings.content_element || this.id);\r
13846                 },\r
13847 \r
13848                 getWin : function() {\r
13849                         var self = this, elm;\r
13850 \r
13851                         if (!self.contentWindow) {\r
13852                                 elm = DOM.get(self.id + "_ifr");\r
13853 \r
13854                                 if (elm)\r
13855                                         self.contentWindow = elm.contentWindow;\r
13856                         }\r
13857 \r
13858                         return self.contentWindow;\r
13859                 },\r
13860 \r
13861                 getDoc : function() {\r
13862                         var self = this, win;\r
13863 \r
13864                         if (!self.contentDocument) {\r
13865                                 win = self.getWin();\r
13866 \r
13867                                 if (win)\r
13868                                         self.contentDocument = win.document;\r
13869                         }\r
13870 \r
13871                         return self.contentDocument;\r
13872                 },\r
13873 \r
13874                 getBody : function() {\r
13875                         return this.bodyElement || this.getDoc().body;\r
13876                 },\r
13877 \r
13878                 convertURL : function(url, name, elm) {\r
13879                         var self = this, settings = self.settings;\r
13880 \r
13881                         // Use callback instead\r
13882                         if (settings.urlconverter_callback)\r
13883                                 return self.execCallback('urlconverter_callback', url, elm, true, name);\r
13884 \r
13885                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs\r
13886                         if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)\r
13887                                 return url;\r
13888 \r
13889                         // Convert to relative\r
13890                         if (settings.relative_urls)\r
13891                                 return self.documentBaseURI.toRelative(url);\r
13892 \r
13893                         // Convert to absolute\r
13894                         url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);\r
13895 \r
13896                         return url;\r
13897                 },\r
13898 \r
13899                 addVisual : function(elm) {\r
13900                         var self = this, settings = self.settings, dom = self.dom, cls;\r
13901 \r
13902                         elm = elm || self.getBody();\r
13903 \r
13904                         if (!is(self.hasVisual))\r
13905                                 self.hasVisual = settings.visual;\r
13906 \r
13907                         each(dom.select('table,a', elm), function(elm) {\r
13908                                 var value;\r
13909 \r
13910                                 switch (elm.nodeName) {\r
13911                                         case 'TABLE':\r
13912                                                 cls = settings.visual_table_class || 'mceItemTable';\r
13913                                                 value = dom.getAttrib(elm, 'border');\r
13914 \r
13915                                                 if (!value || value == '0') {\r
13916                                                         if (self.hasVisual)\r
13917                                                                 dom.addClass(elm, cls);\r
13918                                                         else\r
13919                                                                 dom.removeClass(elm, cls);\r
13920                                                 }\r
13921 \r
13922                                                 return;\r
13923 \r
13924                                         case 'A':\r
13925                                                 value = dom.getAttrib(elm, 'name');\r
13926                                                 cls = 'mceItemAnchor';\r
13927 \r
13928                                                 if (value) {\r
13929                                                         if (self.hasVisual)\r
13930                                                                 dom.addClass(elm, cls);\r
13931                                                         else\r
13932                                                                 dom.removeClass(elm, cls);\r
13933                                                 }\r
13934 \r
13935                                                 return;\r
13936                                 }\r
13937                         });\r
13938 \r
13939                         self.onVisualAid.dispatch(self, elm, self.hasVisual);\r
13940                 },\r
13941 \r
13942                 remove : function() {\r
13943                         var self = this, elm = self.getContainer();\r
13944 \r
13945                         if (!self.removed) {\r
13946                                 self.removed = 1; // Cancels post remove event execution\r
13947                                 self.hide();\r
13948 \r
13949                                 // Don't clear the window or document if content editable\r
13950                                 // is enabled since other instances might still be present\r
13951                                 if (!self.settings.content_editable) {\r
13952                                         Event.clear(self.getWin());\r
13953                                         Event.clear(self.getDoc());\r
13954                                 }\r
13955 \r
13956                                 Event.clear(self.getBody());\r
13957                                 Event.clear(self.formElement);\r
13958                                 Event.unbind(elm);\r
13959 \r
13960                                 self.execCallback('remove_instance_callback', self);\r
13961                                 self.onRemove.dispatch(self);\r
13962 \r
13963                                 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command\r
13964                                 self.onExecCommand.listeners = [];\r
13965 \r
13966                                 tinymce.remove(self);\r
13967                                 DOM.remove(elm);\r
13968                         }\r
13969                 },\r
13970 \r
13971                 destroy : function(s) {\r
13972                         var t = this;\r
13973 \r
13974                         // One time is enough\r
13975                         if (t.destroyed)\r
13976                                 return;\r
13977 \r
13978                         // We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message\r
13979                         if (isGecko) {\r
13980                                 Event.unbind(t.getDoc());\r
13981                                 Event.unbind(t.getWin());\r
13982                                 Event.unbind(t.getBody());\r
13983                         }\r
13984 \r
13985                         if (!s) {\r
13986                                 tinymce.removeUnload(t.destroy);\r
13987                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);\r
13988 \r
13989                                 // Manual destroy\r
13990                                 if (t.theme && t.theme.destroy)\r
13991                                         t.theme.destroy();\r
13992 \r
13993                                 // Destroy controls, selection and dom\r
13994                                 t.controlManager.destroy();\r
13995                                 t.selection.destroy();\r
13996                                 t.dom.destroy();\r
13997                         }\r
13998 \r
13999                         if (t.formElement) {\r
14000                                 t.formElement.submit = t.formElement._mceOldSubmit;\r
14001                                 t.formElement._mceOldSubmit = null;\r
14002                         }\r
14003 \r
14004                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;\r
14005 \r
14006                         if (t.selection)\r
14007                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;\r
14008 \r
14009                         t.destroyed = 1;\r
14010                 },\r
14011 \r
14012                 // Internal functions\r
14013 \r
14014                 _refreshContentEditable : function() {\r
14015                         var self = this, body, parent;\r
14016 \r
14017                         // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again\r
14018                         if (self._isHidden()) {\r
14019                                 body = self.getBody();\r
14020                                 parent = body.parentNode;\r
14021 \r
14022                                 parent.removeChild(body);\r
14023                                 parent.appendChild(body);\r
14024 \r
14025                                 body.focus();\r
14026                         }\r
14027                 },\r
14028 \r
14029                 _isHidden : function() {\r
14030                         var s;\r
14031 \r
14032                         if (!isGecko)\r
14033                                 return 0;\r
14034 \r
14035                         // Weird, wheres that cursor selection?\r
14036                         s = this.selection.getSel();\r
14037                         return (!s || !s.rangeCount || s.rangeCount === 0);\r
14038                 }\r
14039         });\r
14040 })(tinymce);\r
14041 (function(tinymce) {\r
14042         var each = tinymce.each;\r
14043 \r
14044         tinymce.Editor.prototype.setupEvents = function() {\r
14045                 var self = this, settings = self.settings;\r
14046 \r
14047                 // Add events to the editor\r
14048                 each([\r
14049                         'onPreInit',\r
14050 \r
14051                         'onBeforeRenderUI',\r
14052 \r
14053                         'onPostRender',\r
14054 \r
14055                         'onLoad',\r
14056 \r
14057                         'onInit',\r
14058 \r
14059                         'onRemove',\r
14060 \r
14061                         'onActivate',\r
14062 \r
14063                         'onDeactivate',\r
14064 \r
14065                         'onClick',\r
14066 \r
14067                         'onEvent',\r
14068 \r
14069                         'onMouseUp',\r
14070 \r
14071                         'onMouseDown',\r
14072 \r
14073                         'onDblClick',\r
14074 \r
14075                         'onKeyDown',\r
14076 \r
14077                         'onKeyUp',\r
14078 \r
14079                         'onKeyPress',\r
14080 \r
14081                         'onContextMenu',\r
14082 \r
14083                         'onSubmit',\r
14084 \r
14085                         'onReset',\r
14086 \r
14087                         'onPaste',\r
14088 \r
14089                         'onPreProcess',\r
14090 \r
14091                         'onPostProcess',\r
14092 \r
14093                         'onBeforeSetContent',\r
14094 \r
14095                         'onBeforeGetContent',\r
14096 \r
14097                         'onSetContent',\r
14098 \r
14099                         'onGetContent',\r
14100 \r
14101                         'onLoadContent',\r
14102 \r
14103                         'onSaveContent',\r
14104 \r
14105                         'onNodeChange',\r
14106 \r
14107                         'onChange',\r
14108 \r
14109                         'onBeforeExecCommand',\r
14110 \r
14111                         'onExecCommand',\r
14112 \r
14113                         'onUndo',\r
14114 \r
14115                         'onRedo',\r
14116 \r
14117                         'onVisualAid',\r
14118 \r
14119                         'onSetProgressState',\r
14120 \r
14121                         'onSetAttrib'\r
14122                 ], function(name) {\r
14123                         self[name] = new tinymce.util.Dispatcher(self);\r
14124                 });\r
14125 \r
14126                 // Handle legacy cleanup_callback option\r
14127                 if (settings.cleanup_callback) {\r
14128                         self.onBeforeSetContent.add(function(ed, o) {\r
14129                                 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
14130                         });\r
14131 \r
14132                         self.onPreProcess.add(function(ed, o) {\r
14133                                 if (o.set)\r
14134                                         ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);\r
14135 \r
14136                                 if (o.get)\r
14137                                         ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);\r
14138                         });\r
14139 \r
14140                         self.onPostProcess.add(function(ed, o) {\r
14141                                 if (o.set)\r
14142                                         o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
14143 \r
14144                                 if (o.get)                                              \r
14145                                         o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);\r
14146                         });\r
14147                 }\r
14148 \r
14149                 // Handle legacy save_callback option\r
14150                 if (settings.save_callback) {\r
14151                         self.onGetContent.add(function(ed, o) {\r
14152                                 if (o.save)\r
14153                                         o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());\r
14154                         });\r
14155                 }\r
14156 \r
14157                 // Handle legacy handle_event_callback option\r
14158                 if (settings.handle_event_callback) {\r
14159                         self.onEvent.add(function(ed, e, o) {\r
14160                                 if (self.execCallback('handle_event_callback', e, ed, o) === false)\r
14161                                         Event.cancel(e);\r
14162                         });\r
14163                 }\r
14164 \r
14165                 // Handle legacy handle_node_change_callback option\r
14166                 if (settings.handle_node_change_callback) {\r
14167                         self.onNodeChange.add(function(ed, cm, n) {\r
14168                                 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());\r
14169                         });\r
14170                 }\r
14171 \r
14172                 // Handle legacy save_callback option\r
14173                 if (settings.save_callback) {\r
14174                         self.onSaveContent.add(function(ed, o) {\r
14175                                 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());\r
14176 \r
14177                                 if (h)\r
14178                                         o.content = h;\r
14179                         });\r
14180                 }\r
14181 \r
14182                 // Handle legacy onchange_callback option\r
14183                 if (settings.onchange_callback) {\r
14184                         self.onChange.add(function(ed, l) {\r
14185                                 ed.execCallback('onchange_callback', ed, l);\r
14186                         });\r
14187                 }\r
14188         };\r
14189 \r
14190         tinymce.Editor.prototype.bindNativeEvents = function() {\r
14191                 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset\r
14192                 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;\r
14193 \r
14194                 nativeToDispatcherMap = {\r
14195                         mouseup : 'onMouseUp',\r
14196                         mousedown : 'onMouseDown',\r
14197                         click : 'onClick',\r
14198                         keyup : 'onKeyUp',\r
14199                         keydown : 'onKeyDown',\r
14200                         keypress : 'onKeyPress',\r
14201                         submit : 'onSubmit',\r
14202                         reset : 'onReset',\r
14203                         contextmenu : 'onContextMenu',\r
14204                         dblclick : 'onDblClick',\r
14205                         paste : 'onPaste' // Doesn't work in all browsers yet\r
14206                 };\r
14207 \r
14208                 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown\r
14209                 function eventHandler(evt, args) {\r
14210                         var type = evt.type;\r
14211 \r
14212                         // Don't fire events when it's removed\r
14213                         if (self.removed)\r
14214                                 return;\r
14215 \r
14216                         // Sends the native event out to a global dispatcher then to the specific event dispatcher\r
14217                         if (self.onEvent.dispatch(self, evt, args) !== false) {\r
14218                                 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);\r
14219                         }\r
14220                 };\r
14221 \r
14222                 // Opera doesn't support focus event for contentEditable elements so we need to fake it\r
14223                 function doOperaFocus(e) {\r
14224                         self.focus(true);\r
14225                 };\r
14226 \r
14227                 function nodeChanged() {\r
14228                         // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i>\r
14229                         self.selection.normalize();\r
14230                         self.nodeChanged();\r
14231                 }\r
14232 \r
14233                 // Add DOM events\r
14234                 each(nativeToDispatcherMap, function(dispatcherName, nativeName) {\r
14235                         var root = settings.content_editable ? self.getBody() : self.getDoc();\r
14236 \r
14237                         switch (nativeName) {\r
14238                                 case 'contextmenu':\r
14239                                         dom.bind(root, nativeName, eventHandler);\r
14240                                         break;\r
14241 \r
14242                                 case 'paste':\r
14243                                         dom.bind(self.getBody(), nativeName, eventHandler);\r
14244                                         break;\r
14245 \r
14246                                 case 'submit':\r
14247                                 case 'reset':\r
14248                                         dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);\r
14249                                         break;\r
14250 \r
14251                                 default:\r
14252                                         dom.bind(root, nativeName, eventHandler);\r
14253                         }\r
14254                 });\r
14255 \r
14256                 // Set the editor as active when focused\r
14257                 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {\r
14258                         self.focus(true);\r
14259                 });\r
14260 \r
14261                 if (settings.content_editable && tinymce.isOpera) {\r
14262                         dom.bind(self.getBody(), 'click', doOperaFocus);\r
14263                         dom.bind(self.getBody(), 'keydown', doOperaFocus);\r
14264                 }\r
14265 \r
14266                 // Add node change handler\r
14267                 self.onMouseUp.add(nodeChanged);\r
14268 \r
14269                 self.onKeyUp.add(function(ed, e) {\r
14270                         var keyCode = e.keyCode;\r
14271 \r
14272                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)\r
14273                                 nodeChanged();\r
14274                 });\r
14275 \r
14276                 // Add reset handler\r
14277                 self.onReset.add(function() {\r
14278                         self.setContent(self.startContent, {format : 'raw'});\r
14279                 });\r
14280 \r
14281                 // Add shortcuts\r
14282                 function handleShortcut(e, execute) {\r
14283                         if (e.altKey || e.ctrlKey || e.metaKey) {\r
14284                                 each(self.shortcuts, function(shortcut) {\r
14285                                         var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;\r
14286 \r
14287                                         if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)\r
14288                                                 return;\r
14289 \r
14290                                         if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {\r
14291                                                 e.preventDefault();\r
14292 \r
14293                                                 if (execute) {\r
14294                                                         shortcut.func.call(shortcut.scope);\r
14295                                                 }\r
14296 \r
14297                                                 return true;\r
14298                                         }\r
14299                                 });\r
14300                         }\r
14301                 };\r
14302 \r
14303                 self.onKeyUp.add(function(ed, e) {\r
14304                         handleShortcut(e);\r
14305                 });\r
14306 \r
14307                 self.onKeyPress.add(function(ed, e) {\r
14308                         handleShortcut(e);\r
14309                 });\r
14310 \r
14311                 self.onKeyDown.add(function(ed, e) {\r
14312                         handleShortcut(e, true);\r
14313                 });\r
14314 \r
14315                 if (tinymce.isOpera) {\r
14316                         self.onClick.add(function(ed, e) {\r
14317                                 e.preventDefault();\r
14318                         });\r
14319                 }\r
14320         };\r
14321 })(tinymce);\r
14322 (function(tinymce) {\r
14323         // Added for compression purposes\r
14324         var each = tinymce.each, undef, TRUE = true, FALSE = false;\r
14325 \r
14326         tinymce.EditorCommands = function(editor) {\r
14327                 var dom = editor.dom,\r
14328                         selection = editor.selection,\r
14329                         commands = {state: {}, exec : {}, value : {}},\r
14330                         settings = editor.settings,\r
14331                         formatter = editor.formatter,\r
14332                         bookmark;\r
14333 \r
14334                 function execCommand(command, ui, value) {\r
14335                         var func;\r
14336 \r
14337                         command = command.toLowerCase();\r
14338                         if (func = commands.exec[command]) {\r
14339                                 func(command, ui, value);\r
14340                                 return TRUE;\r
14341                         }\r
14342 \r
14343                         return FALSE;\r
14344                 };\r
14345 \r
14346                 function queryCommandState(command) {\r
14347                         var func;\r
14348 \r
14349                         command = command.toLowerCase();\r
14350                         if (func = commands.state[command])\r
14351                                 return func(command);\r
14352 \r
14353                         return -1;\r
14354                 };\r
14355 \r
14356                 function queryCommandValue(command) {\r
14357                         var func;\r
14358 \r
14359                         command = command.toLowerCase();\r
14360                         if (func = commands.value[command])\r
14361                                 return func(command);\r
14362 \r
14363                         return FALSE;\r
14364                 };\r
14365 \r
14366                 function addCommands(command_list, type) {\r
14367                         type = type || 'exec';\r
14368 \r
14369                         each(command_list, function(callback, command) {\r
14370                                 each(command.toLowerCase().split(','), function(command) {\r
14371                                         commands[type][command] = callback;\r
14372                                 });\r
14373                         });\r
14374                 };\r
14375 \r
14376                 // Expose public methods\r
14377                 tinymce.extend(this, {\r
14378                         execCommand : execCommand,\r
14379                         queryCommandState : queryCommandState,\r
14380                         queryCommandValue : queryCommandValue,\r
14381                         addCommands : addCommands\r
14382                 });\r
14383 \r
14384                 // Private methods\r
14385 \r
14386                 function execNativeCommand(command, ui, value) {\r
14387                         if (ui === undef)\r
14388                                 ui = FALSE;\r
14389 \r
14390                         if (value === undef)\r
14391                                 value = null;\r
14392 \r
14393                         return editor.getDoc().execCommand(command, ui, value);\r
14394                 };\r
14395 \r
14396                 function isFormatMatch(name) {\r
14397                         return formatter.match(name);\r
14398                 };\r
14399 \r
14400                 function toggleFormat(name, value) {\r
14401                         formatter.toggle(name, value ? {value : value} : undef);\r
14402                 };\r
14403 \r
14404                 function storeSelection(type) {\r
14405                         bookmark = selection.getBookmark(type);\r
14406                 };\r
14407 \r
14408                 function restoreSelection() {\r
14409                         selection.moveToBookmark(bookmark);\r
14410                 };\r
14411 \r
14412                 // Add execCommand overrides\r
14413                 addCommands({\r
14414                         // Ignore these, added for compatibility\r
14415                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},\r
14416 \r
14417                         // Add undo manager logic\r
14418                         'mceEndUndoLevel,mceAddUndoLevel' : function() {\r
14419                                 editor.undoManager.add();\r
14420                         },\r
14421 \r
14422                         'Cut,Copy,Paste' : function(command) {\r
14423                                 var doc = editor.getDoc(), failed;\r
14424 \r
14425                                 // Try executing the native command\r
14426                                 try {\r
14427                                         execNativeCommand(command);\r
14428                                 } catch (ex) {\r
14429                                         // Command failed\r
14430                                         failed = TRUE;\r
14431                                 }\r
14432 \r
14433                                 // Present alert message about clipboard access not being available\r
14434                                 if (failed || !doc.queryCommandSupported(command)) {\r
14435                                         if (tinymce.isGecko) {\r
14436                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {\r
14437                                                         if (state)\r
14438                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');\r
14439                                                 });\r
14440                                         } else\r
14441                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));\r
14442                                 }\r
14443                         },\r
14444 \r
14445                         // Override unlink command\r
14446                         unlink : function(command) {\r
14447                                 if (selection.isCollapsed())\r
14448                                         selection.select(selection.getNode());\r
14449 \r
14450                                 execNativeCommand(command);\r
14451                                 selection.collapse(FALSE);\r
14452                         },\r
14453 \r
14454                         // Override justify commands to use the text formatter engine\r
14455                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
14456                                 var align = command.substring(7);\r
14457 \r
14458                                 // Remove all other alignments first\r
14459                                 each('left,center,right,full'.split(','), function(name) {\r
14460                                         if (align != name)\r
14461                                                 formatter.remove('align' + name);\r
14462                                 });\r
14463 \r
14464                                 toggleFormat('align' + align);\r
14465                                 execCommand('mceRepaint');\r
14466                         },\r
14467 \r
14468                         // Override list commands to fix WebKit bug\r
14469                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
14470                                 var listElm, listParent;\r
14471 \r
14472                                 execNativeCommand(command);\r
14473 \r
14474                                 // WebKit produces lists within block elements so we need to split them\r
14475                                 // we will replace the native list creation logic to custom logic later on\r
14476                                 // TODO: Remove this when the list creation logic is removed\r
14477                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');\r
14478                                 if (listElm) {\r
14479                                         listParent = listElm.parentNode;\r
14480 \r
14481                                         // If list is within a text block then split that block\r
14482                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {\r
14483                                                 storeSelection();\r
14484                                                 dom.split(listParent, listElm);\r
14485                                                 restoreSelection();\r
14486                                         }\r
14487                                 }\r
14488                         },\r
14489 \r
14490                         // Override commands to use the text formatter engine\r
14491                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
14492                                 toggleFormat(command);\r
14493                         },\r
14494 \r
14495                         // Override commands to use the text formatter engine\r
14496                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {\r
14497                                 toggleFormat(command, value);\r
14498                         },\r
14499 \r
14500                         FontSize : function(command, ui, value) {\r
14501                                 var fontClasses, fontSizes;\r
14502 \r
14503                                 // Convert font size 1-7 to styles\r
14504                                 if (value >= 1 && value <= 7) {\r
14505                                         fontSizes = tinymce.explode(settings.font_size_style_values);\r
14506                                         fontClasses = tinymce.explode(settings.font_size_classes);\r
14507 \r
14508                                         if (fontClasses)\r
14509                                                 value = fontClasses[value - 1] || value;\r
14510                                         else\r
14511                                                 value = fontSizes[value - 1] || value;\r
14512                                 }\r
14513 \r
14514                                 toggleFormat(command, value);\r
14515                         },\r
14516 \r
14517                         RemoveFormat : function(command) {\r
14518                                 formatter.remove(command);\r
14519                         },\r
14520 \r
14521                         mceBlockQuote : function(command) {\r
14522                                 toggleFormat('blockquote');\r
14523                         },\r
14524 \r
14525                         FormatBlock : function(command, ui, value) {\r
14526                                 return toggleFormat(value || 'p');\r
14527                         },\r
14528 \r
14529                         mceCleanup : function() {\r
14530                                 var bookmark = selection.getBookmark();\r
14531 \r
14532                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});\r
14533 \r
14534                                 selection.moveToBookmark(bookmark);\r
14535                         },\r
14536 \r
14537                         mceRemoveNode : function(command, ui, value) {\r
14538                                 var node = value || selection.getNode();\r
14539 \r
14540                                 // Make sure that the body node isn't removed\r
14541                                 if (node != editor.getBody()) {\r
14542                                         storeSelection();\r
14543                                         editor.dom.remove(node, TRUE);\r
14544                                         restoreSelection();\r
14545                                 }\r
14546                         },\r
14547 \r
14548                         mceSelectNodeDepth : function(command, ui, value) {\r
14549                                 var counter = 0;\r
14550 \r
14551                                 dom.getParent(selection.getNode(), function(node) {\r
14552                                         if (node.nodeType == 1 && counter++ == value) {\r
14553                                                 selection.select(node);\r
14554                                                 return FALSE;\r
14555                                         }\r
14556                                 }, editor.getBody());\r
14557                         },\r
14558 \r
14559                         mceSelectNode : function(command, ui, value) {\r
14560                                 selection.select(value);\r
14561                         },\r
14562 \r
14563                         mceInsertContent : function(command, ui, value) {\r
14564                                 var parser, serializer, parentNode, rootNode, fragment, args,\r
14565                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;\r
14566 \r
14567                                 //selection.normalize();\r
14568 \r
14569                                 // Setup parser and serializer\r
14570                                 parser = editor.parser;\r
14571                                 serializer = new tinymce.html.Serializer({}, editor.schema);\r
14572                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';\r
14573 \r
14574                                 // Run beforeSetContent handlers on the HTML to be inserted\r
14575                                 args = {content: value, format: 'html'};\r
14576                                 selection.onBeforeSetContent.dispatch(selection, args);\r
14577                                 value = args.content;\r
14578 \r
14579                                 // Add caret at end of contents if it's missing\r
14580                                 if (value.indexOf('{$caret}') == -1)\r
14581                                         value += '{$caret}';\r
14582 \r
14583                                 // Replace the caret marker with a span bookmark element\r
14584                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);\r
14585 \r
14586                                 // Insert node maker where we will insert the new HTML and get it's parent\r
14587                                 if (!selection.isCollapsed())\r
14588                                         editor.getDoc().execCommand('Delete', false, null);\r
14589 \r
14590                                 parentNode = selection.getNode();\r
14591 \r
14592                                 // Parse the fragment within the context of the parent node\r
14593                                 args = {context : parentNode.nodeName.toLowerCase()};\r
14594                                 fragment = parser.parse(value, args);\r
14595 \r
14596                                 // Move the caret to a more suitable location\r
14597                                 node = fragment.lastChild;\r
14598                                 if (node.attr('id') == 'mce_marker') {\r
14599                                         marker = node;\r
14600 \r
14601                                         for (node = node.prev; node; node = node.walk(true)) {\r
14602                                                 if (node.type == 3 || !dom.isBlock(node.name)) {\r
14603                                                         node.parent.insert(marker, node, node.name === 'br');\r
14604                                                         break;\r
14605                                                 }\r
14606                                         }\r
14607                                 }\r
14608 \r
14609                                 // If parser says valid we can insert the contents into that parent\r
14610                                 if (!args.invalid) {\r
14611                                         value = serializer.serialize(fragment);\r
14612 \r
14613                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent\r
14614                                         node = parentNode.firstChild;\r
14615                                         node2 = parentNode.lastChild;\r
14616                                         if (!node || (node === node2 && node.nodeName === 'BR'))\r
14617                                                 dom.setHTML(parentNode, value);\r
14618                                         else\r
14619                                                 selection.setContent(value);\r
14620                                 } else {\r
14621                                         // If the fragment was invalid within that context then we need\r
14622                                         // to parse and process the parent it's inserted into\r
14623 \r
14624                                         // Insert bookmark node and get the parent\r
14625                                         selection.setContent(bookmarkHtml);\r
14626                                         parentNode = editor.selection.getNode();\r
14627                                         rootNode = editor.getBody();\r
14628 \r
14629                                         // Opera will return the document node when selection is in root\r
14630                                         if (parentNode.nodeType == 9)\r
14631                                                 parentNode = node = rootNode;\r
14632                                         else\r
14633                                                 node = parentNode;\r
14634 \r
14635                                         // Find the ancestor just before the root element\r
14636                                         while (node !== rootNode) {\r
14637                                                 parentNode = node;\r
14638                                                 node = node.parentNode;\r
14639                                         }\r
14640 \r
14641                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that\r
14642                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);\r
14643                                         value = serializer.serialize(\r
14644                                                 parser.parse(\r
14645                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem\r
14646                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {\r
14647                                                                 return serializer.serialize(fragment);\r
14648                                                         })\r
14649                                                 )\r
14650                                         );\r
14651 \r
14652                                         // Set the inner/outer HTML depending on if we are in the root or not\r
14653                                         if (parentNode == rootNode)\r
14654                                                 dom.setHTML(rootNode, value);\r
14655                                         else\r
14656                                                 dom.setOuterHTML(parentNode, value);\r
14657                                 }\r
14658 \r
14659                                 marker = dom.get('mce_marker');\r
14660 \r
14661                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well\r
14662                                 nodeRect = dom.getRect(marker);\r
14663                                 viewPortRect = dom.getViewPort(editor.getWin());\r
14664 \r
14665                                 // Check if node is out side the viewport if it is then scroll to it\r
14666                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||\r
14667                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {\r
14668                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();\r
14669                                         viewportBodyElement.scrollLeft = nodeRect.x;\r
14670                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;\r
14671                                 }\r
14672 \r
14673                                 // Move selection before marker and remove it\r
14674                                 rng = dom.createRng();\r
14675 \r
14676                                 // If previous sibling is a text node set the selection to the end of that node\r
14677                                 node = marker.previousSibling;\r
14678                                 if (node && node.nodeType == 3) {\r
14679                                         rng.setStart(node, node.nodeValue.length);\r
14680                                 } else {\r
14681                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node\r
14682                                         rng.setStartBefore(marker);\r
14683                                         rng.setEndBefore(marker);\r
14684                                 }\r
14685 \r
14686                                 // Remove the marker node and set the new range\r
14687                                 dom.remove(marker);\r
14688                                 selection.setRng(rng);\r
14689 \r
14690                                 // Dispatch after event and add any visual elements needed\r
14691                                 selection.onSetContent.dispatch(selection, args);\r
14692                                 editor.addVisual();\r
14693                         },\r
14694 \r
14695                         mceInsertRawHTML : function(command, ui, value) {\r
14696                                 selection.setContent('tiny_mce_marker');\r
14697                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));\r
14698                         },\r
14699 \r
14700                         mceSetContent : function(command, ui, value) {\r
14701                                 editor.setContent(value);\r
14702                         },\r
14703 \r
14704                         'Indent,Outdent' : function(command) {\r
14705                                 var intentValue, indentUnit, value;\r
14706 \r
14707                                 // Setup indent level\r
14708                                 intentValue = settings.indentation;\r
14709                                 indentUnit = /[a-z%]+$/i.exec(intentValue);\r
14710                                 intentValue = parseInt(intentValue);\r
14711 \r
14712                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {\r
14713                                         // If forced_root_blocks is set to false we don't have a block to indent so lets create a div\r
14714                                         if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {\r
14715                                                 formatter.apply('div');\r
14716                                         }\r
14717 \r
14718                                         each(selection.getSelectedBlocks(), function(element) {\r
14719                                                 if (command == 'outdent') {\r
14720                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);\r
14721                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');\r
14722                                                 } else\r
14723                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);\r
14724                                         });\r
14725                                 } else\r
14726                                         execNativeCommand(command);\r
14727                         },\r
14728 \r
14729                         mceRepaint : function() {\r
14730                                 var bookmark;\r
14731 \r
14732                                 if (tinymce.isGecko) {\r
14733                                         try {\r
14734                                                 storeSelection(TRUE);\r
14735 \r
14736                                                 if (selection.getSel())\r
14737                                                         selection.getSel().selectAllChildren(editor.getBody());\r
14738 \r
14739                                                 selection.collapse(TRUE);\r
14740                                                 restoreSelection();\r
14741                                         } catch (ex) {\r
14742                                                 // Ignore\r
14743                                         }\r
14744                                 }\r
14745                         },\r
14746 \r
14747                         mceToggleFormat : function(command, ui, value) {\r
14748                                 formatter.toggle(value);\r
14749                         },\r
14750 \r
14751                         InsertHorizontalRule : function() {\r
14752                                 editor.execCommand('mceInsertContent', false, '<hr />');\r
14753                         },\r
14754 \r
14755                         mceToggleVisualAid : function() {\r
14756                                 editor.hasVisual = !editor.hasVisual;\r
14757                                 editor.addVisual();\r
14758                         },\r
14759 \r
14760                         mceReplaceContent : function(command, ui, value) {\r
14761                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));\r
14762                         },\r
14763 \r
14764                         mceInsertLink : function(command, ui, value) {\r
14765                                 var anchor;\r
14766 \r
14767                                 if (typeof(value) == 'string')\r
14768                                         value = {href : value};\r
14769 \r
14770                                 anchor = dom.getParent(selection.getNode(), 'a');\r
14771 \r
14772                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.\r
14773                                 value.href = value.href.replace(' ', '%20');\r
14774 \r
14775                                 // Remove existing links if there could be child links or that the href isn't specified\r
14776                                 if (!anchor || !value.href) {\r
14777                                         formatter.remove('link');\r
14778                                 }               \r
14779 \r
14780                                 // Apply new link to selection\r
14781                                 if (value.href) {\r
14782                                         formatter.apply('link', value, anchor);\r
14783                                 }\r
14784                         },\r
14785 \r
14786                         selectAll : function() {\r
14787                                 var root = dom.getRoot(), rng = dom.createRng();\r
14788 \r
14789                                 rng.setStart(root, 0);\r
14790                                 rng.setEnd(root, root.childNodes.length);\r
14791 \r
14792                                 editor.selection.setRng(rng);\r
14793                         }\r
14794                 });\r
14795 \r
14796                 // Add queryCommandState overrides\r
14797                 addCommands({\r
14798                         // Override justify commands\r
14799                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
14800                                 var name = 'align' + command.substring(7);\r
14801                                 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();\r
14802                                 var matches = tinymce.map(nodes, function(node) {\r
14803                                         return !!formatter.matchNode(node, name);\r
14804                                 });\r
14805                                 return tinymce.inArray(matches, TRUE) !== -1;\r
14806                         },\r
14807 \r
14808                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
14809                                 return isFormatMatch(command);\r
14810                         },\r
14811 \r
14812                         mceBlockQuote : function() {\r
14813                                 return isFormatMatch('blockquote');\r
14814                         },\r
14815 \r
14816                         Outdent : function() {\r
14817                                 var node;\r
14818 \r
14819                                 if (settings.inline_styles) {\r
14820                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
14821                                                 return TRUE;\r
14822 \r
14823                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
14824                                                 return TRUE;\r
14825                                 }\r
14826 \r
14827                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));\r
14828                         },\r
14829 \r
14830                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
14831                                 return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');\r
14832                         }\r
14833                 }, 'state');\r
14834 \r
14835                 // Add queryCommandValue overrides\r
14836                 addCommands({\r
14837                         'FontSize,FontName' : function(command) {\r
14838                                 var value = 0, parent;\r
14839 \r
14840                                 if (parent = dom.getParent(selection.getNode(), 'span')) {\r
14841                                         if (command == 'fontsize')\r
14842                                                 value = parent.style.fontSize;\r
14843                                         else\r
14844                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();\r
14845                                 }\r
14846 \r
14847                                 return value;\r
14848                         }\r
14849                 }, 'value');\r
14850 \r
14851                 // Add undo manager logic\r
14852                 addCommands({\r
14853                         Undo : function() {\r
14854                                 editor.undoManager.undo();\r
14855                         },\r
14856 \r
14857                         Redo : function() {\r
14858                                 editor.undoManager.redo();\r
14859                         }\r
14860                 });\r
14861         };\r
14862 })(tinymce);\r
14863 \r
14864 (function(tinymce) {\r
14865         var Dispatcher = tinymce.util.Dispatcher;\r
14866 \r
14867         tinymce.UndoManager = function(editor) {\r
14868                 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;\r
14869 \r
14870                 function getContent() {\r
14871                         // Remove whitespace before/after and remove pure bogus nodes\r
14872                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));\r
14873                 };\r
14874 \r
14875                 function addNonTypingUndoLevel() {\r
14876                         self.typing = false;\r
14877                         self.add();\r
14878                 };\r
14879 \r
14880                 // Create event instances\r
14881                 onAdd = new Dispatcher(self);\r
14882                 onUndo = new Dispatcher(self);\r
14883                 onRedo = new Dispatcher(self);\r
14884 \r
14885                 // Pass though onAdd event from UndoManager to Editor as onChange\r
14886                 onAdd.add(function(undoman, level) {\r
14887                         if (undoman.hasUndo())\r
14888                                 return editor.onChange.dispatch(editor, level, undoman);\r
14889                 });\r
14890 \r
14891                 // Pass though onUndo event from UndoManager to Editor\r
14892                 onUndo.add(function(undoman, level) {\r
14893                         return editor.onUndo.dispatch(editor, level, undoman);\r
14894                 });\r
14895 \r
14896                 // Pass though onRedo event from UndoManager to Editor\r
14897                 onRedo.add(function(undoman, level) {\r
14898                         return editor.onRedo.dispatch(editor, level, undoman);\r
14899                 });\r
14900 \r
14901                 // Add initial undo level when the editor is initialized\r
14902                 editor.onInit.add(function() {\r
14903                         self.add();\r
14904                 });\r
14905 \r
14906                 // Get position before an execCommand is processed\r
14907                 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {\r
14908                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {\r
14909                                 self.beforeChange();\r
14910                         }\r
14911                 });\r
14912 \r
14913                 // Add undo level after an execCommand call was made\r
14914                 editor.onExecCommand.add(function(ed, cmd, ui, val, args) {\r
14915                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {\r
14916                                 self.add();\r
14917                         }\r
14918                 });\r
14919 \r
14920                 // Add undo level on save contents, drag end and blur/focusout\r
14921                 editor.onSaveContent.add(addNonTypingUndoLevel);\r
14922                 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);\r
14923                 editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {\r
14924                         if (!editor.removed && self.typing) {\r
14925                                 addNonTypingUndoLevel();\r
14926                         }\r
14927                 });\r
14928 \r
14929                 editor.onKeyUp.add(function(editor, e) {\r
14930                         var keyCode = e.keyCode;\r
14931 \r
14932                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {\r
14933                                 addNonTypingUndoLevel();\r
14934                         }\r
14935                 });\r
14936 \r
14937                 editor.onKeyDown.add(function(editor, e) {\r
14938                         var keyCode = e.keyCode;\r
14939 \r
14940                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter\r
14941                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {\r
14942                                 if (self.typing) {\r
14943                                         addNonTypingUndoLevel();\r
14944                                 }\r
14945 \r
14946                                 return;\r
14947                         }\r
14948 \r
14949                         // If key isn't shift,ctrl,alt,capslock,metakey\r
14950                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {\r
14951                                 self.beforeChange();\r
14952                                 self.typing = true;\r
14953                                 self.add();\r
14954                         }\r
14955                 });\r
14956 \r
14957                 editor.onMouseDown.add(function(editor, e) {\r
14958                         if (self.typing) {\r
14959                                 addNonTypingUndoLevel();\r
14960                         }\r
14961                 });\r
14962 \r
14963                 // Add keyboard shortcuts for undo/redo keys\r
14964                 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');\r
14965                 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');\r
14966 \r
14967                 self = {\r
14968                         // Explose for debugging reasons\r
14969                         data : data,\r
14970 \r
14971                         typing : false,\r
14972 \r
14973                         onAdd : onAdd,\r
14974 \r
14975                         onUndo : onUndo,\r
14976 \r
14977                         onRedo : onRedo,\r
14978 \r
14979                         beforeChange : function() {\r
14980                                 beforeBookmark = editor.selection.getBookmark(2, true);\r
14981                         },\r
14982 \r
14983                         add : function(level) {\r
14984                                 var i, settings = editor.settings, lastLevel;\r
14985 \r
14986                                 level = level || {};\r
14987                                 level.content = getContent();\r
14988 \r
14989                                 // Add undo level if needed\r
14990                                 lastLevel = data[index];\r
14991                                 if (lastLevel && lastLevel.content == level.content)\r
14992                                         return null;\r
14993 \r
14994                                 // Set before bookmark on previous level\r
14995                                 if (data[index])\r
14996                                         data[index].beforeBookmark = beforeBookmark;\r
14997 \r
14998                                 // Time to compress\r
14999                                 if (settings.custom_undo_redo_levels) {\r
15000                                         if (data.length > settings.custom_undo_redo_levels) {\r
15001                                                 for (i = 0; i < data.length - 1; i++)\r
15002                                                         data[i] = data[i + 1];\r
15003 \r
15004                                                 data.length--;\r
15005                                                 index = data.length;\r
15006                                         }\r
15007                                 }\r
15008 \r
15009                                 // Get a non intrusive normalized bookmark\r
15010                                 level.bookmark = editor.selection.getBookmark(2, true);\r
15011 \r
15012                                 // Crop array if needed\r
15013                                 if (index < data.length - 1)\r
15014                                         data.length = index + 1;\r
15015 \r
15016                                 data.push(level);\r
15017                                 index = data.length - 1;\r
15018 \r
15019                                 self.onAdd.dispatch(self, level);\r
15020                                 editor.isNotDirty = 0;\r
15021 \r
15022                                 return level;\r
15023                         },\r
15024 \r
15025                         undo : function() {\r
15026                                 var level, i;\r
15027 \r
15028                                 if (self.typing) {\r
15029                                         self.add();\r
15030                                         self.typing = false;\r
15031                                 }\r
15032 \r
15033                                 if (index > 0) {\r
15034                                         level = data[--index];\r
15035 \r
15036                                         editor.setContent(level.content, {format : 'raw'});\r
15037                                         editor.selection.moveToBookmark(level.beforeBookmark);\r
15038 \r
15039                                         self.onUndo.dispatch(self, level);\r
15040                                 }\r
15041 \r
15042                                 return level;\r
15043                         },\r
15044 \r
15045                         redo : function() {\r
15046                                 var level;\r
15047 \r
15048                                 if (index < data.length - 1) {\r
15049                                         level = data[++index];\r
15050 \r
15051                                         editor.setContent(level.content, {format : 'raw'});\r
15052                                         editor.selection.moveToBookmark(level.bookmark);\r
15053 \r
15054                                         self.onRedo.dispatch(self, level);\r
15055                                 }\r
15056 \r
15057                                 return level;\r
15058                         },\r
15059 \r
15060                         clear : function() {\r
15061                                 data = [];\r
15062                                 index = 0;\r
15063                                 self.typing = false;\r
15064                         },\r
15065 \r
15066                         hasUndo : function() {\r
15067                                 return index > 0 || this.typing;\r
15068                         },\r
15069 \r
15070                         hasRedo : function() {\r
15071                                 return index < data.length - 1 && !this.typing;\r
15072                         }\r
15073                 };\r
15074 \r
15075                 return self;\r
15076         };\r
15077 })(tinymce);\r
15078 \r
15079 tinymce.ForceBlocks = function(editor) {\r
15080         var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();\r
15081 \r
15082         function addRootBlocks() {\r
15083                 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped;\r
15084 \r
15085                 if (!node || node.nodeType !== 1 || !settings.forced_root_block)\r
15086                         return;\r
15087 \r
15088                 // Check if node is wrapped in block\r
15089                 while (node && node != rootNode) {\r
15090                         if (blockElements[node.nodeName])\r
15091                                 return;\r
15092 \r
15093                         node = node.parentNode;\r
15094                 }\r
15095 \r
15096                 // Get current selection\r
15097                 rng = selection.getRng();\r
15098                 if (rng.setStart) {\r
15099                         startContainer = rng.startContainer;\r
15100                         startOffset = rng.startOffset;\r
15101                         endContainer = rng.endContainer;\r
15102                         endOffset = rng.endOffset;\r
15103                 } else {\r
15104                         // Force control range into text range\r
15105                         if (rng.item) {\r
15106                                 node = rng.item(0);\r
15107                                 rng = editor.getDoc().body.createTextRange();\r
15108                                 rng.moveToElementText(node);\r
15109                         }\r
15110 \r
15111                         tmpRng = rng.duplicate();\r
15112                         tmpRng.collapse(true);\r
15113                         startOffset = tmpRng.move('character', offset) * -1;\r
15114 \r
15115                         if (!tmpRng.collapsed) {\r
15116                                 tmpRng = rng.duplicate();\r
15117                                 tmpRng.collapse(false);\r
15118                                 endOffset = (tmpRng.move('character', offset) * -1) - startOffset;\r
15119                         }\r
15120                 }\r
15121 \r
15122                 // Wrap non block elements and text nodes\r
15123                 node = rootNode.firstChild;\r
15124                 while (node) {\r
15125                         if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {\r
15126                                 if (!rootBlockNode) {\r
15127                                         rootBlockNode = dom.create(settings.forced_root_block);\r
15128                                         node.parentNode.insertBefore(rootBlockNode, node);\r
15129                                         wrapped = true;\r
15130                                 }\r
15131 \r
15132                                 tempNode = node;\r
15133                                 node = node.nextSibling;\r
15134                                 rootBlockNode.appendChild(tempNode);\r
15135                         } else {\r
15136                                 rootBlockNode = null;\r
15137                                 node = node.nextSibling;\r
15138                         }\r
15139                 }\r
15140 \r
15141                 if (rng.setStart) {\r
15142                         rng.setStart(startContainer, startOffset);\r
15143                         rng.setEnd(endContainer, endOffset);\r
15144                         selection.setRng(rng);\r
15145                 } else {\r
15146                         try {\r
15147                                 rng = editor.getDoc().body.createTextRange();\r
15148                                 rng.moveToElementText(rootNode);\r
15149                                 rng.collapse(true);\r
15150                                 rng.moveStart('character', startOffset);\r
15151 \r
15152                                 if (endOffset > 0)\r
15153                                         rng.moveEnd('character', endOffset);\r
15154 \r
15155                                 rng.select();\r
15156                         } catch (ex) {\r
15157                                 // Ignore\r
15158                         }\r
15159                 }\r
15160 \r
15161                 // Only trigger nodeChange when we wrapped nodes to prevent a forever loop\r
15162                 if (wrapped) {\r
15163                         editor.nodeChanged();\r
15164                 }\r
15165         };\r
15166 \r
15167         // Force root blocks\r
15168         if (settings.forced_root_block) {\r
15169                 editor.onKeyUp.add(addRootBlocks);\r
15170                 editor.onNodeChange.add(addRootBlocks);\r
15171         }\r
15172 };\r
15173 \r
15174 (function(tinymce) {\r
15175         // Shorten names\r
15176         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;\r
15177 \r
15178         tinymce.create('tinymce.ControlManager', {\r
15179                 ControlManager : function(ed, s) {\r
15180                         var t = this, i;\r
15181 \r
15182                         s = s || {};\r
15183                         t.editor = ed;\r
15184                         t.controls = {};\r
15185                         t.onAdd = new tinymce.util.Dispatcher(t);\r
15186                         t.onPostRender = new tinymce.util.Dispatcher(t);\r
15187                         t.prefix = s.prefix || ed.id + '_';\r
15188                         t._cls = {};\r
15189 \r
15190                         t.onPostRender.add(function() {\r
15191                                 each(t.controls, function(c) {\r
15192                                         c.postRender();\r
15193                                 });\r
15194                         });\r
15195                 },\r
15196 \r
15197                 get : function(id) {\r
15198                         return this.controls[this.prefix + id] || this.controls[id];\r
15199                 },\r
15200 \r
15201                 setActive : function(id, s) {\r
15202                         var c = null;\r
15203 \r
15204                         if (c = this.get(id))\r
15205                                 c.setActive(s);\r
15206 \r
15207                         return c;\r
15208                 },\r
15209 \r
15210                 setDisabled : function(id, s) {\r
15211                         var c = null;\r
15212 \r
15213                         if (c = this.get(id))\r
15214                                 c.setDisabled(s);\r
15215 \r
15216                         return c;\r
15217                 },\r
15218 \r
15219                 add : function(c) {\r
15220                         var t = this;\r
15221 \r
15222                         if (c) {\r
15223                                 t.controls[c.id] = c;\r
15224                                 t.onAdd.dispatch(c, t);\r
15225                         }\r
15226 \r
15227                         return c;\r
15228                 },\r
15229 \r
15230                 createControl : function(n) {\r
15231                         var c, t = this, ed = t.editor;\r
15232 \r
15233                         each(ed.plugins, function(p) {\r
15234                                 if (p.createControl) {\r
15235                                         c = p.createControl(n, t);\r
15236 \r
15237                                         if (c)\r
15238                                                 return false;\r
15239                                 }\r
15240                         });\r
15241 \r
15242                         switch (n) {\r
15243                                 case "|":\r
15244                                 case "separator":\r
15245                                         return t.createSeparator();\r
15246                         }\r
15247 \r
15248                         if (!c && ed.buttons && (c = ed.buttons[n]))\r
15249                                 return t.createButton(n, c);\r
15250 \r
15251                         return t.add(c);\r
15252                 },\r
15253 \r
15254                 createDropMenu : function(id, s, cc) {\r
15255                         var t = this, ed = t.editor, c, bm, v, cls;\r
15256 \r
15257                         s = extend({\r
15258                                 'class' : 'mceDropDown',\r
15259                                 constrain : ed.settings.constrain_menus\r
15260                         }, s);\r
15261 \r
15262                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';\r
15263                         if (v = ed.getParam('skin_variant'))\r
15264                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);\r
15265 \r
15266                         s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';\r
15267 \r
15268                         id = t.prefix + id;\r
15269                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;\r
15270                         c = t.controls[id] = new cls(id, s);\r
15271                         c.onAddItem.add(function(c, o) {\r
15272                                 var s = o.settings;\r
15273 \r
15274                                 s.title = ed.getLang(s.title, s.title);\r
15275 \r
15276                                 if (!s.onclick) {\r
15277                                         s.onclick = function(v) {\r
15278                                                 if (s.cmd)\r
15279                                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
15280                                         };\r
15281                                 }\r
15282                         });\r
15283 \r
15284                         ed.onRemove.add(function() {\r
15285                                 c.destroy();\r
15286                         });\r
15287 \r
15288                         // Fix for bug #1897785, #1898007\r
15289                         if (tinymce.isIE) {\r
15290                                 c.onShowMenu.add(function() {\r
15291                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
15292                                         ed.focus();\r
15293 \r
15294                                         bm = ed.selection.getBookmark(1);\r
15295                                 });\r
15296 \r
15297                                 c.onHideMenu.add(function() {\r
15298                                         if (bm) {\r
15299                                                 ed.selection.moveToBookmark(bm);\r
15300                                                 bm = 0;\r
15301                                         }\r
15302                                 });\r
15303                         }\r
15304 \r
15305                         return t.add(c);\r
15306                 },\r
15307 \r
15308                 createListBox : function(id, s, cc) {\r
15309                         var t = this, ed = t.editor, cmd, c, cls;\r
15310 \r
15311                         if (t.get(id))\r
15312                                 return null;\r
15313 \r
15314                         s.title = ed.translate(s.title);\r
15315                         s.scope = s.scope || ed;\r
15316 \r
15317                         if (!s.onselect) {\r
15318                                 s.onselect = function(v) {\r
15319                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
15320                                 };\r
15321                         }\r
15322 \r
15323                         s = extend({\r
15324                                 title : s.title,\r
15325                                 'class' : 'mce_' + id,\r
15326                                 scope : s.scope,\r
15327                                 control_manager : t\r
15328                         }, s);\r
15329 \r
15330                         id = t.prefix + id;\r
15331 \r
15332 \r
15333                         function useNativeListForAccessibility(ed) {\r
15334                                 return ed.settings.use_accessible_selects && !tinymce.isGecko\r
15335                         }\r
15336 \r
15337                         if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))\r
15338                                 c = new tinymce.ui.NativeListBox(id, s);\r
15339                         else {\r
15340                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;\r
15341                                 c = new cls(id, s, ed);\r
15342                         }\r
15343 \r
15344                         t.controls[id] = c;\r
15345 \r
15346                         // Fix focus problem in Safari\r
15347                         if (tinymce.isWebKit) {\r
15348                                 c.onPostRender.add(function(c, n) {\r
15349                                         // Store bookmark on mousedown\r
15350                                         Event.add(n, 'mousedown', function() {\r
15351                                                 ed.bookmark = ed.selection.getBookmark(1);\r
15352                                         });\r
15353 \r
15354                                         // Restore on focus, since it might be lost\r
15355                                         Event.add(n, 'focus', function() {\r
15356                                                 ed.selection.moveToBookmark(ed.bookmark);\r
15357                                                 ed.bookmark = null;\r
15358                                         });\r
15359                                 });\r
15360                         }\r
15361 \r
15362                         if (c.hideMenu)\r
15363                                 ed.onMouseDown.add(c.hideMenu, c);\r
15364 \r
15365                         return t.add(c);\r
15366                 },\r
15367 \r
15368                 createButton : function(id, s, cc) {\r
15369                         var t = this, ed = t.editor, o, c, cls;\r
15370 \r
15371                         if (t.get(id))\r
15372                                 return null;\r
15373 \r
15374                         s.title = ed.translate(s.title);\r
15375                         s.label = ed.translate(s.label);\r
15376                         s.scope = s.scope || ed;\r
15377 \r
15378                         if (!s.onclick && !s.menu_button) {\r
15379                                 s.onclick = function() {\r
15380                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
15381                                 };\r
15382                         }\r
15383 \r
15384                         s = extend({\r
15385                                 title : s.title,\r
15386                                 'class' : 'mce_' + id,\r
15387                                 unavailable_prefix : ed.getLang('unavailable', ''),\r
15388                                 scope : s.scope,\r
15389                                 control_manager : t\r
15390                         }, s);\r
15391 \r
15392                         id = t.prefix + id;\r
15393 \r
15394                         if (s.menu_button) {\r
15395                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;\r
15396                                 c = new cls(id, s, ed);\r
15397                                 ed.onMouseDown.add(c.hideMenu, c);\r
15398                         } else {\r
15399                                 cls = t._cls.button || tinymce.ui.Button;\r
15400                                 c = new cls(id, s, ed);\r
15401                         }\r
15402 \r
15403                         return t.add(c);\r
15404                 },\r
15405 \r
15406                 createMenuButton : function(id, s, cc) {\r
15407                         s = s || {};\r
15408                         s.menu_button = 1;\r
15409 \r
15410                         return this.createButton(id, s, cc);\r
15411                 },\r
15412 \r
15413                 createSplitButton : function(id, s, cc) {\r
15414                         var t = this, ed = t.editor, cmd, c, cls;\r
15415 \r
15416                         if (t.get(id))\r
15417                                 return null;\r
15418 \r
15419                         s.title = ed.translate(s.title);\r
15420                         s.scope = s.scope || ed;\r
15421 \r
15422                         if (!s.onclick) {\r
15423                                 s.onclick = function(v) {\r
15424                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
15425                                 };\r
15426                         }\r
15427 \r
15428                         if (!s.onselect) {\r
15429                                 s.onselect = function(v) {\r
15430                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
15431                                 };\r
15432                         }\r
15433 \r
15434                         s = extend({\r
15435                                 title : s.title,\r
15436                                 'class' : 'mce_' + id,\r
15437                                 scope : s.scope,\r
15438                                 control_manager : t\r
15439                         }, s);\r
15440 \r
15441                         id = t.prefix + id;\r
15442                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;\r
15443                         c = t.add(new cls(id, s, ed));\r
15444                         ed.onMouseDown.add(c.hideMenu, c);\r
15445 \r
15446                         return c;\r
15447                 },\r
15448 \r
15449                 createColorSplitButton : function(id, s, cc) {\r
15450                         var t = this, ed = t.editor, cmd, c, cls, bm;\r
15451 \r
15452                         if (t.get(id))\r
15453                                 return null;\r
15454 \r
15455                         s.title = ed.translate(s.title);\r
15456                         s.scope = s.scope || ed;\r
15457 \r
15458                         if (!s.onclick) {\r
15459                                 s.onclick = function(v) {\r
15460                                         if (tinymce.isIE)\r
15461                                                 bm = ed.selection.getBookmark(1);\r
15462 \r
15463                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
15464                                 };\r
15465                         }\r
15466 \r
15467                         if (!s.onselect) {\r
15468                                 s.onselect = function(v) {\r
15469                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
15470                                 };\r
15471                         }\r
15472 \r
15473                         s = extend({\r
15474                                 title : s.title,\r
15475                                 'class' : 'mce_' + id,\r
15476                                 'menu_class' : ed.getParam('skin') + 'Skin',\r
15477                                 scope : s.scope,\r
15478                                 more_colors_title : ed.getLang('more_colors')\r
15479                         }, s);\r
15480 \r
15481                         id = t.prefix + id;\r
15482                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;\r
15483                         c = new cls(id, s, ed);\r
15484                         ed.onMouseDown.add(c.hideMenu, c);\r
15485 \r
15486                         // Remove the menu element when the editor is removed\r
15487                         ed.onRemove.add(function() {\r
15488                                 c.destroy();\r
15489                         });\r
15490 \r
15491                         // Fix for bug #1897785, #1898007\r
15492                         if (tinymce.isIE) {\r
15493                                 c.onShowMenu.add(function() {\r
15494                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
15495                                         ed.focus();\r
15496                                         bm = ed.selection.getBookmark(1);\r
15497                                 });\r
15498 \r
15499                                 c.onHideMenu.add(function() {\r
15500                                         if (bm) {\r
15501                                                 ed.selection.moveToBookmark(bm);\r
15502                                                 bm = 0;\r
15503                                         }\r
15504                                 });\r
15505                         }\r
15506 \r
15507                         return t.add(c);\r
15508                 },\r
15509 \r
15510                 createToolbar : function(id, s, cc) {\r
15511                         var c, t = this, cls;\r
15512 \r
15513                         id = t.prefix + id;\r
15514                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;\r
15515                         c = new cls(id, s, t.editor);\r
15516 \r
15517                         if (t.get(id))\r
15518                                 return null;\r
15519 \r
15520                         return t.add(c);\r
15521                 },\r
15522                 \r
15523                 createToolbarGroup : function(id, s, cc) {\r
15524                         var c, t = this, cls;\r
15525                         id = t.prefix + id;\r
15526                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;\r
15527                         c = new cls(id, s, t.editor);\r
15528                         \r
15529                         if (t.get(id))\r
15530                                 return null;\r
15531                         \r
15532                         return t.add(c);\r
15533                 },\r
15534 \r
15535                 createSeparator : function(cc) {\r
15536                         var cls = cc || this._cls.separator || tinymce.ui.Separator;\r
15537 \r
15538                         return new cls();\r
15539                 },\r
15540 \r
15541                 setControlType : function(n, c) {\r
15542                         return this._cls[n.toLowerCase()] = c;\r
15543                 },\r
15544         \r
15545                 destroy : function() {\r
15546                         each(this.controls, function(c) {\r
15547                                 c.destroy();\r
15548                         });\r
15549 \r
15550                         this.controls = null;\r
15551                 }\r
15552         });\r
15553 })(tinymce);\r
15554 \r
15555 (function(tinymce) {\r
15556         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;\r
15557 \r
15558         tinymce.create('tinymce.WindowManager', {\r
15559                 WindowManager : function(ed) {\r
15560                         var t = this;\r
15561 \r
15562                         t.editor = ed;\r
15563                         t.onOpen = new Dispatcher(t);\r
15564                         t.onClose = new Dispatcher(t);\r
15565                         t.params = {};\r
15566                         t.features = {};\r
15567                 },\r
15568 \r
15569                 open : function(s, p) {\r
15570                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;\r
15571 \r
15572                         // Default some options\r
15573                         s = s || {};\r
15574                         p = p || {};\r
15575                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window\r
15576                         sh = isOpera ? vp.h : screen.height;\r
15577                         s.name = s.name || 'mc_' + new Date().getTime();\r
15578                         s.width = parseInt(s.width || 320);\r
15579                         s.height = parseInt(s.height || 240);\r
15580                         s.resizable = true;\r
15581                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);\r
15582                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);\r
15583                         p.inline = false;\r
15584                         p.mce_width = s.width;\r
15585                         p.mce_height = s.height;\r
15586                         p.mce_auto_focus = s.auto_focus;\r
15587 \r
15588                         if (mo) {\r
15589                                 if (isIE) {\r
15590                                         s.center = true;\r
15591                                         s.help = false;\r
15592                                         s.dialogWidth = s.width + 'px';\r
15593                                         s.dialogHeight = s.height + 'px';\r
15594                                         s.scroll = s.scrollbars || false;\r
15595                                 }\r
15596                         }\r
15597 \r
15598                         // Build features string\r
15599                         each(s, function(v, k) {\r
15600                                 if (tinymce.is(v, 'boolean'))\r
15601                                         v = v ? 'yes' : 'no';\r
15602 \r
15603                                 if (!/^(name|url)$/.test(k)) {\r
15604                                         if (isIE && mo)\r
15605                                                 f += (f ? ';' : '') + k + ':' + v;\r
15606                                         else\r
15607                                                 f += (f ? ',' : '') + k + '=' + v;\r
15608                                 }\r
15609                         });\r
15610 \r
15611                         t.features = s;\r
15612                         t.params = p;\r
15613                         t.onOpen.dispatch(t, s, p);\r
15614 \r
15615                         u = s.url || s.file;\r
15616                         u = tinymce._addVer(u);\r
15617 \r
15618                         try {\r
15619                                 if (isIE && mo) {\r
15620                                         w = 1;\r
15621                                         window.showModalDialog(u, window, f);\r
15622                                 } else\r
15623                                         w = window.open(u, s.name, f);\r
15624                         } catch (ex) {\r
15625                                 // Ignore\r
15626                         }\r
15627 \r
15628                         if (!w)\r
15629                                 alert(t.editor.getLang('popup_blocked'));\r
15630                 },\r
15631 \r
15632                 close : function(w) {\r
15633                         w.close();\r
15634                         this.onClose.dispatch(this);\r
15635                 },\r
15636 \r
15637                 createInstance : function(cl, a, b, c, d, e) {\r
15638                         var f = tinymce.resolve(cl);\r
15639 \r
15640                         return new f(a, b, c, d, e);\r
15641                 },\r
15642 \r
15643                 confirm : function(t, cb, s, w) {\r
15644                         w = w || window;\r
15645 \r
15646                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));\r
15647                 },\r
15648 \r
15649                 alert : function(tx, cb, s, w) {\r
15650                         var t = this;\r
15651 \r
15652                         w = w || window;\r
15653                         w.alert(t._decode(t.editor.getLang(tx, tx)));\r
15654 \r
15655                         if (cb)\r
15656                                 cb.call(s || t);\r
15657                 },\r
15658 \r
15659                 resizeBy : function(dw, dh, win) {\r
15660                         win.resizeBy(dw, dh);\r
15661                 },\r
15662 \r
15663                 // Internal functions\r
15664 \r
15665                 _decode : function(s) {\r
15666                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');\r
15667                 }\r
15668         });\r
15669 }(tinymce));\r
15670 (function(tinymce) {\r
15671         tinymce.Formatter = function(ed) {\r
15672                 var formats = {},\r
15673                         each = tinymce.each,\r
15674                         dom = ed.dom,\r
15675                         selection = ed.selection,\r
15676                         TreeWalker = tinymce.dom.TreeWalker,\r
15677                         rangeUtils = new tinymce.dom.RangeUtils(dom),\r
15678                         isValid = ed.schema.isValidChild,\r
15679                         isBlock = dom.isBlock,\r
15680                         forcedRootBlock = ed.settings.forced_root_block,\r
15681                         nodeIndex = dom.nodeIndex,\r
15682                         INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',\r
15683                         MCE_ATTR_RE = /^(src|href|style)$/,\r
15684                         FALSE = false,\r
15685                         TRUE = true,\r
15686                         undef,\r
15687                         getContentEditable = dom.getContentEditable;\r
15688 \r
15689                 function isArray(obj) {\r
15690                         return obj instanceof Array;\r
15691                 };\r
15692 \r
15693                 function getParents(node, selector) {\r
15694                         return dom.getParents(node, selector, dom.getRoot());\r
15695                 };\r
15696 \r
15697                 function isCaretNode(node) {\r
15698                         return node.nodeType === 1 && node.id === '_mce_caret';\r
15699                 };\r
15700 \r
15701                 function defaultFormats() {\r
15702                         register({\r
15703                                 alignleft : [\r
15704                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},\r
15705                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}\r
15706                                 ],\r
15707 \r
15708                                 aligncenter : [\r
15709                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},\r
15710                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},\r
15711                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}\r
15712                                 ],\r
15713 \r
15714                                 alignright : [\r
15715                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},\r
15716                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}\r
15717                                 ],\r
15718 \r
15719                                 alignfull : [\r
15720                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}\r
15721                                 ],\r
15722 \r
15723                                 bold : [\r
15724                                         {inline : 'strong', remove : 'all'},\r
15725                                         {inline : 'span', styles : {fontWeight : 'bold'}},\r
15726                                         {inline : 'b', remove : 'all'}\r
15727                                 ],\r
15728 \r
15729                                 italic : [\r
15730                                         {inline : 'em', remove : 'all'},\r
15731                                         {inline : 'span', styles : {fontStyle : 'italic'}},\r
15732                                         {inline : 'i', remove : 'all'}\r
15733                                 ],\r
15734 \r
15735                                 underline : [\r
15736                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},\r
15737                                         {inline : 'u', remove : 'all'}\r
15738                                 ],\r
15739 \r
15740                                 strikethrough : [\r
15741                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},\r
15742                                         {inline : 'strike', remove : 'all'}\r
15743                                 ],\r
15744 \r
15745                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},\r
15746                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},\r
15747                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},\r
15748                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},\r
15749                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},\r
15750                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},\r
15751                                 subscript : {inline : 'sub'},\r
15752                                 superscript : {inline : 'sup'},\r
15753 \r
15754                                 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,\r
15755                                         onmatch : function(node) {\r
15756                                                 return true;\r
15757                                         },\r
15758 \r
15759                                         onformat : function(elm, fmt, vars) {\r
15760                                                 each(vars, function(value, key) {\r
15761                                                         dom.setAttrib(elm, key, value);\r
15762                                                 });\r
15763                                         }\r
15764                                 },\r
15765 \r
15766                                 removeformat : [\r
15767                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},\r
15768                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},\r
15769                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}\r
15770                                 ]\r
15771                         });\r
15772 \r
15773                         // Register default block formats\r
15774                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {\r
15775                                 register(name, {block : name, remove : 'all'});\r
15776                         });\r
15777 \r
15778                         // Register user defined formats\r
15779                         register(ed.settings.formats);\r
15780                 };\r
15781 \r
15782                 function addKeyboardShortcuts() {\r
15783                         // Add some inline shortcuts\r
15784                         ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');\r
15785                         ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');\r
15786                         ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');\r
15787 \r
15788                         // BlockFormat shortcuts keys\r
15789                         for (var i = 1; i <= 6; i++) {\r
15790                                 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);\r
15791                         }\r
15792 \r
15793                         ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);\r
15794                         ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);\r
15795                         ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);\r
15796                 };\r
15797 \r
15798                 // Public functions\r
15799 \r
15800                 function get(name) {\r
15801                         return name ? formats[name] : formats;\r
15802                 };\r
15803 \r
15804                 function register(name, format) {\r
15805                         if (name) {\r
15806                                 if (typeof(name) !== 'string') {\r
15807                                         each(name, function(format, name) {\r
15808                                                 register(name, format);\r
15809                                         });\r
15810                                 } else {\r
15811                                         // Force format into array and add it to internal collection\r
15812                                         format = format.length ? format : [format];\r
15813 \r
15814                                         each(format, function(format) {\r
15815                                                 // Set deep to false by default on selector formats this to avoid removing\r
15816                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs\r
15817                                                 if (format.deep === undef)\r
15818                                                         format.deep = !format.selector;\r
15819 \r
15820                                                 // Default to true\r
15821                                                 if (format.split === undef)\r
15822                                                         format.split = !format.selector || format.inline;\r
15823 \r
15824                                                 // Default to true\r
15825                                                 if (format.remove === undef && format.selector && !format.inline)\r
15826                                                         format.remove = 'none';\r
15827 \r
15828                                                 // Mark format as a mixed format inline + block level\r
15829                                                 if (format.selector && format.inline) {\r
15830                                                         format.mixed = true;\r
15831                                                         format.block_expand = true;\r
15832                                                 }\r
15833 \r
15834                                                 // Split classes if needed\r
15835                                                 if (typeof(format.classes) === 'string')\r
15836                                                         format.classes = format.classes.split(/\s+/);\r
15837                                         });\r
15838 \r
15839                                         formats[name] = format;\r
15840                                 }\r
15841                         }\r
15842                 };\r
15843 \r
15844                 var getTextDecoration = function(node) {\r
15845                         var decoration;\r
15846 \r
15847                         ed.dom.getParent(node, function(n) {\r
15848                                 decoration = ed.dom.getStyle(n, 'text-decoration');\r
15849                                 return decoration && decoration !== 'none';\r
15850                         });\r
15851 \r
15852                         return decoration;\r
15853                 };\r
15854 \r
15855                 var processUnderlineAndColor = function(node) {\r
15856                         var textDecoration;\r
15857                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {\r
15858                                 textDecoration = getTextDecoration(node.parentNode);\r
15859                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {\r
15860                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);\r
15861                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {\r
15862                                         ed.dom.setStyle(node, 'text-decoration', null);\r
15863                                 }\r
15864                         }\r
15865                 };\r
15866 \r
15867                 function apply(name, vars, node) {\r
15868                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();\r
15869 \r
15870                         function setElementFormat(elm, fmt) {\r
15871                                 fmt = fmt || format;\r
15872 \r
15873                                 if (elm) {\r
15874                                         if (fmt.onformat) {\r
15875                                                 fmt.onformat(elm, fmt, vars, node);\r
15876                                         }\r
15877 \r
15878                                         each(fmt.styles, function(value, name) {\r
15879                                                 dom.setStyle(elm, name, replaceVars(value, vars));\r
15880                                         });\r
15881 \r
15882                                         each(fmt.attributes, function(value, name) {\r
15883                                                 dom.setAttrib(elm, name, replaceVars(value, vars));\r
15884                                         });\r
15885 \r
15886                                         each(fmt.classes, function(value) {\r
15887                                                 value = replaceVars(value, vars);\r
15888 \r
15889                                                 if (!dom.hasClass(elm, value))\r
15890                                                         dom.addClass(elm, value);\r
15891                                         });\r
15892                                 }\r
15893                         };\r
15894                         function adjustSelectionToVisibleSelection() {\r
15895                                 function findSelectionEnd(start, end) {\r
15896                                         var walker = new TreeWalker(end);\r
15897                                         for (node = walker.current(); node; node = walker.prev()) {\r
15898                                                 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {\r
15899                                                         return node;\r
15900                                                 }\r
15901                                         }\r
15902                                 };\r
15903 \r
15904                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection\r
15905                                 // as this isn't visible to the user.\r
15906                                 var rng = ed.selection.getRng();\r
15907                                 var start = rng.startContainer;\r
15908                                 var end = rng.endContainer;\r
15909 \r
15910                                 if (start != end && rng.endOffset === 0) {\r
15911                                         var newEnd = findSelectionEnd(start, end);\r
15912                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;\r
15913 \r
15914                                         rng.setEnd(newEnd, endOffset);\r
15915                                 }\r
15916 \r
15917                                 return rng;\r
15918                         }\r
15919                         \r
15920                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){\r
15921                                 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;\r
15922                                 \r
15923                                 // find the index of the first child list.\r
15924                                 each(node.childNodes, function(n, index) {\r
15925                                         if (n.nodeName === "UL" || n.nodeName === "OL") {\r
15926                                                 listIndex = index;\r
15927                                                 list = n;\r
15928                                                 return false;\r
15929                                         }\r
15930                                 });\r
15931                                 \r
15932                                 // get the index of the bookmarks\r
15933                                 each(node.childNodes, function(n, index) {\r
15934                                         if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {\r
15935                                                 if (n.id == bookmark.id + "_start") {\r
15936                                                         startIndex = index;\r
15937                                                 } else if (n.id == bookmark.id + "_end") {\r
15938                                                         endIndex = index;\r
15939                                                 }\r
15940                                         }\r
15941                                 });\r
15942                                 \r
15943                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally\r
15944                                 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {\r
15945                                         each(tinymce.grep(node.childNodes), process);\r
15946                                         return 0;\r
15947                                 } else {\r
15948                                         currentWrapElm = dom.clone(wrapElm, FALSE);\r
15949 \r
15950                                         // create a list of the nodes on the same side of the list as the selection\r
15951                                         each(tinymce.grep(node.childNodes), function(n, index) {\r
15952                                                 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {\r
15953                                                         nodes.push(n); \r
15954                                                         n.parentNode.removeChild(n);\r
15955                                                 }\r
15956                                         });\r
15957 \r
15958                                         // insert the wrapping element either before or after the list.\r
15959                                         if (startIndex < listIndex) {\r
15960                                                 node.insertBefore(currentWrapElm, list);\r
15961                                         } else if (startIndex > listIndex) {\r
15962                                                 node.insertBefore(currentWrapElm, list.nextSibling);\r
15963                                         }\r
15964                                         \r
15965                                         // add the new nodes to the list.\r
15966                                         newWrappers.push(currentWrapElm);\r
15967 \r
15968                                         each(nodes, function(node) {\r
15969                                                 currentWrapElm.appendChild(node);\r
15970                                         });\r
15971 \r
15972                                         return currentWrapElm;\r
15973                                 }\r
15974                         };\r
15975 \r
15976                         function applyRngStyle(rng, bookmark, node_specific) {\r
15977                                 var newWrappers = [], wrapName, wrapElm, contentEditable = true;\r
15978 \r
15979                                 // Setup wrapper element\r
15980                                 wrapName = format.inline || format.block;\r
15981                                 wrapElm = dom.create(wrapName);\r
15982                                 setElementFormat(wrapElm);\r
15983 \r
15984                                 rangeUtils.walk(rng, function(nodes) {\r
15985                                         var currentWrapElm;\r
15986 \r
15987                                         function process(node) {\r
15988                                                 var nodeName, parentName, found, hasContentEditableState, lastContentEditable;\r
15989 \r
15990                                                 lastContentEditable = contentEditable;\r
15991                                                 nodeName = node.nodeName.toLowerCase();\r
15992                                                 parentName = node.parentNode.nodeName.toLowerCase();\r
15993 \r
15994                                                 // Node has a contentEditable value\r
15995                                                 if (node.nodeType === 1 && getContentEditable(node)) {\r
15996                                                         lastContentEditable = contentEditable;\r
15997                                                         contentEditable = getContentEditable(node) === "true";\r
15998                                                         hasContentEditableState = true; // We don't want to wrap the container only it's children\r
15999                                                 }\r
16000 \r
16001                                                 // Stop wrapping on br elements\r
16002                                                 if (isEq(nodeName, 'br')) {\r
16003                                                         currentWrapElm = 0;\r
16004 \r
16005                                                         // Remove any br elements when we wrap things\r
16006                                                         if (format.block)\r
16007                                                                 dom.remove(node);\r
16008 \r
16009                                                         return;\r
16010                                                 }\r
16011 \r
16012                                                 // If node is wrapper type\r
16013                                                 if (format.wrapper && matchNode(node, name, vars)) {\r
16014                                                         currentWrapElm = 0;\r
16015                                                         return;\r
16016                                                 }\r
16017 \r
16018                                                 // Can we rename the block\r
16019                                                 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {\r
16020                                                         node = dom.rename(node, wrapName);\r
16021                                                         setElementFormat(node);\r
16022                                                         newWrappers.push(node);\r
16023                                                         currentWrapElm = 0;\r
16024                                                         return;\r
16025                                                 }\r
16026 \r
16027                                                 // Handle selector patterns\r
16028                                                 if (format.selector) {\r
16029                                                         // Look for matching formats\r
16030                                                         each(formatList, function(format) {\r
16031                                                                 // Check collapsed state if it exists\r
16032                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {\r
16033                                                                         return;\r
16034                                                                 }\r
16035 \r
16036                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {\r
16037                                                                         setElementFormat(node, format);\r
16038                                                                         found = true;\r
16039                                                                 }\r
16040                                                         });\r
16041 \r
16042                                                         // Continue processing if a selector match wasn't found and a inline element is defined\r
16043                                                         if (!format.inline || found) {\r
16044                                                                 currentWrapElm = 0;\r
16045                                                                 return;\r
16046                                                         }\r
16047                                                 }\r
16048 \r
16049                                                 // Is it valid to wrap this item\r
16050                                                 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
16051                                                                 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {\r
16052                                                         // Start wrapping\r
16053                                                         if (!currentWrapElm) {\r
16054                                                                 // Wrap the node\r
16055                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);\r
16056                                                                 node.parentNode.insertBefore(currentWrapElm, node);\r
16057                                                                 newWrappers.push(currentWrapElm);\r
16058                                                         }\r
16059 \r
16060                                                         currentWrapElm.appendChild(node);\r
16061                                                 } else if (nodeName == 'li' && bookmark) {\r
16062                                                         // Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.\r
16063                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);\r
16064                                                 } else {\r
16065                                                         // Start a new wrapper for possible children\r
16066                                                         currentWrapElm = 0;\r
16067                                                         \r
16068                                                         each(tinymce.grep(node.childNodes), process);\r
16069 \r
16070                                                         if (hasContentEditableState) {\r
16071                                                                 contentEditable = lastContentEditable; // Restore last contentEditable state from stack\r
16072                                                         }\r
16073 \r
16074                                                         // End the last wrapper\r
16075                                                         currentWrapElm = 0;\r
16076                                                 }\r
16077                                         };\r
16078 \r
16079                                         // Process siblings from range\r
16080                                         each(nodes, process);\r
16081                                 });\r
16082 \r
16083                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link\r
16084                                 if (format.wrap_links === false) {\r
16085                                         each(newWrappers, function(node) {\r
16086                                                 function process(node) {\r
16087                                                         var i, currentWrapElm, children;\r
16088 \r
16089                                                         if (node.nodeName === 'A') {\r
16090                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);\r
16091                                                                 newWrappers.push(currentWrapElm);\r
16092 \r
16093                                                                 children = tinymce.grep(node.childNodes);\r
16094                                                                 for (i = 0; i < children.length; i++)\r
16095                                                                         currentWrapElm.appendChild(children[i]);\r
16096 \r
16097                                                                 node.appendChild(currentWrapElm);\r
16098                                                         }\r
16099 \r
16100                                                         each(tinymce.grep(node.childNodes), process);\r
16101                                                 };\r
16102 \r
16103                                                 process(node);\r
16104                                         });\r
16105                                 }\r
16106 \r
16107                                 // Cleanup\r
16108                                 \r
16109                                 each(newWrappers, function(node) {\r
16110                                         var childCount;\r
16111 \r
16112                                         function getChildCount(node) {\r
16113                                                 var count = 0;\r
16114 \r
16115                                                 each(node.childNodes, function(node) {\r
16116                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))\r
16117                                                                 count++;\r
16118                                                 });\r
16119 \r
16120                                                 return count;\r
16121                                         };\r
16122 \r
16123                                         function mergeStyles(node) {\r
16124                                                 var child, clone;\r
16125 \r
16126                                                 each(node.childNodes, function(node) {\r
16127                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {\r
16128                                                                 child = node;\r
16129                                                                 return FALSE; // break loop\r
16130                                                         }\r
16131                                                 });\r
16132 \r
16133                                                 // If child was found and of the same type as the current node\r
16134                                                 if (child && matchName(child, format)) {\r
16135                                                         clone = dom.clone(child, FALSE);\r
16136                                                         setElementFormat(clone);\r
16137 \r
16138                                                         dom.replace(clone, node, TRUE);\r
16139                                                         dom.remove(child, 1);\r
16140                                                 }\r
16141 \r
16142                                                 return clone || node;\r
16143                                         };\r
16144 \r
16145                                         childCount = getChildCount(node);\r
16146 \r
16147                                         // Remove empty nodes but only if there is multiple wrappers and they are not block\r
16148                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at\r
16149                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {\r
16150                                                 dom.remove(node, 1);\r
16151                                                 return;\r
16152                                         }\r
16153 \r
16154                                         if (format.inline || format.wrapper) {\r
16155                                                 // Merges the current node with it's children of similar type to reduce the number of elements\r
16156                                                 if (!format.exact && childCount === 1)\r
16157                                                         node = mergeStyles(node);\r
16158 \r
16159                                                 // Remove/merge children\r
16160                                                 each(formatList, function(format) {\r
16161                                                         // Merge all children of similar type will move styles from child to parent\r
16162                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>\r
16163                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>\r
16164                                                         each(dom.select(format.inline, node), function(child) {\r
16165                                                                 var parent;\r
16166 \r
16167                                                                 // When wrap_links is set to false we don't want\r
16168                                                                 // to remove the format on children within links\r
16169                                                                 if (format.wrap_links === false) {\r
16170                                                                         parent = child.parentNode;\r
16171 \r
16172                                                                         do {\r
16173                                                                                 if (parent.nodeName === 'A')\r
16174                                                                                         return;\r
16175                                                                         } while (parent = parent.parentNode);\r
16176                                                                 }\r
16177 \r
16178                                                                 removeFormat(format, vars, child, format.exact ? child : null);\r
16179                                                         });\r
16180                                                 });\r
16181 \r
16182                                                 // Remove child if direct parent is of same type\r
16183                                                 if (matchNode(node.parentNode, name, vars)) {\r
16184                                                         dom.remove(node, 1);\r
16185                                                         node = 0;\r
16186                                                         return TRUE;\r
16187                                                 }\r
16188 \r
16189                                                 // Look for parent with similar style format\r
16190                                                 if (format.merge_with_parents) {\r
16191                                                         dom.getParent(node.parentNode, function(parent) {\r
16192                                                                 if (matchNode(parent, name, vars)) {\r
16193                                                                         dom.remove(node, 1);\r
16194                                                                         node = 0;\r
16195                                                                         return TRUE;\r
16196                                                                 }\r
16197                                                         });\r
16198                                                 }\r
16199 \r
16200                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>\r
16201                                                 if (node && format.merge_siblings !== false) {\r
16202                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);\r
16203                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));\r
16204                                                 }\r
16205                                         }\r
16206                                 });\r
16207                         };\r
16208 \r
16209                         if (format) {\r
16210                                 if (node) {\r
16211                                         if (node.nodeType) {\r
16212                                                 rng = dom.createRng();\r
16213                                                 rng.setStartBefore(node);\r
16214                                                 rng.setEndAfter(node);\r
16215                                                 applyRngStyle(expandRng(rng, formatList), null, true);\r
16216                                         } else {\r
16217                                                 applyRngStyle(node, null, true);\r
16218                                         }\r
16219                                 } else {\r
16220                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
16221                                                 // Obtain selection node before selection is unselected by applyRngStyle()\r
16222                                                 var curSelNode = ed.selection.getNode();\r
16223 \r
16224                                                 // If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false\r
16225                                                 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way\r
16226                                                 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {\r
16227                                                         apply(formatList[0].defaultBlock);\r
16228                                                 }\r
16229 \r
16230                                                 // Apply formatting to selection\r
16231                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());\r
16232                                                 bookmark = selection.getBookmark();\r
16233                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);\r
16234 \r
16235                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.\r
16236                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {\r
16237                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');\r
16238                                                         processUnderlineAndColor(curSelNode);\r
16239                                                 }\r
16240 \r
16241                                                 selection.moveToBookmark(bookmark);\r
16242                                                 moveStart(selection.getRng(TRUE));\r
16243                                                 ed.nodeChanged();\r
16244                                         } else\r
16245                                                 performCaretAction('apply', name, vars);\r
16246                                 }\r
16247                         }\r
16248                 };\r
16249 \r
16250                 function remove(name, vars, node) {\r
16251                         var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;\r
16252 \r
16253                         // Merges the styles for each node\r
16254                         function process(node) {\r
16255                                 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;\r
16256 \r
16257                                 // Node has a contentEditable value\r
16258                                 if (node.nodeType === 1 && getContentEditable(node)) {\r
16259                                         lastContentEditable = contentEditable;\r
16260                                         contentEditable = getContentEditable(node) === "true";\r
16261                                         hasContentEditableState = true; // We don't want to wrap the container only it's children\r
16262                                 }\r
16263 \r
16264                                 // Grab the children first since the nodelist might be changed\r
16265                                 children = tinymce.grep(node.childNodes);\r
16266 \r
16267                                 // Process current node\r
16268                                 if (contentEditable && !hasContentEditableState) {\r
16269                                         for (i = 0, l = formatList.length; i < l; i++) {\r
16270                                                 if (removeFormat(formatList[i], vars, node, node))\r
16271                                                         break;\r
16272                                         }\r
16273                                 }\r
16274 \r
16275                                 // Process the children\r
16276                                 if (format.deep) {\r
16277                                         if (children.length) {                                  \r
16278                                                 for (i = 0, l = children.length; i < l; i++)\r
16279                                                         process(children[i]);\r
16280 \r
16281                                                 if (hasContentEditableState) {\r
16282                                                         contentEditable = lastContentEditable; // Restore last contentEditable state from stack\r
16283                                                 }\r
16284                                         }\r
16285                                 }\r
16286                         };\r
16287 \r
16288                         function findFormatRoot(container) {\r
16289                                 var formatRoot;\r
16290 \r
16291                                 // Find format root\r
16292                                 each(getParents(container.parentNode).reverse(), function(parent) {\r
16293                                         var format;\r
16294 \r
16295                                         // Find format root element\r
16296                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {\r
16297                                                 // Is the node matching the format we are looking for\r
16298                                                 format = matchNode(parent, name, vars);\r
16299                                                 if (format && format.split !== false)\r
16300                                                         formatRoot = parent;\r
16301                                         }\r
16302                                 });\r
16303 \r
16304                                 return formatRoot;\r
16305                         };\r
16306 \r
16307                         function wrapAndSplit(format_root, container, target, split) {\r
16308                                 var parent, clone, lastClone, firstClone, i, formatRootParent;\r
16309 \r
16310                                 // Format root found then clone formats and split it\r
16311                                 if (format_root) {\r
16312                                         formatRootParent = format_root.parentNode;\r
16313 \r
16314                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {\r
16315                                                 clone = dom.clone(parent, FALSE);\r
16316 \r
16317                                                 for (i = 0; i < formatList.length; i++) {\r
16318                                                         if (removeFormat(formatList[i], vars, clone, clone)) {\r
16319                                                                 clone = 0;\r
16320                                                                 break;\r
16321                                                         }\r
16322                                                 }\r
16323 \r
16324                                                 // Build wrapper node\r
16325                                                 if (clone) {\r
16326                                                         if (lastClone)\r
16327                                                                 clone.appendChild(lastClone);\r
16328 \r
16329                                                         if (!firstClone)\r
16330                                                                 firstClone = clone;\r
16331 \r
16332                                                         lastClone = clone;\r
16333                                                 }\r
16334                                         }\r
16335 \r
16336                                         // Never split block elements if the format is mixed\r
16337                                         if (split && (!format.mixed || !isBlock(format_root)))\r
16338                                                 container = dom.split(format_root, container);\r
16339 \r
16340                                         // Wrap container in cloned formats\r
16341                                         if (lastClone) {\r
16342                                                 target.parentNode.insertBefore(lastClone, target);\r
16343                                                 firstClone.appendChild(target);\r
16344                                         }\r
16345                                 }\r
16346 \r
16347                                 return container;\r
16348                         };\r
16349 \r
16350                         function splitToFormatRoot(container) {\r
16351                                 return wrapAndSplit(findFormatRoot(container), container, container, true);\r
16352                         };\r
16353 \r
16354                         function unwrap(start) {\r
16355                                 var node = dom.get(start ? '_start' : '_end'),\r
16356                                         out = node[start ? 'firstChild' : 'lastChild'];\r
16357 \r
16358                                 // If the end is placed within the start the result will be removed\r
16359                                 // So this checks if the out node is a bookmark node if it is it\r
16360                                 // checks for another more suitable node\r
16361                                 if (isBookmarkNode(out))\r
16362                                         out = out[start ? 'firstChild' : 'lastChild'];\r
16363 \r
16364                                 dom.remove(node, true);\r
16365 \r
16366                                 return out;\r
16367                         };\r
16368 \r
16369                         function removeRngStyle(rng) {\r
16370                                 var startContainer, endContainer, node;\r
16371 \r
16372                                 rng = expandRng(rng, formatList, TRUE);\r
16373 \r
16374                                 if (format.split) {\r
16375                                         startContainer = getContainer(rng, TRUE);\r
16376                                         endContainer = getContainer(rng);\r
16377 \r
16378                                         if (startContainer != endContainer) {\r
16379                                                 // WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead\r
16380                                                 // This will happen if you tripple click a table cell and use remove formatting\r
16381                                                 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {\r
16382                                                         startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;\r
16383                                                 }\r
16384 \r
16385                                                 // Wrap start/end nodes in span element since these might be cloned/moved\r
16386                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});\r
16387                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});\r
16388 \r
16389                                                 // Split start/end\r
16390                                                 splitToFormatRoot(startContainer);\r
16391                                                 splitToFormatRoot(endContainer);\r
16392 \r
16393                                                 // Unwrap start/end to get real elements again\r
16394                                                 startContainer = unwrap(TRUE);\r
16395                                                 endContainer = unwrap();\r
16396                                         } else\r
16397                                                 startContainer = endContainer = splitToFormatRoot(startContainer);\r
16398 \r
16399                                         // Update range positions since they might have changed after the split operations\r
16400                                         rng.startContainer = startContainer.parentNode;\r
16401                                         rng.startOffset = nodeIndex(startContainer);\r
16402                                         rng.endContainer = endContainer.parentNode;\r
16403                                         rng.endOffset = nodeIndex(endContainer) + 1;\r
16404                                 }\r
16405 \r
16406                                 // Remove items between start/end\r
16407                                 rangeUtils.walk(rng, function(nodes) {\r
16408                                         each(nodes, function(node) {\r
16409                                                 process(node);\r
16410 \r
16411                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.\r
16412                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {\r
16413                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);\r
16414                                                 }\r
16415                                         });\r
16416                                 });\r
16417                         };\r
16418 \r
16419                         // Handle node\r
16420                         if (node) {\r
16421                                 if (node.nodeType) {\r
16422                                         rng = dom.createRng();\r
16423                                         rng.setStartBefore(node);\r
16424                                         rng.setEndAfter(node);\r
16425                                         removeRngStyle(rng);\r
16426                                 } else {\r
16427                                         removeRngStyle(node);\r
16428                                 }\r
16429 \r
16430                                 return;\r
16431                         }\r
16432 \r
16433                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
16434                                 bookmark = selection.getBookmark();\r
16435                                 removeRngStyle(selection.getRng(TRUE));\r
16436                                 selection.moveToBookmark(bookmark);\r
16437 \r
16438                                 // 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
16439                                 if (format.inline && match(name, vars, selection.getStart())) {\r
16440                                         moveStart(selection.getRng(true));\r
16441                                 }\r
16442 \r
16443                                 ed.nodeChanged();\r
16444                         } else\r
16445                                 performCaretAction('remove', name, vars);\r
16446                 };\r
16447 \r
16448                 function toggle(name, vars, node) {\r
16449                         var fmt = get(name);\r
16450 \r
16451                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))\r
16452                                 remove(name, vars, node);\r
16453                         else\r
16454                                 apply(name, vars, node);\r
16455                 };\r
16456 \r
16457                 function matchNode(node, name, vars, similar) {\r
16458                         var formatList = get(name), format, i, classes;\r
16459 \r
16460                         function matchItems(node, format, item_name) {\r
16461                                 var key, value, items = format[item_name], i;\r
16462 \r
16463                                 // Custom match\r
16464                                 if (format.onmatch) {\r
16465                                         return format.onmatch(node, format, item_name);\r
16466                                 }\r
16467 \r
16468                                 // Check all items\r
16469                                 if (items) {\r
16470                                         // Non indexed object\r
16471                                         if (items.length === undef) {\r
16472                                                 for (key in items) {\r
16473                                                         if (items.hasOwnProperty(key)) {\r
16474                                                                 if (item_name === 'attributes')\r
16475                                                                         value = dom.getAttrib(node, key);\r
16476                                                                 else\r
16477                                                                         value = getStyle(node, key);\r
16478 \r
16479                                                                 if (similar && !value && !format.exact)\r
16480                                                                         return;\r
16481 \r
16482                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))\r
16483                                                                         return;\r
16484                                                         }\r
16485                                                 }\r
16486                                         } else {\r
16487                                                 // Only one match needed for indexed arrays\r
16488                                                 for (i = 0; i < items.length; i++) {\r
16489                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))\r
16490                                                                 return format;\r
16491                                                 }\r
16492                                         }\r
16493                                 }\r
16494 \r
16495                                 return format;\r
16496                         };\r
16497 \r
16498                         if (formatList && node) {\r
16499                                 // Check each format in list\r
16500                                 for (i = 0; i < formatList.length; i++) {\r
16501                                         format = formatList[i];\r
16502 \r
16503                                         // Name name, attributes, styles and classes\r
16504                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {\r
16505                                                 // Match classes\r
16506                                                 if (classes = format.classes) {\r
16507                                                         for (i = 0; i < classes.length; i++) {\r
16508                                                                 if (!dom.hasClass(node, classes[i]))\r
16509                                                                         return;\r
16510                                                         }\r
16511                                                 }\r
16512 \r
16513                                                 return format;\r
16514                                         }\r
16515                                 }\r
16516                         }\r
16517                 };\r
16518 \r
16519                 function match(name, vars, node) {\r
16520                         var startNode;\r
16521 \r
16522                         function matchParents(node) {\r
16523                                 // Find first node with similar format settings\r
16524                                 node = dom.getParent(node, function(node) {\r
16525                                         return !!matchNode(node, name, vars, true);\r
16526                                 });\r
16527 \r
16528                                 // Do an exact check on the similar format element\r
16529                                 return matchNode(node, name, vars);\r
16530                         };\r
16531 \r
16532                         // Check specified node\r
16533                         if (node)\r
16534                                 return matchParents(node);\r
16535 \r
16536                         // Check selected node\r
16537                         node = selection.getNode();\r
16538                         if (matchParents(node))\r
16539                                 return TRUE;\r
16540 \r
16541                         // Check start node if it's different\r
16542                         startNode = selection.getStart();\r
16543                         if (startNode != node) {\r
16544                                 if (matchParents(startNode))\r
16545                                         return TRUE;\r
16546                         }\r
16547 \r
16548                         return FALSE;\r
16549                 };\r
16550 \r
16551                 function matchAll(names, vars) {\r
16552                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;\r
16553 \r
16554                         // Check start of selection for formats\r
16555                         startElement = selection.getStart();\r
16556                         dom.getParent(startElement, function(node) {\r
16557                                 var i, name;\r
16558 \r
16559                                 for (i = 0; i < names.length; i++) {\r
16560                                         name = names[i];\r
16561 \r
16562                                         if (!checkedMap[name] && matchNode(node, name, vars)) {\r
16563                                                 checkedMap[name] = true;\r
16564                                                 matchedFormatNames.push(name);\r
16565                                         }\r
16566                                 }\r
16567                         });\r
16568 \r
16569                         return matchedFormatNames;\r
16570                 };\r
16571 \r
16572                 function canApply(name) {\r
16573                         var formatList = get(name), startNode, parents, i, x, selector;\r
16574 \r
16575                         if (formatList) {\r
16576                                 startNode = selection.getStart();\r
16577                                 parents = getParents(startNode);\r
16578 \r
16579                                 for (x = formatList.length - 1; x >= 0; x--) {\r
16580                                         selector = formatList[x].selector;\r
16581 \r
16582                                         // Format is not selector based, then always return TRUE\r
16583                                         if (!selector)\r
16584                                                 return TRUE;\r
16585 \r
16586                                         for (i = parents.length - 1; i >= 0; i--) {\r
16587                                                 if (dom.is(parents[i], selector))\r
16588                                                         return TRUE;\r
16589                                         }\r
16590                                 }\r
16591                         }\r
16592 \r
16593                         return FALSE;\r
16594                 };\r
16595 \r
16596                 // Expose to public\r
16597                 tinymce.extend(this, {\r
16598                         get : get,\r
16599                         register : register,\r
16600                         apply : apply,\r
16601                         remove : remove,\r
16602                         toggle : toggle,\r
16603                         match : match,\r
16604                         matchAll : matchAll,\r
16605                         matchNode : matchNode,\r
16606                         canApply : canApply\r
16607                 });\r
16608 \r
16609                 // Initialize\r
16610                 defaultFormats();\r
16611                 addKeyboardShortcuts();\r
16612 \r
16613                 // Private functions\r
16614 \r
16615                 function matchName(node, format) {\r
16616                         // Check for inline match\r
16617                         if (isEq(node, format.inline))\r
16618                                 return TRUE;\r
16619 \r
16620                         // Check for block match\r
16621                         if (isEq(node, format.block))\r
16622                                 return TRUE;\r
16623 \r
16624                         // Check for selector match\r
16625                         if (format.selector)\r
16626                                 return dom.is(node, format.selector);\r
16627                 };\r
16628 \r
16629                 function isEq(str1, str2) {\r
16630                         str1 = str1 || '';\r
16631                         str2 = str2 || '';\r
16632 \r
16633                         str1 = '' + (str1.nodeName || str1);\r
16634                         str2 = '' + (str2.nodeName || str2);\r
16635 \r
16636                         return str1.toLowerCase() == str2.toLowerCase();\r
16637                 };\r
16638 \r
16639                 function getStyle(node, name) {\r
16640                         var styleVal = dom.getStyle(node, name);\r
16641 \r
16642                         // Force the format to hex\r
16643                         if (name == 'color' || name == 'backgroundColor')\r
16644                                 styleVal = dom.toHex(styleVal);\r
16645 \r
16646                         // Opera will return bold as 700\r
16647                         if (name == 'fontWeight' && styleVal == 700)\r
16648                                 styleVal = 'bold';\r
16649 \r
16650                         return '' + styleVal;\r
16651                 };\r
16652 \r
16653                 function replaceVars(value, vars) {\r
16654                         if (typeof(value) != "string")\r
16655                                 value = value(vars);\r
16656                         else if (vars) {\r
16657                                 value = value.replace(/%(\w+)/g, function(str, name) {\r
16658                                         return vars[name] || str;\r
16659                                 });\r
16660                         }\r
16661 \r
16662                         return value;\r
16663                 };\r
16664 \r
16665                 function isWhiteSpaceNode(node) {\r
16666                         return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);\r
16667                 };\r
16668 \r
16669                 function wrap(node, name, attrs) {\r
16670                         var wrapper = dom.create(name, attrs);\r
16671 \r
16672                         node.parentNode.insertBefore(wrapper, node);\r
16673                         wrapper.appendChild(node);\r
16674 \r
16675                         return wrapper;\r
16676                 };\r
16677 \r
16678                 function expandRng(rng, format, remove) {\r
16679                         var sibling, lastIdx, leaf, endPoint,\r
16680                                 startContainer = rng.startContainer,\r
16681                                 startOffset = rng.startOffset,\r
16682                                 endContainer = rng.endContainer,\r
16683                                 endOffset = rng.endOffset;\r
16684 \r
16685                         // This function walks up the tree if there is no siblings before/after the node\r
16686                         function findParentContainer(start) {\r
16687                                 var container, parent, child, sibling, siblingName, root;\r
16688 \r
16689                                 container = parent = start ? startContainer : endContainer;\r
16690                                 siblingName = start ? 'previousSibling' : 'nextSibling';\r
16691                                 root = dom.getRoot();\r
16692 \r
16693                                 // If it's a text node and the offset is inside the text\r
16694                                 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {\r
16695                                         if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {\r
16696                                                 return container;\r
16697                                         }\r
16698                                 }\r
16699 \r
16700                                 for (;;) {\r
16701                                         // Stop expanding on block elements\r
16702                                         if (!format[0].block_expand && isBlock(parent))\r
16703                                                 return parent;\r
16704 \r
16705                                         // Walk left/right\r
16706                                         for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {\r
16707                                                 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {\r
16708                                                         return parent;\r
16709                                                 }\r
16710                                         }\r
16711 \r
16712                                         // Check if we can move up are we at root level or body level\r
16713                                         if (parent.parentNode == root) {\r
16714                                                 container = parent;\r
16715                                                 break;\r
16716                                         }\r
16717 \r
16718                                         parent = parent.parentNode;\r
16719                                 }\r
16720 \r
16721                                 return container;\r
16722                         };\r
16723 \r
16724                         // This function walks down the tree to find the leaf at the selection.\r
16725                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.\r
16726                         function findLeaf(node, offset) {\r
16727                                 if (offset === undef)\r
16728                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
16729                                 while (node && node.hasChildNodes()) {\r
16730                                         node = node.childNodes[offset];\r
16731                                         if (node)\r
16732                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
16733                                 }\r
16734                                 return { node: node, offset: offset };\r
16735                         }\r
16736 \r
16737                         // If index based start position then resolve it\r
16738                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {\r
16739                                 lastIdx = startContainer.childNodes.length - 1;\r
16740                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];\r
16741 \r
16742                                 if (startContainer.nodeType == 3)\r
16743                                         startOffset = 0;\r
16744                         }\r
16745 \r
16746                         // If index based end position then resolve it\r
16747                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {\r
16748                                 lastIdx = endContainer.childNodes.length - 1;\r
16749                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];\r
16750 \r
16751                                 if (endContainer.nodeType == 3)\r
16752                                         endOffset = endContainer.nodeValue.length;\r
16753                         }\r
16754 \r
16755                         // Expands the node to the closes contentEditable false element if it exists\r
16756                         function findParentContentEditable(node) {\r
16757                                 var parent = node;\r
16758 \r
16759                                 while (parent) {\r
16760                                         if (parent.nodeType === 1 && getContentEditable(parent)) {\r
16761                                                 return getContentEditable(parent) === "false" ? parent : node;\r
16762                                         }\r
16763 \r
16764                                         parent = parent.parentNode;\r
16765                                 }\r
16766 \r
16767                                 return node;\r
16768                         };\r
16769 \r
16770                         function findWordEndPoint(container, offset, start) {\r
16771                                 var walker, node, pos, lastTextNode;\r
16772 \r
16773                                 function findSpace(node, offset) {\r
16774                                         var pos, pos2, str = node.nodeValue;\r
16775 \r
16776                                         if (typeof(offset) == "undefined") {\r
16777                                                 offset = start ? str.length : 0;\r
16778                                         }\r
16779 \r
16780                                         if (start) {\r
16781                                                 pos = str.lastIndexOf(' ', offset);\r
16782                                                 pos2 = str.lastIndexOf('\u00a0', offset);\r
16783                                                 pos = pos > pos2 ? pos : pos2;\r
16784 \r
16785                                                 // Include the space on remove to avoid tag soup\r
16786                                                 if (pos !== -1 && !remove) {\r
16787                                                         pos++;\r
16788                                                 }\r
16789                                         } else {\r
16790                                                 pos = str.indexOf(' ', offset);\r
16791                                                 pos2 = str.indexOf('\u00a0', offset);\r
16792                                                 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;\r
16793                                         }\r
16794 \r
16795                                         return pos;\r
16796                                 };\r
16797 \r
16798                                 if (container.nodeType === 3) {\r
16799                                         pos = findSpace(container, offset);\r
16800 \r
16801                                         if (pos !== -1) {\r
16802                                                 return {container : container, offset : pos};\r
16803                                         }\r
16804 \r
16805                                         lastTextNode = container;\r
16806                                 }\r
16807 \r
16808                                 // Walk the nodes inside the block\r
16809                                 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());\r
16810                                 while (node = walker[start ? 'prev' : 'next']()) {\r
16811                                         if (node.nodeType === 3) {\r
16812                                                 lastTextNode = node;\r
16813                                                 pos = findSpace(node);\r
16814 \r
16815                                                 if (pos !== -1) {\r
16816                                                         return {container : node, offset : pos};\r
16817                                                 }\r
16818                                         } else if (isBlock(node)) {\r
16819                                                 break;\r
16820                                         }\r
16821                                 }\r
16822 \r
16823                                 if (lastTextNode) {\r
16824                                         if (start) {\r
16825                                                 offset = 0;\r
16826                                         } else {\r
16827                                                 offset = lastTextNode.length;\r
16828                                         }\r
16829 \r
16830                                         return {container: lastTextNode, offset: offset};\r
16831                                 }\r
16832                         };\r
16833 \r
16834                         function findSelectorEndPoint(container, sibling_name) {\r
16835                                 var parents, i, y, curFormat;\r
16836 \r
16837                                 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])\r
16838                                         container = container[sibling_name];\r
16839 \r
16840                                 parents = getParents(container);\r
16841                                 for (i = 0; i < parents.length; i++) {\r
16842                                         for (y = 0; y < format.length; y++) {\r
16843                                                 curFormat = format[y];\r
16844 \r
16845                                                 // If collapsed state is set then skip formats that doesn't match that\r
16846                                                 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)\r
16847                                                         continue;\r
16848 \r
16849                                                 if (dom.is(parents[i], curFormat.selector))\r
16850                                                         return parents[i];\r
16851                                         }\r
16852                                 }\r
16853 \r
16854                                 return container;\r
16855                         };\r
16856 \r
16857                         function findBlockEndPoint(container, sibling_name, sibling_name2) {\r
16858                                 var node;\r
16859 \r
16860                                 // Expand to block of similar type\r
16861                                 if (!format[0].wrapper)\r
16862                                         node = dom.getParent(container, format[0].block);\r
16863 \r
16864                                 // Expand to first wrappable block element or any block element\r
16865                                 if (!node)\r
16866                                         node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);\r
16867 \r
16868                                 // Exclude inner lists from wrapping\r
16869                                 if (node && format[0].wrapper)\r
16870                                         node = getParents(node, 'ul,ol').reverse()[0] || node;\r
16871 \r
16872                                 // Didn't find a block element look for first/last wrappable element\r
16873                                 if (!node) {\r
16874                                         node = container;\r
16875 \r
16876                                         while (node[sibling_name] && !isBlock(node[sibling_name])) {\r
16877                                                 node = node[sibling_name];\r
16878 \r
16879                                                 // Break on BR but include it will be removed later on\r
16880                                                 // we can't remove it now since we need to check if it can be wrapped\r
16881                                                 if (isEq(node, 'br'))\r
16882                                                         break;\r
16883                                         }\r
16884                                 }\r
16885 \r
16886                                 return node || container;\r
16887                         };\r
16888 \r
16889                         // Expand to closest contentEditable element\r
16890                         startContainer = findParentContentEditable(startContainer);\r
16891                         endContainer = findParentContentEditable(endContainer);\r
16892 \r
16893                         // Exclude bookmark nodes if possible\r
16894                         if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {\r
16895                                 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;\r
16896                                 startContainer = startContainer.nextSibling || startContainer;\r
16897 \r
16898                                 if (startContainer.nodeType == 3)\r
16899                                         startOffset = 0;\r
16900                         }\r
16901 \r
16902                         if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {\r
16903                                 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;\r
16904                                 endContainer = endContainer.previousSibling || endContainer;\r
16905 \r
16906                                 if (endContainer.nodeType == 3)\r
16907                                         endOffset = endContainer.length;\r
16908                         }\r
16909 \r
16910                         if (format[0].inline) {\r
16911                                 if (rng.collapsed) {\r
16912                                         // Expand left to closest word boundery\r
16913                                         endPoint = findWordEndPoint(startContainer, startOffset, true);\r
16914                                         if (endPoint) {\r
16915                                                 startContainer = endPoint.container;\r
16916                                                 startOffset = endPoint.offset;\r
16917                                         }\r
16918 \r
16919                                         // Expand right to closest word boundery\r
16920                                         endPoint = findWordEndPoint(endContainer, endOffset);\r
16921                                         if (endPoint) {\r
16922                                                 endContainer = endPoint.container;\r
16923                                                 endOffset = endPoint.offset;\r
16924                                         }\r
16925                                 }\r
16926 \r
16927                                 // Avoid applying formatting to a trailing space.\r
16928                                 leaf = findLeaf(endContainer, endOffset);\r
16929                                 if (leaf.node) {\r
16930                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)\r
16931                                                 leaf = findLeaf(leaf.node.previousSibling);\r
16932 \r
16933                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&\r
16934                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {\r
16935 \r
16936                                                 if (leaf.offset > 1) {\r
16937                                                         endContainer = leaf.node;\r
16938                                                         endContainer.splitText(leaf.offset - 1);\r
16939                                                 }\r
16940                                         }\r
16941                                 }\r
16942                         }\r
16943 \r
16944                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers\r
16945                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!\r
16946                         // This will reduce the number of wrapper elements that needs to be created\r
16947                         // Move start point up the tree\r
16948                         if (format[0].inline || format[0].block_expand) {\r
16949                                 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {\r
16950                                         startContainer = findParentContainer(true);\r
16951                                 }\r
16952 \r
16953                                 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {\r
16954                                         endContainer = findParentContainer();\r
16955                                 }\r
16956                         }\r
16957 \r
16958                         // Expand start/end container to matching selector\r
16959                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {\r
16960                                 // Find new startContainer/endContainer if there is better one\r
16961                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');\r
16962                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');\r
16963                         }\r
16964 \r
16965                         // Expand start/end container to matching block element or text node\r
16966                         if (format[0].block || format[0].selector) {\r
16967                                 // Find new startContainer/endContainer if there is better one\r
16968                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');\r
16969                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');\r
16970 \r
16971                                 // Non block element then try to expand up the leaf\r
16972                                 if (format[0].block) {\r
16973                                         if (!isBlock(startContainer))\r
16974                                                 startContainer = findParentContainer(true);\r
16975 \r
16976                                         if (!isBlock(endContainer))\r
16977                                                 endContainer = findParentContainer();\r
16978                                 }\r
16979                         }\r
16980 \r
16981                         // Setup index for startContainer\r
16982                         if (startContainer.nodeType == 1) {\r
16983                                 startOffset = nodeIndex(startContainer);\r
16984                                 startContainer = startContainer.parentNode;\r
16985                         }\r
16986 \r
16987                         // Setup index for endContainer\r
16988                         if (endContainer.nodeType == 1) {\r
16989                                 endOffset = nodeIndex(endContainer) + 1;\r
16990                                 endContainer = endContainer.parentNode;\r
16991                         }\r
16992 \r
16993                         // Return new range like object\r
16994                         return {\r
16995                                 startContainer : startContainer,\r
16996                                 startOffset : startOffset,\r
16997                                 endContainer : endContainer,\r
16998                                 endOffset : endOffset\r
16999                         };\r
17000                 }\r
17001 \r
17002                 function removeFormat(format, vars, node, compare_node) {\r
17003                         var i, attrs, stylesModified;\r
17004 \r
17005                         // Check if node matches format\r
17006                         if (!matchName(node, format))\r
17007                                 return FALSE;\r
17008 \r
17009                         // Should we compare with format attribs and styles\r
17010                         if (format.remove != 'all') {\r
17011                                 // Remove styles\r
17012                                 each(format.styles, function(value, name) {\r
17013                                         value = replaceVars(value, vars);\r
17014 \r
17015                                         // Indexed array\r
17016                                         if (typeof(name) === 'number') {\r
17017                                                 name = value;\r
17018                                                 compare_node = 0;\r
17019                                         }\r
17020 \r
17021                                         if (!compare_node || isEq(getStyle(compare_node, name), value))\r
17022                                                 dom.setStyle(node, name, '');\r
17023 \r
17024                                         stylesModified = 1;\r
17025                                 });\r
17026 \r
17027                                 // Remove style attribute if it's empty\r
17028                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {\r
17029                                         node.removeAttribute('style');\r
17030                                         node.removeAttribute('data-mce-style');\r
17031                                 }\r
17032 \r
17033                                 // Remove attributes\r
17034                                 each(format.attributes, function(value, name) {\r
17035                                         var valueOut;\r
17036 \r
17037                                         value = replaceVars(value, vars);\r
17038 \r
17039                                         // Indexed array\r
17040                                         if (typeof(name) === 'number') {\r
17041                                                 name = value;\r
17042                                                 compare_node = 0;\r
17043                                         }\r
17044 \r
17045                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {\r
17046                                                 // Keep internal classes\r
17047                                                 if (name == 'class') {\r
17048                                                         value = dom.getAttrib(node, name);\r
17049                                                         if (value) {\r
17050                                                                 // Build new class value where everything is removed except the internal prefixed classes\r
17051                                                                 valueOut = '';\r
17052                                                                 each(value.split(/\s+/), function(cls) {\r
17053                                                                         if (/mce\w+/.test(cls))\r
17054                                                                                 valueOut += (valueOut ? ' ' : '') + cls;\r
17055                                                                 });\r
17056 \r
17057                                                                 // We got some internal classes left\r
17058                                                                 if (valueOut) {\r
17059                                                                         dom.setAttrib(node, name, valueOut);\r
17060                                                                         return;\r
17061                                                                 }\r
17062                                                         }\r
17063                                                 }\r
17064 \r
17065                                                 // IE6 has a bug where the attribute doesn't get removed correctly\r
17066                                                 if (name == "class")\r
17067                                                         node.removeAttribute('className');\r
17068 \r
17069                                                 // Remove mce prefixed attributes\r
17070                                                 if (MCE_ATTR_RE.test(name))\r
17071                                                         node.removeAttribute('data-mce-' + name);\r
17072 \r
17073                                                 node.removeAttribute(name);\r
17074                                         }\r
17075                                 });\r
17076 \r
17077                                 // Remove classes\r
17078                                 each(format.classes, function(value) {\r
17079                                         value = replaceVars(value, vars);\r
17080 \r
17081                                         if (!compare_node || dom.hasClass(compare_node, value))\r
17082                                                 dom.removeClass(node, value);\r
17083                                 });\r
17084 \r
17085                                 // Check for non internal attributes\r
17086                                 attrs = dom.getAttribs(node);\r
17087                                 for (i = 0; i < attrs.length; i++) {\r
17088                                         if (attrs[i].nodeName.indexOf('_') !== 0)\r
17089                                                 return FALSE;\r
17090                                 }\r
17091                         }\r
17092 \r
17093                         // Remove the inline child if it's empty for example <b> or <span>\r
17094                         if (format.remove != 'none') {\r
17095                                 removeNode(node, format);\r
17096                                 return TRUE;\r
17097                         }\r
17098                 };\r
17099 \r
17100                 function removeNode(node, format) {\r
17101                         var parentNode = node.parentNode, rootBlockElm;\r
17102 \r
17103                         function find(node, next, inc) {\r
17104                                 node = getNonWhiteSpaceSibling(node, next, inc);\r
17105 \r
17106                                 return !node || (node.nodeName == 'BR' || isBlock(node));\r
17107                         };\r
17108 \r
17109                         if (format.block) {\r
17110                                 if (!forcedRootBlock) {\r
17111                                         // Append BR elements if needed before we remove the block\r
17112                                         if (isBlock(node) && !isBlock(parentNode)) {\r
17113                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))\r
17114                                                         node.insertBefore(dom.create('br'), node.firstChild);\r
17115 \r
17116                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))\r
17117                                                         node.appendChild(dom.create('br'));\r
17118                                         }\r
17119                                 } else {\r
17120                                         // Wrap the block in a forcedRootBlock if we are at the root of document\r
17121                                         if (parentNode == dom.getRoot()) {\r
17122                                                 if (!format.list_block || !isEq(node, format.list_block)) {\r
17123                                                         each(tinymce.grep(node.childNodes), function(node) {\r
17124                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {\r
17125                                                                         if (!rootBlockElm)\r
17126                                                                                 rootBlockElm = wrap(node, forcedRootBlock);\r
17127                                                                         else\r
17128                                                                                 rootBlockElm.appendChild(node);\r
17129                                                                 } else\r
17130                                                                         rootBlockElm = 0;\r
17131                                                         });\r
17132                                                 }\r
17133                                         }\r
17134                                 }\r
17135                         }\r
17136 \r
17137                         // Never remove nodes that isn't the specified inline element if a selector is specified too\r
17138                         if (format.selector && format.inline && !isEq(format.inline, node))\r
17139                                 return;\r
17140 \r
17141                         dom.remove(node, 1);\r
17142                 };\r
17143 \r
17144                 function getNonWhiteSpaceSibling(node, next, inc) {\r
17145                         if (node) {\r
17146                                 next = next ? 'nextSibling' : 'previousSibling';\r
17147 \r
17148                                 for (node = inc ? node : node[next]; node; node = node[next]) {\r
17149                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))\r
17150                                                 return node;\r
17151                                 }\r
17152                         }\r
17153                 };\r
17154 \r
17155                 function isBookmarkNode(node) {\r
17156                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';\r
17157                 };\r
17158 \r
17159                 function mergeSiblings(prev, next) {\r
17160                         var marker, sibling, tmpSibling;\r
17161 \r
17162                         function compareElements(node1, node2) {\r
17163                                 // Not the same name\r
17164                                 if (node1.nodeName != node2.nodeName)\r
17165                                         return FALSE;\r
17166 \r
17167                                 function getAttribs(node) {\r
17168                                         var attribs = {};\r
17169 \r
17170                                         each(dom.getAttribs(node), function(attr) {\r
17171                                                 var name = attr.nodeName.toLowerCase();\r
17172 \r
17173                                                 // Don't compare internal attributes or style\r
17174                                                 if (name.indexOf('_') !== 0 && name !== 'style')\r
17175                                                         attribs[name] = dom.getAttrib(node, name);\r
17176                                         });\r
17177 \r
17178                                         return attribs;\r
17179                                 };\r
17180 \r
17181                                 function compareObjects(obj1, obj2) {\r
17182                                         var value, name;\r
17183 \r
17184                                         for (name in obj1) {\r
17185                                                 // Obj1 has item obj2 doesn't have\r
17186                                                 if (obj1.hasOwnProperty(name)) {\r
17187                                                         value = obj2[name];\r
17188 \r
17189                                                         // Obj2 doesn't have obj1 item\r
17190                                                         if (value === undef)\r
17191                                                                 return FALSE;\r
17192 \r
17193                                                         // Obj2 item has a different value\r
17194                                                         if (obj1[name] != value)\r
17195                                                                 return FALSE;\r
17196 \r
17197                                                         // Delete similar value\r
17198                                                         delete obj2[name];\r
17199                                                 }\r
17200                                         }\r
17201 \r
17202                                         // Check if obj 2 has something obj 1 doesn't have\r
17203                                         for (name in obj2) {\r
17204                                                 // Obj2 has item obj1 doesn't have\r
17205                                                 if (obj2.hasOwnProperty(name))\r
17206                                                         return FALSE;\r
17207                                         }\r
17208 \r
17209                                         return TRUE;\r
17210                                 };\r
17211 \r
17212                                 // Attribs are not the same\r
17213                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))\r
17214                                         return FALSE;\r
17215 \r
17216                                 // Styles are not the same\r
17217                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))\r
17218                                         return FALSE;\r
17219 \r
17220                                 return TRUE;\r
17221                         };\r
17222 \r
17223                         function findElementSibling(node, sibling_name) {\r
17224                                 for (sibling = node; sibling; sibling = sibling[sibling_name]) {\r
17225                                         if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)\r
17226                                                 return node;\r
17227 \r
17228                                         if (sibling.nodeType == 1 && !isBookmarkNode(sibling))\r
17229                                                 return sibling;\r
17230                                 }\r
17231 \r
17232                                 return node;\r
17233                         };\r
17234 \r
17235                         // Check if next/prev exists and that they are elements\r
17236                         if (prev && next) {\r
17237                                 // If previous sibling is empty then jump over it\r
17238                                 prev = findElementSibling(prev, 'previousSibling');\r
17239                                 next = findElementSibling(next, 'nextSibling');\r
17240 \r
17241                                 // Compare next and previous nodes\r
17242                                 if (compareElements(prev, next)) {\r
17243                                         // Append nodes between\r
17244                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {\r
17245                                                 tmpSibling = sibling;\r
17246                                                 sibling = sibling.nextSibling;\r
17247                                                 prev.appendChild(tmpSibling);\r
17248                                         }\r
17249 \r
17250                                         // Remove next node\r
17251                                         dom.remove(next);\r
17252 \r
17253                                         // Move children into prev node\r
17254                                         each(tinymce.grep(next.childNodes), function(node) {\r
17255                                                 prev.appendChild(node);\r
17256                                         });\r
17257 \r
17258                                         return prev;\r
17259                                 }\r
17260                         }\r
17261 \r
17262                         return next;\r
17263                 };\r
17264 \r
17265                 function isTextBlock(name) {\r
17266                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);\r
17267                 };\r
17268 \r
17269                 function getContainer(rng, start) {\r
17270                         var container, offset, lastIdx, walker;\r
17271 \r
17272                         container = rng[start ? 'startContainer' : 'endContainer'];\r
17273                         offset = rng[start ? 'startOffset' : 'endOffset'];\r
17274 \r
17275                         if (container.nodeType == 1) {\r
17276                                 lastIdx = container.childNodes.length - 1;\r
17277 \r
17278                                 if (!start && offset)\r
17279                                         offset--;\r
17280 \r
17281                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];\r
17282                         }\r
17283 \r
17284                         // If start text node is excluded then walk to the next node\r
17285                         if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {\r
17286                                 container = new TreeWalker(container, ed.getBody()).next() || container;\r
17287                         }\r
17288 \r
17289                         // If end text node is excluded then walk to the previous node\r
17290                         if (container.nodeType === 3 && !start && offset === 0) {\r
17291                                 container = new TreeWalker(container, ed.getBody()).prev() || container;\r
17292                         }\r
17293 \r
17294                         return container;\r
17295                 };\r
17296 \r
17297                 function performCaretAction(type, name, vars) {\r
17298                         var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;\r
17299 \r
17300                         // Creates a caret container bogus element\r
17301                         function createCaretContainer(fill) {\r
17302                                 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});\r
17303 \r
17304                                 if (fill) {\r
17305                                         caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));\r
17306                                 }\r
17307 \r
17308                                 return caretContainer;\r
17309                         };\r
17310 \r
17311                         function isCaretContainerEmpty(node, nodes) {\r
17312                                 while (node) {\r
17313                                         if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {\r
17314                                                 return false;\r
17315                                         }\r
17316 \r
17317                                         // Collect nodes\r
17318                                         if (nodes && node.nodeType === 1) {\r
17319                                                 nodes.push(node);\r
17320                                         }\r
17321 \r
17322                                         node = node.firstChild;\r
17323                                 }\r
17324 \r
17325                                 return true;\r
17326                         };\r
17327                         \r
17328                         // Returns any parent caret container element\r
17329                         function getParentCaretContainer(node) {\r
17330                                 while (node) {\r
17331                                         if (node.id === caretContainerId) {\r
17332                                                 return node;\r
17333                                         }\r
17334 \r
17335                                         node = node.parentNode;\r
17336                                 }\r
17337                         };\r
17338 \r
17339                         // Finds the first text node in the specified node\r
17340                         function findFirstTextNode(node) {\r
17341                                 var walker;\r
17342 \r
17343                                 if (node) {\r
17344                                         walker = new TreeWalker(node, node);\r
17345 \r
17346                                         for (node = walker.current(); node; node = walker.next()) {\r
17347                                                 if (node.nodeType === 3) {\r
17348                                                         return node;\r
17349                                                 }\r
17350                                         }\r
17351                                 }\r
17352                         };\r
17353 \r
17354                         // Removes the caret container for the specified node or all on the current document\r
17355                         function removeCaretContainer(node, move_caret) {\r
17356                                 var child, rng;\r
17357 \r
17358                                 if (!node) {\r
17359                                         node = getParentCaretContainer(selection.getStart());\r
17360 \r
17361                                         if (!node) {\r
17362                                                 while (node = dom.get(caretContainerId)) {\r
17363                                                         removeCaretContainer(node, false);\r
17364                                                 }\r
17365                                         }\r
17366                                 } else {\r
17367                                         rng = selection.getRng(true);\r
17368 \r
17369                                         if (isCaretContainerEmpty(node)) {\r
17370                                                 if (move_caret !== false) {\r
17371                                                         rng.setStartBefore(node);\r
17372                                                         rng.setEndBefore(node);\r
17373                                                 }\r
17374 \r
17375                                                 dom.remove(node);\r
17376                                         } else {\r
17377                                                 child = findFirstTextNode(node);\r
17378 \r
17379                                                 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {\r
17380                                                         child = child.deleteData(0, 1);\r
17381                                                 }\r
17382 \r
17383                                                 dom.remove(node, 1);\r
17384                                         }\r
17385 \r
17386                                         selection.setRng(rng);\r
17387                                 }\r
17388                         };\r
17389                         \r
17390                         // Applies formatting to the caret postion\r
17391                         function applyCaretFormat() {\r
17392                                 var rng, caretContainer, textNode, offset, bookmark, container, text;\r
17393 \r
17394                                 rng = selection.getRng(true);\r
17395                                 offset = rng.startOffset;\r
17396                                 container = rng.startContainer;\r
17397                                 text = container.nodeValue;\r
17398 \r
17399                                 caretContainer = getParentCaretContainer(selection.getStart());\r
17400                                 if (caretContainer) {\r
17401                                         textNode = findFirstTextNode(caretContainer);\r
17402                                 }\r
17403 \r
17404                                 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character\r
17405                                 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {\r
17406                                         // Get bookmark of caret position\r
17407                                         bookmark = selection.getBookmark();\r
17408 \r
17409                                         // Collapse bookmark range (WebKit)\r
17410                                         rng.collapse(true);\r
17411 \r
17412                                         // Expand the range to the closest word and split it at those points\r
17413                                         rng = expandRng(rng, get(name));\r
17414                                         rng = rangeUtils.split(rng);\r
17415 \r
17416                                         // Apply the format to the range\r
17417                                         apply(name, vars, rng);\r
17418 \r
17419                                         // Move selection back to caret position\r
17420                                         selection.moveToBookmark(bookmark);\r
17421                                 } else {\r
17422                                         if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {\r
17423                                                 caretContainer = createCaretContainer(true);\r
17424                                                 textNode = caretContainer.firstChild;\r
17425 \r
17426                                                 rng.insertNode(caretContainer);\r
17427                                                 offset = 1;\r
17428 \r
17429                                                 apply(name, vars, caretContainer);\r
17430                                         } else {\r
17431                                                 apply(name, vars, caretContainer);\r
17432                                         }\r
17433 \r
17434                                         // Move selection to text node\r
17435                                         selection.setCursorLocation(textNode, offset);\r
17436                                 }\r
17437                         };\r
17438 \r
17439                         function removeCaretFormat() {\r
17440                                 var rng = selection.getRng(true), container, offset, bookmark,\r
17441                                         hasContentAfter, node, formatNode, parents = [], i, caretContainer;\r
17442 \r
17443                                 container = rng.startContainer;\r
17444                                 offset = rng.startOffset;\r
17445                                 node = container;\r
17446 \r
17447                                 if (container.nodeType == 3) {\r
17448                                         if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {\r
17449                                                 hasContentAfter = true;\r
17450                                         }\r
17451 \r
17452                                         node = node.parentNode;\r
17453                                 }\r
17454 \r
17455                                 while (node) {\r
17456                                         if (matchNode(node, name, vars)) {\r
17457                                                 formatNode = node;\r
17458                                                 break;\r
17459                                         }\r
17460 \r
17461                                         if (node.nextSibling) {\r
17462                                                 hasContentAfter = true;\r
17463                                         }\r
17464 \r
17465                                         parents.push(node);\r
17466                                         node = node.parentNode;\r
17467                                 }\r
17468 \r
17469                                 // Node doesn't have the specified format\r
17470                                 if (!formatNode) {\r
17471                                         return;\r
17472                                 }\r
17473 \r
17474                                 // Is there contents after the caret then remove the format on the element\r
17475                                 if (hasContentAfter) {\r
17476                                         // Get bookmark of caret position\r
17477                                         bookmark = selection.getBookmark();\r
17478 \r
17479                                         // Collapse bookmark range (WebKit)\r
17480                                         rng.collapse(true);\r
17481 \r
17482                                         // Expand the range to the closest word and split it at those points\r
17483                                         rng = expandRng(rng, get(name), true);\r
17484                                         rng = rangeUtils.split(rng);\r
17485 \r
17486                                         // Remove the format from the range\r
17487                                         remove(name, vars, rng);\r
17488 \r
17489                                         // Move selection back to caret position\r
17490                                         selection.moveToBookmark(bookmark);\r
17491                                 } else {\r
17492                                         caretContainer = createCaretContainer();\r
17493 \r
17494                                         node = caretContainer;\r
17495                                         for (i = parents.length - 1; i >= 0; i--) {\r
17496                                                 node.appendChild(dom.clone(parents[i], false));\r
17497                                                 node = node.firstChild;\r
17498                                         }\r
17499 \r
17500                                         // Insert invisible character into inner most format element\r
17501                                         node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));\r
17502                                         node = node.firstChild;\r
17503 \r
17504                                         // Insert caret container after the formated node\r
17505                                         dom.insertAfter(caretContainer, formatNode);\r
17506 \r
17507                                         // Move selection to text node\r
17508                                         selection.setCursorLocation(node, 1);\r
17509                                 }\r
17510                         };\r
17511 \r
17512                         // Only bind the caret events once\r
17513                         if (!self._hasCaretEvents) {\r
17514                                 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements\r
17515                                 ed.onBeforeGetContent.addToTop(function() {\r
17516                                         var nodes = [], i;\r
17517 \r
17518                                         if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {\r
17519                                                 // Mark children\r
17520                                                 i = nodes.length;\r
17521                                                 while (i--) {\r
17522                                                         dom.setAttrib(nodes[i], 'data-mce-bogus', '1');\r
17523                                                 }\r
17524                                         }\r
17525                                 });\r
17526 \r
17527                                 // Remove caret container on mouse up and on key up\r
17528                                 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {\r
17529                                         ed[name].addToTop(function() {\r
17530                                                 removeCaretContainer();\r
17531                                         });\r
17532                                 });\r
17533 \r
17534                                 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys\r
17535                                 ed.onKeyDown.addToTop(function(ed, e) {\r
17536                                         var keyCode = e.keyCode;\r
17537 \r
17538                                         if (keyCode == 8 || keyCode == 37 || keyCode == 39) {\r
17539                                                 removeCaretContainer(getParentCaretContainer(selection.getStart()));\r
17540                                         }\r
17541                                 });\r
17542 \r
17543                                 // Remove bogus state if they got filled by contents using editor.selection.setContent\r
17544                                 selection.onSetContent.add(function() {\r
17545                                         dom.getParent(selection.getStart(), function(node) {\r
17546                                                 if (node.id !== caretContainerId && dom.getAttrib(node, 'data-mce-bogus') && !dom.isEmpty(node)) {\r
17547                                                         dom.setAttrib(node, 'data-mce-bogus', null);\r
17548                                                 }\r
17549                                         });\r
17550                                 });\r
17551 \r
17552                                 self._hasCaretEvents = true;\r
17553                         }\r
17554 \r
17555                         // Do apply or remove caret format\r
17556                         if (type == "apply") {\r
17557                                 applyCaretFormat();\r
17558                         } else {\r
17559                                 removeCaretFormat();\r
17560                         }\r
17561                 };\r
17562 \r
17563                 function moveStart(rng) {\r
17564                         var container = rng.startContainer,\r
17565                                         offset = rng.startOffset, isAtEndOfText,\r
17566                                         walker, node, nodes, tmpNode;\r
17567 \r
17568                         // Convert text node into index if possible\r
17569                         if (container.nodeType == 3 && offset >= container.nodeValue.length) {\r
17570                                 // Get the parent container location and walk from there\r
17571                                 offset = nodeIndex(container);\r
17572                                 container = container.parentNode;\r
17573                                 isAtEndOfText = true;\r
17574                         }\r
17575 \r
17576                         // Move startContainer/startOffset in to a suitable node\r
17577                         if (container.nodeType == 1) {\r
17578                                 nodes = container.childNodes;\r
17579                                 container = nodes[Math.min(offset, nodes.length - 1)];\r
17580                                 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));\r
17581 \r
17582                                 // If offset is at end of the parent node walk to the next one\r
17583                                 if (offset > nodes.length - 1 || isAtEndOfText)\r
17584                                         walker.next();\r
17585 \r
17586                                 for (node = walker.current(); node; node = walker.next()) {\r
17587                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
17588                                                 // IE has a "neat" feature where it moves the start node into the closest element\r
17589                                                 // we can avoid this by inserting an element before it and then remove it after we set the selection\r
17590                                                 tmpNode = dom.create('a', null, INVISIBLE_CHAR);\r
17591                                                 node.parentNode.insertBefore(tmpNode, node);\r
17592 \r
17593                                                 // Set selection and remove tmpNode\r
17594                                                 rng.setStart(node, 0);\r
17595                                                 selection.setRng(rng);\r
17596                                                 dom.remove(tmpNode);\r
17597 \r
17598                                                 return;\r
17599                                         }\r
17600                                 }\r
17601                         }\r
17602                 };\r
17603         };\r
17604 })(tinymce);\r
17605 \r
17606 tinymce.onAddEditor.add(function(tinymce, ed) {\r
17607         var filters, fontSizes, dom, settings = ed.settings;\r
17608 \r
17609         function replaceWithSpan(node, styles) {\r
17610                 tinymce.each(styles, function(value, name) {\r
17611                         if (value)\r
17612                                 dom.setStyle(node, name, value);\r
17613                 });\r
17614 \r
17615                 dom.rename(node, 'span');\r
17616         };\r
17617 \r
17618         function convert(editor, params) {\r
17619                 dom = editor.dom;\r
17620 \r
17621                 if (settings.convert_fonts_to_spans) {\r
17622                         tinymce.each(dom.select('font,u,strike', params.node), function(node) {\r
17623                                 filters[node.nodeName.toLowerCase()](ed.dom, node);\r
17624                         });\r
17625                 }\r
17626         };\r
17627 \r
17628         if (settings.inline_styles) {\r
17629                 fontSizes = tinymce.explode(settings.font_size_legacy_values);\r
17630 \r
17631                 filters = {\r
17632                         font : function(dom, node) {\r
17633                                 replaceWithSpan(node, {\r
17634                                         backgroundColor : node.style.backgroundColor,\r
17635                                         color : node.color,\r
17636                                         fontFamily : node.face,\r
17637                                         fontSize : fontSizes[parseInt(node.size, 10) - 1]\r
17638                                 });\r
17639                         },\r
17640 \r
17641                         u : function(dom, node) {\r
17642                                 replaceWithSpan(node, {\r
17643                                         textDecoration : 'underline'\r
17644                                 });\r
17645                         },\r
17646 \r
17647                         strike : function(dom, node) {\r
17648                                 replaceWithSpan(node, {\r
17649                                         textDecoration : 'line-through'\r
17650                                 });\r
17651                         }\r
17652                 };\r
17653 \r
17654                 ed.onPreProcess.add(convert);\r
17655                 ed.onSetContent.add(convert);\r
17656 \r
17657                 ed.onInit.add(function() {\r
17658                         ed.selection.onSetContent.add(convert);\r
17659                 });\r
17660         }\r
17661 });\r
17662 \r
17663 (function(tinymce) {\r
17664         var TreeWalker = tinymce.dom.TreeWalker;\r
17665 \r
17666         tinymce.EnterKey = function(editor) {\r
17667                 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager;\r
17668 \r
17669                 function handleEnterKey(evt) {\r
17670                         var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,\r
17671                                 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;\r
17672 \r
17673                         // Returns true if the block can be split into two blocks or not\r
17674                         function canSplitBlock(node) {\r
17675                                 return node &&\r
17676                                         dom.isBlock(node) &&\r
17677                                         !/^(TD|TH|CAPTION)$/.test(node.nodeName) &&\r
17678                                         !/^(fixed|absolute)/i.test(node.style.position) && \r
17679                                         dom.getContentEditable(node) !== "true";\r
17680                         };\r
17681 \r
17682                         // Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image\r
17683                         function moveToCaretPosition(root) {\r
17684                                 var walker, node, rng, y, viewPort, lastNode = root, tempElm;\r
17685 \r
17686                                 rng = dom.createRng();\r
17687 \r
17688                                 if (root.hasChildNodes()) {\r
17689                                         walker = new TreeWalker(root, root);\r
17690 \r
17691                                         while (node = walker.current()) {\r
17692                                                 if (node.nodeType == 3) {\r
17693                                                         rng.setStart(node, 0);\r
17694                                                         rng.setEnd(node, 0);\r
17695                                                         break;\r
17696                                                 }\r
17697 \r
17698                                                 if (/^(BR|IMG)$/.test(node.nodeName)) {\r
17699                                                         rng.setStartBefore(node);\r
17700                                                         rng.setEndBefore(node);\r
17701                                                         break;\r
17702                                                 }\r
17703 \r
17704                                                 lastNode = node;\r
17705                                                 node = walker.next();\r
17706                                         }\r
17707 \r
17708                                         if (!node) {\r
17709                                                 rng.setStart(lastNode, 0);\r
17710                                                 rng.setEnd(lastNode, 0);\r
17711                                         }\r
17712                                 } else {\r
17713                                         if (root.nodeName == 'BR') {\r
17714                                                 if (root.nextSibling && dom.isBlock(root.nextSibling)) {\r
17715                                                         // Trick on older IE versions to render the caret before the BR between two lists\r
17716                                                         if (!documentMode || documentMode < 9) {\r
17717                                                                 tempElm = dom.create('br');\r
17718                                                                 root.parentNode.insertBefore(tempElm, root);\r
17719                                                         }\r
17720 \r
17721                                                         rng.setStartBefore(root);\r
17722                                                         rng.setEndBefore(root);\r
17723                                                 } else {\r
17724                                                         rng.setStartAfter(root);\r
17725                                                         rng.setEndAfter(root);\r
17726                                                 }\r
17727                                         } else {\r
17728                                                 rng.setStart(root, 0);\r
17729                                                 rng.setEnd(root, 0);\r
17730                                         }\r
17731                                 }\r
17732 \r
17733                                 selection.setRng(rng);\r
17734 \r
17735                                 // Remove tempElm created for old IE:s\r
17736                                 dom.remove(tempElm);\r
17737 \r
17738                                 viewPort = dom.getViewPort(editor.getWin());\r
17739 \r
17740                                 // 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
17741                                 y = dom.getPos(root).y;\r
17742                                 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {\r
17743                                         editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks\r
17744                                 }\r
17745                         };\r
17746 \r
17747                         // Creates a new block element by cloning the current one or creating a new one if the name is specified\r
17748                         // This function will also copy any text formatting from the parent block and add it to the new one\r
17749                         function createNewBlock(name) {\r
17750                                 var node = container, block, clonedNode, caretNode;\r
17751 \r
17752                                 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);\r
17753                                 caretNode = block;\r
17754 \r
17755                                 // Clone any parent styles\r
17756                                 if (settings.keep_styles !== false) {\r
17757                                         do {\r
17758                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {\r
17759                                                         clonedNode = node.cloneNode(false);\r
17760                                                         dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique\r
17761 \r
17762                                                         if (block.hasChildNodes()) {\r
17763                                                                 clonedNode.appendChild(block.firstChild);\r
17764                                                                 block.appendChild(clonedNode);\r
17765                                                         } else {\r
17766                                                                 caretNode = clonedNode;\r
17767                                                                 block.appendChild(clonedNode);\r
17768                                                         }\r
17769                                                 }\r
17770                                         } while (node = node.parentNode);\r
17771                                 }\r
17772 \r
17773                                 // BR is needed in empty blocks on non IE browsers\r
17774                                 if (!tinymce.isIE) {\r
17775                                         caretNode.innerHTML = '<br>';\r
17776                                 }\r
17777 \r
17778                                 return block;\r
17779                         };\r
17780 \r
17781                         // Returns true/false if the caret is at the start/end of the parent block element\r
17782                         function isCaretAtStartOrEndOfBlock(start) {\r
17783                                 var walker, node, name;\r
17784 \r
17785                                 // Caret is in the middle of a text node like "a|b"\r
17786                                 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {\r
17787                                         return false;\r
17788                                 }\r
17789 \r
17790                                 // If after the last element in block node edge case for #5091\r
17791                                 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {\r
17792                                         return true;\r
17793                                 }\r
17794 \r
17795                                 // Caret can be before/after a table\r
17796                                 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {\r
17797                                         return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);\r
17798                                 }\r
17799 \r
17800                                 // Walk the DOM and look for text nodes or non empty elements\r
17801                                 walker = new TreeWalker(container, parentBlock);\r
17802                                 while (node = (start ? walker.prev() : walker.next())) {\r
17803                                         if (node.nodeType === 1) {\r
17804                                                 // Ignore bogus elements\r
17805                                                 if (node.getAttribute('data-mce-bogus')) {\r
17806                                                         continue;\r
17807                                                 }\r
17808 \r
17809                                                 // Keep empty elements like <img />\r
17810                                                 name = node.nodeName.toLowerCase();\r
17811                                                 if (name === 'IMG') {\r
17812                                                         return false;\r
17813                                                 }\r
17814                                         } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {\r
17815                                                 return false;\r
17816                                         }\r
17817                                 }\r
17818 \r
17819                                 return true;\r
17820                         };\r
17821 \r
17822                         // Wraps any text nodes or inline elements in the specified forced root block name\r
17823                         function wrapSelfAndSiblingsInDefaultBlock(container, offset) {\r
17824                                 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';\r
17825 \r
17826                                 // Not in a block element or in a table cell or caption\r
17827                                 parentBlock = dom.getParent(container, dom.isBlock);\r
17828                                 if (!parentBlock || !canSplitBlock(parentBlock)) {\r
17829                                         parentBlock = parentBlock || editableRoot;\r
17830 \r
17831                                         if (!parentBlock.hasChildNodes()) {\r
17832                                                 newBlock = dom.create(blockName);\r
17833                                                 parentBlock.appendChild(newBlock);\r
17834                                                 rng.setStart(newBlock, 0);\r
17835                                                 rng.setEnd(newBlock, 0);\r
17836                                                 return newBlock;\r
17837                                         }\r
17838 \r
17839                                         // Find parent that is the first child of parentBlock\r
17840                                         node = container;\r
17841                                         while (node.parentNode != parentBlock) {\r
17842                                                 node = node.parentNode;\r
17843                                         }\r
17844 \r
17845                                         // Loop left to find start node start wrapping at\r
17846                                         while (node && !dom.isBlock(node)) {\r
17847                                                 startNode = node;\r
17848                                                 node = node.previousSibling;\r
17849                                         }\r
17850 \r
17851                                         if (startNode) {\r
17852                                                 newBlock = dom.create(blockName);\r
17853                                                 startNode.parentNode.insertBefore(newBlock, startNode);\r
17854 \r
17855                                                 // Start wrapping until we hit a block\r
17856                                                 node = startNode;\r
17857                                                 while (node && !dom.isBlock(node)) {\r
17858                                                         next = node.nextSibling;\r
17859                                                         newBlock.appendChild(node);\r
17860                                                         node = next;\r
17861                                                 }\r
17862 \r
17863                                                 // Restore range to it's past location\r
17864                                                 rng.setStart(container, offset);\r
17865                                                 rng.setEnd(container, offset);\r
17866                                         }\r
17867                                 }\r
17868 \r
17869                                 return container;\r
17870                         };\r
17871 \r
17872                         // Inserts a block or br before/after or in the middle of a split list of the LI is empty\r
17873                         function handleEmptyListItem() {\r
17874                                 function isFirstOrLastLi(first) {\r
17875                                         var node = containerBlock[first ? 'firstChild' : 'lastChild'];\r
17876 \r
17877                                         // Find first/last element since there might be whitespace there\r
17878                                         while (node) {\r
17879                                                 if (node.nodeType == 1) {\r
17880                                                         break;\r
17881                                                 }\r
17882 \r
17883                                                 node = node[first ? 'nextSibling' : 'previousSibling'];\r
17884                                         }\r
17885 \r
17886                                         return node === parentBlock;\r
17887                                 };\r
17888 \r
17889                                 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');\r
17890 \r
17891                                 if (isFirstOrLastLi(true) && isFirstOrLastLi()) {\r
17892                                         // Is first and last list item then replace the OL/UL with a text block\r
17893                                         dom.replace(newBlock, containerBlock);\r
17894                                 } else if (isFirstOrLastLi(true)) {\r
17895                                         // First LI in list then remove LI and add text block before list\r
17896                                         containerBlock.parentNode.insertBefore(newBlock, containerBlock);\r
17897                                 } else if (isFirstOrLastLi()) {\r
17898                                         // Last LI in list then temove LI and add text block after list\r
17899                                         dom.insertAfter(newBlock, containerBlock);\r
17900                                 } else {\r
17901                                         // Middle LI in list the split the list and insert a text block in the middle\r
17902                                         // Extract after fragment and insert it after the current block\r
17903                                         tmpRng = rng.cloneRange();\r
17904                                         tmpRng.setStartAfter(parentBlock);\r
17905                                         tmpRng.setEndAfter(containerBlock);\r
17906                                         fragment = tmpRng.extractContents();\r
17907                                         dom.insertAfter(fragment, containerBlock);\r
17908                                         dom.insertAfter(newBlock, containerBlock);\r
17909                                 }\r
17910 \r
17911                                 dom.remove(parentBlock);\r
17912                                 moveToCaretPosition(newBlock);\r
17913                                 undoManager.add();\r
17914                         };\r
17915 \r
17916                         // Walks the parent block to the right and look for BR elements\r
17917                         function hasRightSideBr() {\r
17918                                 var walker = new TreeWalker(container, parentBlock), node;\r
17919 \r
17920                                 while (node = walker.current()) {\r
17921                                         if (node.nodeName == 'BR') {\r
17922                                                 return true;\r
17923                                         }\r
17924 \r
17925                                         node = walker.next();\r
17926                                 }\r
17927                         }\r
17928                         \r
17929                         // Inserts a BR element if the forced_root_block option is set to false or empty string\r
17930                         function insertBr() {\r
17931                                 var brElm, extraBr;\r
17932 \r
17933                                 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {\r
17934                                         // Insert extra BR element at the end block elements\r
17935                                         if (!tinymce.isIE && !hasRightSideBr()) {\r
17936                                                 brElm = dom.create('br')\r
17937                                                 rng.insertNode(brElm);\r
17938                                                 rng.setStartAfter(brElm);\r
17939                                                 rng.setEndAfter(brElm);\r
17940                                                 extraBr = true;\r
17941                                         }\r
17942                                 }\r
17943 \r
17944                                 brElm = dom.create('br');\r
17945                                 rng.insertNode(brElm);\r
17946 \r
17947                                 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it\r
17948                                 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {\r
17949                                         brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);\r
17950                                 }\r
17951 \r
17952                                 if (!extraBr) {\r
17953                                         rng.setStartAfter(brElm);\r
17954                                         rng.setEndAfter(brElm);\r
17955                                 } else {\r
17956                                         rng.setStartBefore(brElm);\r
17957                                         rng.setEndBefore(brElm);\r
17958                                 }\r
17959 \r
17960                                 selection.setRng(rng);\r
17961                                 undoManager.add();\r
17962                         };\r
17963 \r
17964                         // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element\r
17965                         function trimLeadingLineBreaks(node) {\r
17966                                 do {\r
17967                                         if (node.nodeType === 3) {\r
17968                                                 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');\r
17969                                         }\r
17970 \r
17971                                         node = node.firstChild;\r
17972                                 } while (node);\r
17973                         };\r
17974 \r
17975                         function getEditableRoot(node) {\r
17976                                 var root = dom.getRoot(), parent, editableRoot;\r
17977 \r
17978                                 // Get all parents until we hit a non editable parent or the root\r
17979                                 parent = node;\r
17980                                 while (parent !== root && dom.getContentEditable(parent) !== "false") {\r
17981                                         if (dom.getContentEditable(parent) === "true") {\r
17982                                                 editableRoot = parent;\r
17983                                         }\r
17984 \r
17985                                         parent = parent.parentNode;\r
17986                                 }\r
17987                                 \r
17988                                 return parent !== root ? editableRoot : root;\r
17989                         };\r
17990 \r
17991                         // Delete any selected contents\r
17992                         if (!rng.collapsed) {\r
17993                                 editor.execCommand('Delete');\r
17994                                 return;\r
17995                         }\r
17996 \r
17997                         // Event is blocked by some other handler for example the lists plugin\r
17998                         if (evt.isDefaultPrevented()) {\r
17999                                 return;\r
18000                         }\r
18001 \r
18002                         // Setup range items and newBlockName\r
18003                         container = rng.startContainer;\r
18004                         offset = rng.startOffset;\r
18005                         newBlockName = settings.forced_root_block;\r
18006                         newBlockName = newBlockName ? newBlockName.toUpperCase() : '';\r
18007                         documentMode = dom.doc.documentMode;\r
18008 \r
18009                         // Resolve node index\r
18010                         if (container.nodeType == 1 && container.hasChildNodes()) {\r
18011                                 isAfterLastNodeInContainer = offset > container.childNodes.length - 1;\r
18012                                 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;\r
18013                                 offset = 0;\r
18014                         }\r
18015 \r
18016                         // Get editable root node normaly the body element but sometimes a div or span\r
18017                         editableRoot = getEditableRoot(container);\r
18018 \r
18019                         // If there is no editable root then enter is done inside a contentEditable false element\r
18020                         if (!editableRoot) {\r
18021                                 return;\r
18022                         }\r
18023 \r
18024                         undoManager.beforeChange();\r
18025 \r
18026                         // If editable root isn't block nor the root of the editor\r
18027                         if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {\r
18028                                 if (!newBlockName || evt.shiftKey) {\r
18029                                         insertBr();\r
18030                                 }\r
18031 \r
18032                                 return;\r
18033                         }\r
18034 \r
18035                         // Wrap the current node and it's sibling in a default block if it's needed.\r
18036                         // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>\r
18037                         // This won't happen if root blocks are disabled or the shiftKey is pressed\r
18038                         if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {\r
18039                                 container = wrapSelfAndSiblingsInDefaultBlock(container, offset);\r
18040                         }\r
18041 \r
18042                         // Find parent block and setup empty block paddings\r
18043                         parentBlock = dom.getParent(container, dom.isBlock);\r
18044                         containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;\r
18045 \r
18046                         // Setup block names\r
18047                         parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5\r
18048                         containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5\r
18049 \r
18050                         // Handle enter inside an empty list item\r
18051                         if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {\r
18052                                 // Let the list plugin or browser handle nested lists for now\r
18053                                 if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {\r
18054                                         return false;\r
18055                                 }\r
18056 \r
18057                                 handleEmptyListItem();\r
18058                                 return;\r
18059                         }\r
18060 \r
18061                         // Don't split PRE tags but insert a BR instead easier when writing code samples etc\r
18062                         if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {\r
18063                                 if (!evt.shiftKey) {\r
18064                                         insertBr();\r
18065                                         return;\r
18066                                 }\r
18067                         } else {\r
18068                                 // If no root block is configured then insert a BR by default or if the shiftKey is pressed\r
18069                                 if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {\r
18070                                         insertBr();\r
18071                                         return;\r
18072                                 }\r
18073                         }\r
18074 \r
18075                         // Default block name if it's not configured\r
18076                         newBlockName = newBlockName || 'P';\r
18077 \r
18078                         // Insert new block before/after the parent block depending on caret location\r
18079                         if (isCaretAtStartOrEndOfBlock()) {\r
18080                                 // If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup\r
18081                                 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {\r
18082                                         newBlock = createNewBlock(newBlockName);\r
18083                                 } else {\r
18084                                         newBlock = createNewBlock();\r
18085                                 }\r
18086 \r
18087                                 // Split the current container block element if enter is pressed inside an empty inner block element\r
18088                                 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {\r
18089                                         // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P\r
18090                                         newBlock = dom.split(containerBlock, parentBlock);\r
18091                                 } else {\r
18092                                         dom.insertAfter(newBlock, parentBlock);\r
18093                                 }\r
18094                         } else if (isCaretAtStartOrEndOfBlock(true)) {\r
18095                                 // Insert new block before\r
18096                                 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);\r
18097                         } else {\r
18098                                 // Extract after fragment and insert it after the current block\r
18099                                 tmpRng = rng.cloneRange();\r
18100                                 tmpRng.setEndAfter(parentBlock);\r
18101                                 fragment = tmpRng.extractContents();\r
18102                                 trimLeadingLineBreaks(fragment);\r
18103                                 newBlock = fragment.firstChild;\r
18104                                 dom.insertAfter(fragment, parentBlock);\r
18105                         }\r
18106 \r
18107                         dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique\r
18108                         moveToCaretPosition(newBlock);\r
18109                         undoManager.add();\r
18110                 }\r
18111 \r
18112                 editor.onKeyDown.add(function(ed, evt) {\r
18113                         if (evt.keyCode == 13) {\r
18114                                 if (handleEnterKey(evt) !== false) {\r
18115                                         evt.preventDefault();\r
18116                                 }\r
18117                         }\r
18118                 });\r
18119         };\r
18120 })(tinymce);\r
18121 \r