]> git.mxchange.org Git - friendica.git/blob - library/tinymce/jscripts/tiny_mce/tiny_mce_src.js
reverting tinymce changes, updating smarty to 3.1.19
[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.8',\r
10 \r
11                 releaseDate : '2012-11-20',\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' && tinymce.isArray(o))\r
111                                 return true;\r
112 \r
113                         return typeof(o) == t;\r
114                 },\r
115 \r
116                 isArray: Array.isArray || function(obj) {\r
117                         return Object.prototype.toString.call(obj) === "[object Array]";\r
118                 },\r
119 \r
120                 makeMap : function(items, delim, map) {\r
121                         var i;\r
122 \r
123                         items = items || [];\r
124                         delim = delim || ',';\r
125 \r
126                         if (typeof(items) == "string")\r
127                                 items = items.split(delim);\r
128 \r
129                         map = map || {};\r
130 \r
131                         i = items.length;\r
132                         while (i--)\r
133                                 map[items[i]] = {};\r
134 \r
135                         return map;\r
136                 },\r
137 \r
138                 each : function(o, cb, s) {\r
139                         var n, l;\r
140 \r
141                         if (!o)\r
142                                 return 0;\r
143 \r
144                         s = s || o;\r
145 \r
146                         if (o.length !== undef) {\r
147                                 // Indexed arrays, needed for Safari\r
148                                 for (n=0, l = o.length; n < l; n++) {\r
149                                         if (cb.call(s, o[n], n, o) === false)\r
150                                                 return 0;\r
151                                 }\r
152                         } else {\r
153                                 // Hashtables\r
154                                 for (n in o) {\r
155                                         if (o.hasOwnProperty(n)) {\r
156                                                 if (cb.call(s, o[n], n, o) === false)\r
157                                                         return 0;\r
158                                         }\r
159                                 }\r
160                         }\r
161 \r
162                         return 1;\r
163                 },\r
164 \r
165 \r
166                 map : function(a, f) {\r
167                         var o = [];\r
168 \r
169                         tinymce.each(a, function(v) {\r
170                                 o.push(f(v));\r
171                         });\r
172 \r
173                         return o;\r
174                 },\r
175 \r
176                 grep : function(a, f) {\r
177                         var o = [];\r
178 \r
179                         tinymce.each(a, function(v) {\r
180                                 if (!f || f(v))\r
181                                         o.push(v);\r
182                         });\r
183 \r
184                         return o;\r
185                 },\r
186 \r
187                 inArray : function(a, v) {\r
188                         var i, l;\r
189 \r
190                         if (a) {\r
191                                 for (i = 0, l = a.length; i < l; i++) {\r
192                                         if (a[i] === v)\r
193                                                 return i;\r
194                                 }\r
195                         }\r
196 \r
197                         return -1;\r
198                 },\r
199 \r
200                 extend : function(obj, ext) {\r
201                         var i, l, name, args = arguments, value;\r
202 \r
203                         for (i = 1, l = args.length; i < l; i++) {\r
204                                 ext = args[i];\r
205                                 for (name in ext) {\r
206                                         if (ext.hasOwnProperty(name)) {\r
207                                                 value = ext[name];\r
208 \r
209                                                 if (value !== undef) {\r
210                                                         obj[name] = value;\r
211                                                 }\r
212                                         }\r
213                                 }\r
214                         }\r
215 \r
216                         return obj;\r
217                 },\r
218 \r
219 \r
220                 trim : function(s) {\r
221                         return (s ? '' + s : '').replace(whiteSpaceRe, '');\r
222                 },\r
223 \r
224                 create : function(s, p, root) {\r
225                         var t = this, sp, ns, cn, scn, c, de = 0;\r
226 \r
227                         // Parse : <prefix> <class>:<super class>\r
228                         s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);\r
229                         cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name\r
230 \r
231                         // Create namespace for new class\r
232                         ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);\r
233 \r
234                         // Class already exists\r
235                         if (ns[cn])\r
236                                 return;\r
237 \r
238                         // Make pure static class\r
239                         if (s[2] == 'static') {\r
240                                 ns[cn] = p;\r
241 \r
242                                 if (this.onCreate)\r
243                                         this.onCreate(s[2], s[3], ns[cn]);\r
244 \r
245                                 return;\r
246                         }\r
247 \r
248                         // Create default constructor\r
249                         if (!p[cn]) {\r
250                                 p[cn] = function() {};\r
251                                 de = 1;\r
252                         }\r
253 \r
254                         // Add constructor and methods\r
255                         ns[cn] = p[cn];\r
256                         t.extend(ns[cn].prototype, p);\r
257 \r
258                         // Extend\r
259                         if (s[5]) {\r
260                                 sp = t.resolve(s[5]).prototype;\r
261                                 scn = s[5].match(/\.(\w+)$/i)[1]; // Class name\r
262 \r
263                                 // Extend constructor\r
264                                 c = ns[cn];\r
265                                 if (de) {\r
266                                         // Add passthrough constructor\r
267                                         ns[cn] = function() {\r
268                                                 return sp[scn].apply(this, arguments);\r
269                                         };\r
270                                 } else {\r
271                                         // Add inherit constructor\r
272                                         ns[cn] = function() {\r
273                                                 this.parent = sp[scn];\r
274                                                 return c.apply(this, arguments);\r
275                                         };\r
276                                 }\r
277                                 ns[cn].prototype[cn] = ns[cn];\r
278 \r
279                                 // Add super methods\r
280                                 t.each(sp, function(f, n) {\r
281                                         ns[cn].prototype[n] = sp[n];\r
282                                 });\r
283 \r
284                                 // Add overridden methods\r
285                                 t.each(p, function(f, n) {\r
286                                         // Extend methods if needed\r
287                                         if (sp[n]) {\r
288                                                 ns[cn].prototype[n] = function() {\r
289                                                         this.parent = sp[n];\r
290                                                         return f.apply(this, arguments);\r
291                                                 };\r
292                                         } else {\r
293                                                 if (n != cn)\r
294                                                         ns[cn].prototype[n] = f;\r
295                                         }\r
296                                 });\r
297                         }\r
298 \r
299                         // Add static methods\r
300                         t.each(p['static'], function(f, n) {\r
301                                 ns[cn][n] = f;\r
302                         });\r
303 \r
304                         if (this.onCreate)\r
305                                 this.onCreate(s[2], s[3], ns[cn].prototype);\r
306                 },\r
307 \r
308                 walk : function(o, f, n, s) {\r
309                         s = s || this;\r
310 \r
311                         if (o) {\r
312                                 if (n)\r
313                                         o = o[n];\r
314 \r
315                                 tinymce.each(o, function(o, i) {\r
316                                         if (f.call(s, o, i, n) === false)\r
317                                                 return false;\r
318 \r
319                                         tinymce.walk(o, f, n, s);\r
320                                 });\r
321                         }\r
322                 },\r
323 \r
324                 createNS : function(n, o) {\r
325                         var i, v;\r
326 \r
327                         o = o || win;\r
328 \r
329                         n = n.split('.');\r
330                         for (i=0; i<n.length; i++) {\r
331                                 v = n[i];\r
332 \r
333                                 if (!o[v])\r
334                                         o[v] = {};\r
335 \r
336                                 o = o[v];\r
337                         }\r
338 \r
339                         return o;\r
340                 },\r
341 \r
342                 resolve : function(n, o) {\r
343                         var i, l;\r
344 \r
345                         o = o || win;\r
346 \r
347                         n = n.split('.');\r
348                         for (i = 0, l = n.length; i < l; i++) {\r
349                                 o = o[n[i]];\r
350 \r
351                                 if (!o)\r
352                                         break;\r
353                         }\r
354 \r
355                         return o;\r
356                 },\r
357 \r
358                 addUnload : function(f, s) {\r
359                         var t = this, unload;\r
360 \r
361                         unload = function() {\r
362                                 var li = t.unloads, o, n;\r
363 \r
364                                 if (li) {\r
365                                         // Call unload handlers\r
366                                         for (n in li) {\r
367                                                 o = li[n];\r
368 \r
369                                                 if (o && o.func)\r
370                                                         o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy\r
371                                         }\r
372 \r
373                                         // Detach unload function\r
374                                         if (win.detachEvent) {\r
375                                                 win.detachEvent('onbeforeunload', fakeUnload);\r
376                                                 win.detachEvent('onunload', unload);\r
377                                         } else if (win.removeEventListener)\r
378                                                 win.removeEventListener('unload', unload, false);\r
379 \r
380                                         // Destroy references\r
381                                         t.unloads = o = li = w = unload = 0;\r
382 \r
383                                         // Run garbarge collector on IE\r
384                                         if (win.CollectGarbage)\r
385                                                 CollectGarbage();\r
386                                 }\r
387                         };\r
388 \r
389                         function fakeUnload() {\r
390                                 var d = document;\r
391 \r
392                                 function stop() {\r
393                                         // Prevent memory leak\r
394                                         d.detachEvent('onstop', stop);\r
395 \r
396                                         // Call unload handler\r
397                                         if (unload)\r
398                                                 unload();\r
399 \r
400                                         d = 0;\r
401                                 };\r
402 \r
403                                 // Is there things still loading, then do some magic\r
404                                 if (d.readyState == 'interactive') {\r
405                                         // Fire unload when the currently loading page is stopped\r
406                                         if (d)\r
407                                                 d.attachEvent('onstop', stop);\r
408 \r
409                                         // Remove onstop listener after a while to prevent the unload function\r
410                                         // to execute if the user presses cancel in an onbeforeunload\r
411                                         // confirm dialog and then presses the browser stop button\r
412                                         win.setTimeout(function() {\r
413                                                 if (d)\r
414                                                         d.detachEvent('onstop', stop);\r
415                                         }, 0);\r
416                                 }\r
417                         };\r
418 \r
419                         f = {func : f, scope : s || this};\r
420 \r
421                         if (!t.unloads) {\r
422                                 // Attach unload handler\r
423                                 if (win.attachEvent) {\r
424                                         win.attachEvent('onunload', unload);\r
425                                         win.attachEvent('onbeforeunload', fakeUnload);\r
426                                 } else if (win.addEventListener)\r
427                                         win.addEventListener('unload', unload, false);\r
428 \r
429                                 // Setup initial unload handler array\r
430                                 t.unloads = [f];\r
431                         } else\r
432                                 t.unloads.push(f);\r
433 \r
434                         return f;\r
435                 },\r
436 \r
437                 removeUnload : function(f) {\r
438                         var u = this.unloads, r = null;\r
439 \r
440                         tinymce.each(u, function(o, i) {\r
441                                 if (o && o.func == f) {\r
442                                         u.splice(i, 1);\r
443                                         r = f;\r
444                                         return false;\r
445                                 }\r
446                         });\r
447 \r
448                         return r;\r
449                 },\r
450 \r
451                 explode : function(s, d) {\r
452                         if (!s || tinymce.is(s, 'array')) {\r
453                                 return s;\r
454                         }\r
455 \r
456                         return tinymce.map(s.split(d || ','), tinymce.trim);\r
457                 },\r
458 \r
459                 _addVer : function(u) {\r
460                         var v;\r
461 \r
462                         if (!this.query)\r
463                                 return u;\r
464 \r
465                         v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;\r
466 \r
467                         if (u.indexOf('#') == -1)\r
468                                 return u + v;\r
469 \r
470                         return u.replace('#', v + '#');\r
471                 },\r
472 \r
473                 // Fix function for IE 9 where regexps isn't working correctly\r
474                 // Todo: remove me once MS fixes the bug\r
475                 _replace : function(find, replace, str) {\r
476                         // On IE9 we have to fake $x replacement\r
477                         if (isRegExpBroken) {\r
478                                 return str.replace(find, function() {\r
479                                         var val = replace, args = arguments, i;\r
480 \r
481                                         for (i = 0; i < args.length - 2; i++) {\r
482                                                 if (args[i] === undef) {\r
483                                                         val = val.replace(new RegExp('\\$' + i, 'g'), '');\r
484                                                 } else {\r
485                                                         val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);\r
486                                                 }\r
487                                         }\r
488 \r
489                                         return val;\r
490                                 });\r
491                         }\r
492 \r
493                         return str.replace(find, replace);\r
494                 }\r
495 \r
496                 };\r
497 \r
498         // Initialize the API\r
499         tinymce._init();\r
500 \r
501         // Expose tinymce namespace to the global namespace (window)\r
502         win.tinymce = win.tinyMCE = tinymce;\r
503 \r
504         // Describe the different namespaces\r
505 \r
506         })(window);\r
507 \r
508 \r
509 \r
510 tinymce.create('tinymce.util.Dispatcher', {\r
511         scope : null,\r
512         listeners : null,\r
513         inDispatch: false,\r
514 \r
515         Dispatcher : function(scope) {\r
516                 this.scope = scope || this;\r
517                 this.listeners = [];\r
518         },\r
519 \r
520         add : function(callback, scope) {\r
521                 this.listeners.push({cb : callback, scope : scope || this.scope});\r
522 \r
523                 return callback;\r
524         },\r
525 \r
526         addToTop : function(callback, scope) {\r
527                 var self = this, listener = {cb : callback, scope : scope || self.scope};\r
528 \r
529                 // Create new listeners if addToTop is executed in a dispatch loop\r
530                 if (self.inDispatch) {\r
531                         self.listeners = [listener].concat(self.listeners);\r
532                 } else {\r
533                         self.listeners.unshift(listener);\r
534                 }\r
535 \r
536                 return callback;\r
537         },\r
538 \r
539         remove : function(callback) {\r
540                 var listeners = this.listeners, output = null;\r
541 \r
542                 tinymce.each(listeners, function(listener, i) {\r
543                         if (callback == listener.cb) {\r
544                                 output = listener;\r
545                                 listeners.splice(i, 1);\r
546                                 return false;\r
547                         }\r
548                 });\r
549 \r
550                 return output;\r
551         },\r
552 \r
553         dispatch : function() {\r
554                 var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;\r
555 \r
556                 self.inDispatch = true;\r
557                 \r
558                 // Needs to be a real loop since the listener count might change while looping\r
559                 // And this is also more efficient\r
560                 for (i = 0; i < listeners.length; i++) {\r
561                         listener = listeners[i];\r
562                         returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);\r
563 \r
564                         if (returnValue === false)\r
565                                 break;\r
566                 }\r
567 \r
568                 self.inDispatch = false;\r
569 \r
570                 return returnValue;\r
571         }\r
572 \r
573         });\r
574 \r
575 (function() {\r
576         var each = tinymce.each;\r
577 \r
578         tinymce.create('tinymce.util.URI', {\r
579                 URI : function(u, s) {\r
580                         var t = this, o, a, b, base_url;\r
581 \r
582                         // Trim whitespace\r
583                         u = tinymce.trim(u);\r
584 \r
585                         // Default settings\r
586                         s = t.settings = s || {};\r
587 \r
588                         // Strange app protocol that isn't http/https or local anchor\r
589                         // For example: mailto,skype,tel etc.\r
590                         if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {\r
591                                 t.source = u;\r
592                                 return;\r
593                         }\r
594 \r
595                         // Absolute path with no host, fake host and protocol\r
596                         if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)\r
597                                 u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;\r
598 \r
599                         // Relative path http:// or protocol relative //path\r
600                         if (!/^[\w\-]*:?\/\//.test(u)) {\r
601                                 base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;\r
602                                 u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);\r
603                         }\r
604 \r
605                         // Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)\r
606                         u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something\r
607                         u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);\r
608                         each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {\r
609                                 var s = u[i];\r
610 \r
611                                 // Zope 3 workaround, they use @@something\r
612                                 if (s)\r
613                                         s = s.replace(/\(mce_at\)/g, '@@');\r
614 \r
615                                 t[v] = s;\r
616                         });\r
617 \r
618                         b = s.base_uri;\r
619                         if (b) {\r
620                                 if (!t.protocol)\r
621                                         t.protocol = b.protocol;\r
622 \r
623                                 if (!t.userInfo)\r
624                                         t.userInfo = b.userInfo;\r
625 \r
626                                 if (!t.port && t.host === 'mce_host')\r
627                                         t.port = b.port;\r
628 \r
629                                 if (!t.host || t.host === 'mce_host')\r
630                                         t.host = b.host;\r
631 \r
632                                 t.source = '';\r
633                         }\r
634 \r
635                         //t.path = t.path || '/';\r
636                 },\r
637 \r
638                 setPath : function(p) {\r
639                         var t = this;\r
640 \r
641                         p = /^(.*?)\/?(\w+)?$/.exec(p);\r
642 \r
643                         // Update path parts\r
644                         t.path = p[0];\r
645                         t.directory = p[1];\r
646                         t.file = p[2];\r
647 \r
648                         // Rebuild source\r
649                         t.source = '';\r
650                         t.getURI();\r
651                 },\r
652 \r
653                 toRelative : function(u) {\r
654                         var t = this, o;\r
655 \r
656                         if (u === "./")\r
657                                 return u;\r
658 \r
659                         u = new tinymce.util.URI(u, {base_uri : t});\r
660 \r
661                         // Not on same domain/port or protocol\r
662                         if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)\r
663                                 return u.getURI();\r
664 \r
665                         var tu = t.getURI(), uu = u.getURI();\r
666                         \r
667                         // Allow usage of the base_uri when relative_urls = true\r
668                         if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))\r
669                                 return tu;\r
670 \r
671                         o = t.toRelPath(t.path, u.path);\r
672 \r
673                         // Add query\r
674                         if (u.query)\r
675                                 o += '?' + u.query;\r
676 \r
677                         // Add anchor\r
678                         if (u.anchor)\r
679                                 o += '#' + u.anchor;\r
680 \r
681                         return o;\r
682                 },\r
683         \r
684                 toAbsolute : function(u, nh) {\r
685                         u = new tinymce.util.URI(u, {base_uri : this});\r
686 \r
687                         return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);\r
688                 },\r
689 \r
690                 toRelPath : function(base, path) {\r
691                         var items, bp = 0, out = '', i, l;\r
692 \r
693                         // Split the paths\r
694                         base = base.substring(0, base.lastIndexOf('/'));\r
695                         base = base.split('/');\r
696                         items = path.split('/');\r
697 \r
698                         if (base.length >= items.length) {\r
699                                 for (i = 0, l = base.length; i < l; i++) {\r
700                                         if (i >= items.length || base[i] != items[i]) {\r
701                                                 bp = i + 1;\r
702                                                 break;\r
703                                         }\r
704                                 }\r
705                         }\r
706 \r
707                         if (base.length < items.length) {\r
708                                 for (i = 0, l = items.length; i < l; i++) {\r
709                                         if (i >= base.length || base[i] != items[i]) {\r
710                                                 bp = i + 1;\r
711                                                 break;\r
712                                         }\r
713                                 }\r
714                         }\r
715 \r
716                         if (bp === 1)\r
717                                 return path;\r
718 \r
719                         for (i = 0, l = base.length - (bp - 1); i < l; i++)\r
720                                 out += "../";\r
721 \r
722                         for (i = bp - 1, l = items.length; i < l; i++) {\r
723                                 if (i != bp - 1)\r
724                                         out += "/" + items[i];\r
725                                 else\r
726                                         out += items[i];\r
727                         }\r
728 \r
729                         return out;\r
730                 },\r
731 \r
732                 toAbsPath : function(base, path) {\r
733                         var i, nb = 0, o = [], tr, outPath;\r
734 \r
735                         // Split paths\r
736                         tr = /\/$/.test(path) ? '/' : '';\r
737                         base = base.split('/');\r
738                         path = path.split('/');\r
739 \r
740                         // Remove empty chunks\r
741                         each(base, function(k) {\r
742                                 if (k)\r
743                                         o.push(k);\r
744                         });\r
745 \r
746                         base = o;\r
747 \r
748                         // Merge relURLParts chunks\r
749                         for (i = path.length - 1, o = []; i >= 0; i--) {\r
750                                 // Ignore empty or .\r
751                                 if (path[i].length === 0 || path[i] === ".")\r
752                                         continue;\r
753 \r
754                                 // Is parent\r
755                                 if (path[i] === '..') {\r
756                                         nb++;\r
757                                         continue;\r
758                                 }\r
759 \r
760                                 // Move up\r
761                                 if (nb > 0) {\r
762                                         nb--;\r
763                                         continue;\r
764                                 }\r
765 \r
766                                 o.push(path[i]);\r
767                         }\r
768 \r
769                         i = base.length - nb;\r
770 \r
771                         // If /a/b/c or /\r
772                         if (i <= 0)\r
773                                 outPath = o.reverse().join('/');\r
774                         else\r
775                                 outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');\r
776 \r
777                         // Add front / if it's needed\r
778                         if (outPath.indexOf('/') !== 0)\r
779                                 outPath = '/' + outPath;\r
780 \r
781                         // Add traling / if it's needed\r
782                         if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)\r
783                                 outPath += tr;\r
784 \r
785                         return outPath;\r
786                 },\r
787 \r
788                 getURI : function(nh) {\r
789                         var s, t = this;\r
790 \r
791                         // Rebuild source\r
792                         if (!t.source || nh) {\r
793                                 s = '';\r
794 \r
795                                 if (!nh) {\r
796                                         if (t.protocol)\r
797                                                 s += t.protocol + '://';\r
798 \r
799                                         if (t.userInfo)\r
800                                                 s += t.userInfo + '@';\r
801 \r
802                                         if (t.host)\r
803                                                 s += t.host;\r
804 \r
805                                         if (t.port)\r
806                                                 s += ':' + t.port;\r
807                                 }\r
808 \r
809                                 if (t.path)\r
810                                         s += t.path;\r
811 \r
812                                 if (t.query)\r
813                                         s += '?' + t.query;\r
814 \r
815                                 if (t.anchor)\r
816                                         s += '#' + t.anchor;\r
817 \r
818                                 t.source = s;\r
819                         }\r
820 \r
821                         return t.source;\r
822                 }\r
823         });\r
824 })();\r
825 \r
826 (function() {\r
827         var each = tinymce.each;\r
828 \r
829         tinymce.create('static tinymce.util.Cookie', {\r
830                 getHash : function(n) {\r
831                         var v = this.get(n), h;\r
832 \r
833                         if (v) {\r
834                                 each(v.split('&'), function(v) {\r
835                                         v = v.split('=');\r
836                                         h = h || {};\r
837                                         h[unescape(v[0])] = unescape(v[1]);\r
838                                 });\r
839                         }\r
840 \r
841                         return h;\r
842                 },\r
843 \r
844                 setHash : function(n, v, e, p, d, s) {\r
845                         var o = '';\r
846 \r
847                         each(v, function(v, k) {\r
848                                 o += (!o ? '' : '&') + escape(k) + '=' + escape(v);\r
849                         });\r
850 \r
851                         this.set(n, o, e, p, d, s);\r
852                 },\r
853 \r
854                 get : function(n) {\r
855                         var c = document.cookie, e, p = n + "=", b;\r
856 \r
857                         // Strict mode\r
858                         if (!c)\r
859                                 return;\r
860 \r
861                         b = c.indexOf("; " + p);\r
862 \r
863                         if (b == -1) {\r
864                                 b = c.indexOf(p);\r
865 \r
866                                 if (b !== 0)\r
867                                         return null;\r
868                         } else\r
869                                 b += 2;\r
870 \r
871                         e = c.indexOf(";", b);\r
872 \r
873                         if (e == -1)\r
874                                 e = c.length;\r
875 \r
876                         return unescape(c.substring(b + p.length, e));\r
877                 },\r
878 \r
879                 set : function(n, v, e, p, d, s) {\r
880                         document.cookie = n + "=" + escape(v) +\r
881                                 ((e) ? "; expires=" + e.toGMTString() : "") +\r
882                                 ((p) ? "; path=" + escape(p) : "") +\r
883                                 ((d) ? "; domain=" + d : "") +\r
884                                 ((s) ? "; secure" : "");\r
885                 },\r
886 \r
887                 remove : function(name, path, domain) {\r
888                         var date = new Date();\r
889 \r
890                         date.setTime(date.getTime() - 1000);\r
891 \r
892                         this.set(name, '', date, path, domain);\r
893                 }\r
894         });\r
895 })();\r
896 \r
897 (function() {\r
898         function serialize(o, quote) {\r
899                 var i, v, t, name;\r
900 \r
901                 quote = quote || '"';\r
902 \r
903                 if (o == null)\r
904                         return 'null';\r
905 \r
906                 t = typeof o;\r
907 \r
908                 if (t == 'string') {\r
909                         v = '\bb\tt\nn\ff\rr\""\'\'\\\\';\r
910 \r
911                         return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {\r
912                                 // Make sure single quotes never get encoded inside double quotes for JSON compatibility\r
913                                 if (quote === '"' && a === "'")\r
914                                         return a;\r
915 \r
916                                 i = v.indexOf(b);\r
917 \r
918                                 if (i + 1)\r
919                                         return '\\' + v.charAt(i + 1);\r
920 \r
921                                 a = b.charCodeAt().toString(16);\r
922 \r
923                                 return '\\u' + '0000'.substring(a.length) + a;\r
924                         }) + quote;\r
925                 }\r
926 \r
927                 if (t == 'object') {\r
928                         if (o.hasOwnProperty && Object.prototype.toString.call(o) === '[object Array]') {\r
929                                         for (i=0, v = '['; i<o.length; i++)\r
930                                                 v += (i > 0 ? ',' : '') + serialize(o[i], quote);\r
931 \r
932                                         return v + ']';\r
933                                 }\r
934 \r
935                                 v = '{';\r
936 \r
937                                 for (name in o) {\r
938                                         if (o.hasOwnProperty(name)) {\r
939                                                 v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';\r
940                                         }\r
941                                 }\r
942 \r
943                                 return v + '}';\r
944                 }\r
945 \r
946                 return '' + o;\r
947         };\r
948 \r
949         tinymce.util.JSON = {\r
950                 serialize: serialize,\r
951 \r
952                 parse: function(s) {\r
953                         try {\r
954                                 return eval('(' + s + ')');\r
955                         } catch (ex) {\r
956                                 // Ignore\r
957                         }\r
958                 }\r
959 \r
960                 };\r
961 })();\r
962 \r
963 tinymce.create('static tinymce.util.XHR', {\r
964         send : function(o) {\r
965                 var x, t, w = window, c = 0;\r
966 \r
967                 function ready() {\r
968                         if (!o.async || x.readyState == 4 || c++ > 10000) {\r
969                                 if (o.success && c < 10000 && x.status == 200)\r
970                                         o.success.call(o.success_scope, '' + x.responseText, x, o);\r
971                                 else if (o.error)\r
972                                         o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);\r
973 \r
974                                 x = null;\r
975                         } else\r
976                                 w.setTimeout(ready, 10);\r
977                 };\r
978 \r
979                 // Default settings\r
980                 o.scope = o.scope || this;\r
981                 o.success_scope = o.success_scope || o.scope;\r
982                 o.error_scope = o.error_scope || o.scope;\r
983                 o.async = o.async === false ? false : true;\r
984                 o.data = o.data || '';\r
985 \r
986                 function get(s) {\r
987                         x = 0;\r
988 \r
989                         try {\r
990                                 x = new ActiveXObject(s);\r
991                         } catch (ex) {\r
992                         }\r
993 \r
994                         return x;\r
995                 };\r
996 \r
997                 x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');\r
998 \r
999                 if (x) {\r
1000                         if (x.overrideMimeType)\r
1001                                 x.overrideMimeType(o.content_type);\r
1002 \r
1003                         x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);\r
1004 \r
1005                         if (o.content_type)\r
1006                                 x.setRequestHeader('Content-Type', o.content_type);\r
1007 \r
1008                         x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');\r
1009 \r
1010                         x.send(o.data);\r
1011 \r
1012                         // Syncronous request\r
1013                         if (!o.async)\r
1014                                 return ready();\r
1015 \r
1016                         // Wait for response, onReadyStateChange can not be used since it leaks memory in IE\r
1017                         t = w.setTimeout(ready, 10);\r
1018                 }\r
1019         }\r
1020 });\r
1021 \r
1022 (function() {\r
1023         var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;\r
1024 \r
1025         tinymce.create('tinymce.util.JSONRequest', {\r
1026                 JSONRequest : function(s) {\r
1027                         this.settings = extend({\r
1028                         }, s);\r
1029                         this.count = 0;\r
1030                 },\r
1031 \r
1032                 send : function(o) {\r
1033                         var ecb = o.error, scb = o.success;\r
1034 \r
1035                         o = extend(this.settings, o);\r
1036 \r
1037                         o.success = function(c, x) {\r
1038                                 c = JSON.parse(c);\r
1039 \r
1040                                 if (typeof(c) == 'undefined') {\r
1041                                         c = {\r
1042                                                 error : 'JSON Parse error.'\r
1043                                         };\r
1044                                 }\r
1045 \r
1046                                 if (c.error)\r
1047                                         ecb.call(o.error_scope || o.scope, c.error, x);\r
1048                                 else\r
1049                                         scb.call(o.success_scope || o.scope, c.result);\r
1050                         };\r
1051 \r
1052                         o.error = function(ty, x) {\r
1053                                 if (ecb)\r
1054                                         ecb.call(o.error_scope || o.scope, ty, x);\r
1055                         };\r
1056 \r
1057                         o.data = JSON.serialize({\r
1058                                 id : o.id || 'c' + (this.count++),\r
1059                                 method : o.method,\r
1060                                 params : o.params\r
1061                         });\r
1062 \r
1063                         // JSON content type for Ruby on rails. Bug: #1883287\r
1064                         o.content_type = 'application/json';\r
1065 \r
1066                         XHR.send(o);\r
1067                 },\r
1068 \r
1069                 'static' : {\r
1070                         sendRPC : function(o) {\r
1071                                 return new tinymce.util.JSONRequest().send(o);\r
1072                         }\r
1073                 }\r
1074         });\r
1075 }());\r
1076 (function(tinymce){\r
1077         tinymce.VK = {\r
1078                 BACKSPACE: 8,\r
1079                 DELETE: 46,\r
1080                 DOWN: 40,\r
1081                 ENTER: 13,\r
1082                 LEFT: 37,\r
1083                 RIGHT: 39,\r
1084                 SPACEBAR: 32,\r
1085                 TAB: 9,\r
1086                 UP: 38,\r
1087 \r
1088                 modifierPressed: function (e) {\r
1089                         return e.shiftKey || e.ctrlKey || e.altKey;\r
1090                 },\r
1091 \r
1092                 metaKeyPressed: function(e) {\r
1093                         // Check if ctrl or meta key is pressed also check if alt is false for Polish users\r
1094                         return tinymce.isMac ? e.metaKey : e.ctrlKey && !e.altKey;\r
1095                 }\r
1096         };\r
1097 })(tinymce);\r
1098 \r
1099 tinymce.util.Quirks = function(editor) {\r
1100         var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection,\r
1101                 settings = editor.settings, parser = editor.parser, serializer = editor.serializer, each = tinymce.each;\r
1102 \r
1103         function setEditorCommandState(cmd, state) {\r
1104                 try {\r
1105                         editor.getDoc().execCommand(cmd, false, state);\r
1106                 } catch (ex) {\r
1107                         // Ignore\r
1108                 }\r
1109         }\r
1110 \r
1111         function getDocumentMode() {\r
1112                 var documentMode = editor.getDoc().documentMode;\r
1113 \r
1114                 return documentMode ? documentMode : 6;\r
1115         };\r
1116 \r
1117         function isDefaultPrevented(e) {\r
1118                 return e.isDefaultPrevented();\r
1119         };\r
1120 \r
1121         function cleanupStylesWhenDeleting() {\r
1122                 function removeMergedFormatSpans(isDelete) {\r
1123                         var rng, blockElm, node, clonedSpan;\r
1124 \r
1125                         rng = selection.getRng();\r
1126 \r
1127                         // Find root block\r
1128                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1129 \r
1130                         // On delete clone the root span of the next block element\r
1131                         if (isDelete) {\r
1132                                 blockElm = dom.getNext(blockElm, dom.isBlock);\r
1133                         }\r
1134 \r
1135                         // Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace\r
1136                         if (blockElm) {\r
1137                                 node = blockElm.firstChild;\r
1138 \r
1139                                 // Ignore empty text nodes\r
1140                                 while (node && node.nodeType == 3 && node.nodeValue.length === 0) {\r
1141                                         node = node.nextSibling;\r
1142                                 }\r
1143 \r
1144                                 if (node && node.nodeName === 'SPAN') {\r
1145                                         clonedSpan = node.cloneNode(false);\r
1146                                 }\r
1147                         }\r
1148 \r
1149                         each(dom.select('span', blockElm), function(span) {\r
1150                                 span.setAttribute('data-mce-mark', '1');\r
1151                         });\r
1152 \r
1153                         // Do the backspace/delete action\r
1154                         editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
1155 \r
1156                         // Find all odd apple-style-spans\r
1157                         blockElm = dom.getParent(rng.startContainer, dom.isBlock);\r
1158                         each(dom.select('span', blockElm), function(span) {\r
1159                                 var bm = selection.getBookmark();\r
1160 \r
1161                                 if (clonedSpan) {\r
1162                                         dom.replace(clonedSpan.cloneNode(false), span, true);\r
1163                                 } else if (!span.getAttribute('data-mce-mark')) {\r
1164                                         dom.remove(span, true);\r
1165                                 } else {\r
1166                                         span.removeAttribute('data-mce-mark');\r
1167                                 }\r
1168 \r
1169                                 // Restore the selection\r
1170                                 selection.moveToBookmark(bm);\r
1171                         });\r
1172                 }\r
1173 \r
1174                 editor.onKeyDown.add(function(editor, e) {\r
1175                         var isDelete;\r
1176 \r
1177                         isDelete = e.keyCode == DELETE;\r
1178                         if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {\r
1179                                 e.preventDefault();\r
1180                                 removeMergedFormatSpans(isDelete);\r
1181                         }\r
1182                 });\r
1183 \r
1184                 editor.addCommand('Delete', function() {removeMergedFormatSpans();});\r
1185         };\r
1186         \r
1187         function emptyEditorWhenDeleting() {\r
1188                 function serializeRng(rng) {\r
1189                         var body = dom.create("body");\r
1190                         var contents = rng.cloneContents();\r
1191                         body.appendChild(contents);\r
1192                         return selection.serializer.serialize(body, {format: 'html'});\r
1193                 }\r
1194 \r
1195                 function allContentsSelected(rng) {\r
1196                         var selection = serializeRng(rng);\r
1197 \r
1198                         var allRng = dom.createRng();\r
1199                         allRng.selectNode(editor.getBody());\r
1200 \r
1201                         var allSelection = serializeRng(allRng);\r
1202                         return selection === allSelection;\r
1203                 }\r
1204 \r
1205                 editor.onKeyDown.add(function(editor, e) {\r
1206                         var keyCode = e.keyCode, isCollapsed;\r
1207 \r
1208                         // Empty the editor if it's needed for example backspace at <p><b>|</b></p>\r
1209                         if (!isDefaultPrevented(e) && (keyCode == DELETE || keyCode == BACKSPACE)) {\r
1210                                 isCollapsed = editor.selection.isCollapsed();\r
1211 \r
1212                                 // Selection is collapsed but the editor isn't empty\r
1213                                 if (isCollapsed && !dom.isEmpty(editor.getBody())) {\r
1214                                         return;\r
1215                                 }\r
1216 \r
1217                                 // IE deletes all contents correctly when everything is selected\r
1218                                 if (tinymce.isIE && !isCollapsed) {\r
1219                                         return;\r
1220                                 }\r
1221 \r
1222                                 // Selection isn't collapsed but not all the contents is selected\r
1223                                 if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {\r
1224                                         return;\r
1225                                 }\r
1226 \r
1227                                 // Manually empty the editor\r
1228                                 editor.setContent('');\r
1229                                 editor.selection.setCursorLocation(editor.getBody(), 0);\r
1230                                 editor.nodeChanged();\r
1231                         }\r
1232                 });\r
1233         };\r
1234 \r
1235         function selectAll() {\r
1236                 editor.onKeyDown.add(function(editor, e) {\r
1237                         if (!isDefaultPrevented(e) && e.keyCode == 65 && VK.metaKeyPressed(e)) {\r
1238                                 e.preventDefault();\r
1239                                 editor.execCommand('SelectAll');\r
1240                         }\r
1241                 });\r
1242         };\r
1243 \r
1244         function inputMethodFocus() {\r
1245                 if (!editor.settings.content_editable) {\r
1246                         // Case 1 IME doesn't initialize if you focus the document\r
1247                         dom.bind(editor.getDoc(), 'focusin', function(e) {\r
1248                                 selection.setRng(selection.getRng());\r
1249                         });\r
1250 \r
1251                         // Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event\r
1252                         dom.bind(editor.getDoc(), 'mousedown', function(e) {\r
1253                                 if (e.target == editor.getDoc().documentElement) {\r
1254                                         editor.getWin().focus();\r
1255                                         selection.setRng(selection.getRng());\r
1256                                 }\r
1257                         });\r
1258                 }\r
1259         };\r
1260 \r
1261         function removeHrOnBackspace() {\r
1262                 editor.onKeyDown.add(function(editor, e) {\r
1263                         if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {\r
1264                                 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {\r
1265                                         var node = selection.getNode();\r
1266                                         var previousSibling = node.previousSibling;\r
1267 \r
1268                                         if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {\r
1269                                                 dom.remove(previousSibling);\r
1270                                                 tinymce.dom.Event.cancel(e);\r
1271                                         }\r
1272                                 }\r
1273                         }\r
1274                 })\r
1275         }\r
1276 \r
1277         function focusBody() {\r
1278                 // Fix for a focus bug in FF 3.x where the body element\r
1279                 // wouldn't get proper focus if the user clicked on the HTML element\r
1280                 if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4\r
1281                         editor.onMouseDown.add(function(editor, e) {\r
1282                                 if (!isDefaultPrevented(e) && e.target.nodeName === "HTML") {\r
1283                                         var body = editor.getBody();\r
1284 \r
1285                                         // Blur the body it's focused but not correctly focused\r
1286                                         body.blur();\r
1287 \r
1288                                         // Refocus the body after a little while\r
1289                                         setTimeout(function() {\r
1290                                                 body.focus();\r
1291                                         }, 0);\r
1292                                 }\r
1293                         });\r
1294                 }\r
1295         };\r
1296 \r
1297         function selectControlElements() {\r
1298                 editor.onClick.add(function(editor, e) {\r
1299                         e = e.target;\r
1300 \r
1301                         // Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250\r
1302                         // WebKit can't even do simple things like selecting an image\r
1303                         // Needs tobe the setBaseAndExtend or it will fail to select floated images\r
1304                         if (/^(IMG|HR)$/.test(e.nodeName)) {\r
1305                                 selection.getSel().setBaseAndExtent(e, 0, e, 1);\r
1306                         }\r
1307 \r
1308                         if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {\r
1309                                 selection.select(e);\r
1310                         }\r
1311 \r
1312                         editor.nodeChanged();\r
1313                 });\r
1314         };\r
1315 \r
1316         function removeStylesWhenDeletingAccrossBlockElements() {\r
1317                 function getAttributeApplyFunction() {\r
1318                         var template = dom.getAttribs(selection.getStart().cloneNode(false));\r
1319 \r
1320                         return function() {\r
1321                                 var target = selection.getStart();\r
1322 \r
1323                                 if (target !== editor.getBody()) {\r
1324                                         dom.setAttrib(target, "style", null);\r
1325 \r
1326                                         each(template, function(attr) {\r
1327                                                 target.setAttributeNode(attr.cloneNode(true));\r
1328                                         });\r
1329                                 }\r
1330                         };\r
1331                 }\r
1332 \r
1333                 function isSelectionAcrossElements() {\r
1334                         return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) != dom.getParent(selection.getEnd(), dom.isBlock);\r
1335                 }\r
1336 \r
1337                 function blockEvent(editor, e) {\r
1338                         e.preventDefault();\r
1339                         return false;\r
1340                 }\r
1341 \r
1342                 editor.onKeyPress.add(function(editor, e) {\r
1343                         var applyAttributes;\r
1344 \r
1345                         if (!isDefaultPrevented(e) && (e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {\r
1346                                 applyAttributes = getAttributeApplyFunction();\r
1347                                 editor.getDoc().execCommand('delete', false, null);\r
1348                                 applyAttributes();\r
1349                                 e.preventDefault();\r
1350                                 return false;\r
1351                         }\r
1352                 });\r
1353 \r
1354                 dom.bind(editor.getDoc(), 'cut', function(e) {\r
1355                         var applyAttributes;\r
1356 \r
1357                         if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {\r
1358                                 applyAttributes = getAttributeApplyFunction();\r
1359                                 editor.onKeyUp.addToTop(blockEvent);\r
1360 \r
1361                                 setTimeout(function() {\r
1362                                         applyAttributes();\r
1363                                         editor.onKeyUp.remove(blockEvent);\r
1364                                 }, 0);\r
1365                         }\r
1366                 });\r
1367         }\r
1368 \r
1369         function selectionChangeNodeChanged() {\r
1370                 var lastRng, selectionTimer;\r
1371 \r
1372                 dom.bind(editor.getDoc(), 'selectionchange', function() {\r
1373                         if (selectionTimer) {\r
1374                                 clearTimeout(selectionTimer);\r
1375                                 selectionTimer = 0;\r
1376                         }\r
1377 \r
1378                         selectionTimer = window.setTimeout(function() {\r
1379                                 var rng = selection.getRng();\r
1380 \r
1381                                 // Compare the ranges to see if it was a real change or not\r
1382                                 if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {\r
1383                                         editor.nodeChanged();\r
1384                                         lastRng = rng;\r
1385                                 }\r
1386                         }, 50);\r
1387                 });\r
1388         }\r
1389 \r
1390         function ensureBodyHasRoleApplication() {\r
1391                 document.body.setAttribute("role", "application");\r
1392         }\r
1393 \r
1394         function disableBackspaceIntoATable() {\r
1395                 editor.onKeyDown.add(function(editor, e) {\r
1396                         if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {\r
1397                                 if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {\r
1398                                         var previousSibling = selection.getNode().previousSibling;\r
1399                                         if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {\r
1400                                                 return tinymce.dom.Event.cancel(e);\r
1401                                         }\r
1402                                 }\r
1403                         }\r
1404                 })\r
1405         }\r
1406 \r
1407         function addNewLinesBeforeBrInPre() {\r
1408                 // IE8+ rendering mode does the right thing with BR in PRE\r
1409                 if (getDocumentMode() > 7) {\r
1410                         return;\r
1411                 }\r
1412 \r
1413                  // Enable display: none in area and add a specific class that hides all BR elements in PRE to\r
1414                  // avoid the caret from getting stuck at the BR elements while pressing the right arrow key\r
1415                 setEditorCommandState('RespectVisibilityInDesign', true);\r
1416                 editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');\r
1417                 dom.addClass(editor.getBody(), 'mceHideBrInPre');\r
1418 \r
1419                 // Adds a \n before all BR elements in PRE to get them visual\r
1420                 parser.addNodeFilter('pre', function(nodes, name) {\r
1421                         var i = nodes.length, brNodes, j, brElm, sibling;\r
1422 \r
1423                         while (i--) {\r
1424                                 brNodes = nodes[i].getAll('br');\r
1425                                 j = brNodes.length;\r
1426                                 while (j--) {\r
1427                                         brElm = brNodes[j];\r
1428 \r
1429                                         // Add \n before BR in PRE elements on older IE:s so the new lines get rendered\r
1430                                         sibling = brElm.prev;\r
1431                                         if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {\r
1432                                                 sibling.value += '\n';\r
1433                                         } else {\r
1434                                                 brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';\r
1435                                         }\r
1436                                 }\r
1437                         }\r
1438                 });\r
1439 \r
1440                 // Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible\r
1441                 serializer.addNodeFilter('pre', function(nodes, name) {\r
1442                         var i = nodes.length, brNodes, j, brElm, sibling;\r
1443 \r
1444                         while (i--) {\r
1445                                 brNodes = nodes[i].getAll('br');\r
1446                                 j = brNodes.length;\r
1447                                 while (j--) {\r
1448                                         brElm = brNodes[j];\r
1449                                         sibling = brElm.prev;\r
1450                                         if (sibling && sibling.type == 3) {\r
1451                                                 sibling.value = sibling.value.replace(/\r?\n$/, '');\r
1452                                         }\r
1453                                 }\r
1454                         }\r
1455                 });\r
1456         }\r
1457 \r
1458         function removePreSerializedStylesWhenSelectingControls() {\r
1459                 dom.bind(editor.getBody(), 'mouseup', function(e) {\r
1460                         var value, node = selection.getNode();\r
1461 \r
1462                         // Moved styles to attributes on IMG eements\r
1463                         if (node.nodeName == 'IMG') {\r
1464                                 // Convert style width to width attribute\r
1465                                 if (value = dom.getStyle(node, 'width')) {\r
1466                                         dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));\r
1467                                         dom.setStyle(node, 'width', '');\r
1468                                 }\r
1469 \r
1470                                 // Convert style height to height attribute\r
1471                                 if (value = dom.getStyle(node, 'height')) {\r
1472                                         dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));\r
1473                                         dom.setStyle(node, 'height', '');\r
1474                                 }\r
1475                         }\r
1476                 });\r
1477         }\r
1478 \r
1479         function keepInlineElementOnDeleteBackspace() {\r
1480                 editor.onKeyDown.add(function(editor, e) {\r
1481                         var isDelete, rng, container, offset, brElm, sibling, collapsed;\r
1482 \r
1483                         isDelete = e.keyCode == DELETE;\r
1484                         if (!isDefaultPrevented(e) && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {\r
1485                                 rng = selection.getRng();\r
1486                                 container = rng.startContainer;\r
1487                                 offset = rng.startOffset;\r
1488                                 collapsed = rng.collapsed;\r
1489 \r
1490                                 // Override delete if the start container is a text node and is at the beginning of text or\r
1491                                 // just before/after the last character to be deleted in collapsed mode\r
1492                                 if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {\r
1493                                         nonEmptyElements = editor.schema.getNonEmptyElements();\r
1494 \r
1495                                         // Prevent default logic since it's broken\r
1496                                         e.preventDefault();\r
1497 \r
1498                                         // Insert a BR before the text node this will prevent the containing element from being deleted/converted\r
1499                                         brElm = dom.create('br', {id: '__tmp'});\r
1500                                         container.parentNode.insertBefore(brElm, container);\r
1501 \r
1502                                         // Do the browser delete\r
1503                                         editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);\r
1504 \r
1505                                         // Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>\r
1506                                         container = selection.getRng().startContainer;\r
1507                                         sibling = container.previousSibling;\r
1508                                         if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {\r
1509                                                 dom.remove(sibling);\r
1510                                         }\r
1511 \r
1512                                         // Remove the temp element we inserted\r
1513                                         dom.remove('__tmp');\r
1514                                 }\r
1515                         }\r
1516                 });\r
1517         }\r
1518 \r
1519         function removeBlockQuoteOnBackSpace() {\r
1520                 // Add block quote deletion handler\r
1521                 editor.onKeyDown.add(function(editor, e) {\r
1522                         var rng, container, offset, root, parent;\r
1523 \r
1524                         if (isDefaultPrevented(e) || e.keyCode != VK.BACKSPACE) {\r
1525                                 return;\r
1526                         }\r
1527 \r
1528                         rng = selection.getRng();\r
1529                         container = rng.startContainer;\r
1530                         offset = rng.startOffset;\r
1531                         root = dom.getRoot();\r
1532                         parent = container;\r
1533 \r
1534                         if (!rng.collapsed || offset !== 0) {\r
1535                                 return;\r
1536                         }\r
1537 \r
1538                         while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {\r
1539                                 parent = parent.parentNode;\r
1540                         }\r
1541 \r
1542                         // Is the cursor at the beginning of a blockquote?\r
1543                         if (parent.tagName === 'BLOCKQUOTE') {\r
1544                                 // Remove the blockquote\r
1545                                 editor.formatter.toggle('blockquote', null, parent);\r
1546 \r
1547                                 // Move the caret to the beginning of container\r
1548                                 rng = dom.createRng();\r
1549                                 rng.setStart(container, 0);\r
1550                                 rng.setEnd(container, 0);\r
1551                                 selection.setRng(rng);\r
1552                         }\r
1553                 });\r
1554         };\r
1555 \r
1556         function setGeckoEditingOptions() {\r
1557                 function setOpts() {\r
1558                         editor._refreshContentEditable();\r
1559 \r
1560                         setEditorCommandState("StyleWithCSS", false);\r
1561                         setEditorCommandState("enableInlineTableEditing", false);\r
1562 \r
1563                         if (!settings.object_resizing) {\r
1564                                 setEditorCommandState("enableObjectResizing", false);\r
1565                         }\r
1566                 };\r
1567 \r
1568                 if (!settings.readonly) {\r
1569                         editor.onBeforeExecCommand.add(setOpts);\r
1570                         editor.onMouseDown.add(setOpts);\r
1571                 }\r
1572         };\r
1573 \r
1574         function addBrAfterLastLinks() {\r
1575                 function fixLinks(editor, o) {\r
1576                         each(dom.select('a'), function(node) {\r
1577                                 var parentNode = node.parentNode, root = dom.getRoot();\r
1578 \r
1579                                 if (parentNode.lastChild === node) {\r
1580                                         while (parentNode && !dom.isBlock(parentNode)) {\r
1581                                                 if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {\r
1582                                                         return;\r
1583                                                 }\r
1584 \r
1585                                                 parentNode = parentNode.parentNode;\r
1586                                         }\r
1587 \r
1588                                         dom.add(parentNode, 'br', {'data-mce-bogus' : 1});\r
1589                                 }\r
1590                         });\r
1591                 };\r
1592 \r
1593                 editor.onExecCommand.add(function(editor, cmd) {\r
1594                         if (cmd === 'CreateLink') {\r
1595                                 fixLinks(editor);\r
1596                         }\r
1597                 });\r
1598 \r
1599                 editor.onSetContent.add(selection.onSetContent.add(fixLinks));\r
1600         };\r
1601 \r
1602         function setDefaultBlockType() {\r
1603                 if (settings.forced_root_block) {\r
1604                         editor.onInit.add(function() {\r
1605                                 setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);\r
1606                         });\r
1607                 }\r
1608         }\r
1609 \r
1610         function removeGhostSelection() {\r
1611                 function repaint(sender, args) {\r
1612                         if (!sender || !args.initial) {\r
1613                                 editor.execCommand('mceRepaint');\r
1614                         }\r
1615                 };\r
1616 \r
1617                 editor.onUndo.add(repaint);\r
1618                 editor.onRedo.add(repaint);\r
1619                 editor.onSetContent.add(repaint);\r
1620         };\r
1621 \r
1622         function deleteControlItemOnBackSpace() {\r
1623                 editor.onKeyDown.add(function(editor, e) {\r
1624                         var rng;\r
1625 \r
1626                         if (!isDefaultPrevented(e) && e.keyCode == BACKSPACE) {\r
1627                                 rng = editor.getDoc().selection.createRange();\r
1628                                 if (rng && rng.item) {\r
1629                                         e.preventDefault();\r
1630                                         editor.undoManager.beforeChange();\r
1631                                         dom.remove(rng.item(0));\r
1632                                         editor.undoManager.add();\r
1633                                 }\r
1634                         }\r
1635                 });\r
1636         };\r
1637 \r
1638         function renderEmptyBlocksFix() {\r
1639                 var emptyBlocksCSS;\r
1640 \r
1641                 // IE10+\r
1642                 if (getDocumentMode() >= 10) {\r
1643                         emptyBlocksCSS = '';\r
1644                         each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {\r
1645                                 emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';\r
1646                         });\r
1647 \r
1648                         editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');\r
1649                 }\r
1650         };\r
1651 \r
1652         function fakeImageResize() {\r
1653                 var selectedElmX, selectedElmY, selectedElm, selectedElmGhost, selectedHandle, startX, startY, startW, startH, ratio,\r
1654                         resizeHandles, width, height, rootDocument = document, editableDoc = editor.getDoc();\r
1655 \r
1656                 if (!settings.object_resizing || settings.webkit_fake_resize === false) {\r
1657                         return;\r
1658                 }\r
1659 \r
1660                 // Try disabling object resizing if WebKit implements resizing in the future\r
1661                 setEditorCommandState("enableObjectResizing", false);\r
1662 \r
1663                 // Details about each resize handle how to scale etc\r
1664                 resizeHandles = {\r
1665                         // Name: x multiplier, y multiplier, delta size x, delta size y\r
1666                         n: [.5, 0, 0, -1],\r
1667                         e: [1, .5, 1, 0],\r
1668                         s: [.5, 1, 0, 1],\r
1669                         w: [0, .5, -1, 0],\r
1670                         nw: [0, 0, -1, -1],\r
1671                         ne: [1, 0, 1, -1],\r
1672                         se: [1, 1, 1, 1],\r
1673                         sw : [0, 1, -1, 1]\r
1674                 };\r
1675 \r
1676                 function resizeElement(e) {\r
1677                         var deltaX, deltaY;\r
1678 \r
1679                         // Calc new width/height\r
1680                         deltaX = e.screenX - startX;\r
1681                         deltaY = e.screenY - startY;\r
1682 \r
1683                         // Calc new size\r
1684                         width = deltaX * selectedHandle[2] + startW;\r
1685                         height = deltaY * selectedHandle[3] + startH;\r
1686 \r
1687                         // Never scale down lower than 5 pixels\r
1688                         width = width < 5 ? 5 : width;\r
1689                         height = height < 5 ? 5 : height;\r
1690 \r
1691                         // Constrain proportions when modifier key is pressed or if the nw, ne, sw, se corners are moved on an image\r
1692                         if (VK.modifierPressed(e) || (selectedElm.nodeName == "IMG" && selectedHandle[2] * selectedHandle[3] !== 0)) {\r
1693                                 width = Math.round(height / ratio);\r
1694                                 height = Math.round(width * ratio);\r
1695                         }\r
1696 \r
1697                         // Update ghost size\r
1698                         dom.setStyles(selectedElmGhost, {\r
1699                                 width: width,\r
1700                                 height: height\r
1701                         });\r
1702 \r
1703                         // Update ghost X position if needed\r
1704                         if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {\r
1705                                 dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));\r
1706                         }\r
1707 \r
1708                         // Update ghost Y position if needed\r
1709                         if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {\r
1710                                 dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));\r
1711                         }\r
1712                 }\r
1713 \r
1714                 function endResize() {\r
1715                         function setSizeProp(name, value) {\r
1716                                 if (value) {\r
1717                                         // Resize by using style or attribute\r
1718                                         if (selectedElm.style[name] || !editor.schema.isValid(selectedElm.nodeName.toLowerCase(), name)) {\r
1719                                                 dom.setStyle(selectedElm, name, value);\r
1720                                         } else {\r
1721                                                 dom.setAttrib(selectedElm, name, value);\r
1722                                         }\r
1723                                 }\r
1724                         }\r
1725 \r
1726                         // Set width/height properties\r
1727                         setSizeProp('width', width);\r
1728                         setSizeProp('height', height);\r
1729 \r
1730                         dom.unbind(editableDoc, 'mousemove', resizeElement);\r
1731                         dom.unbind(editableDoc, 'mouseup', endResize);\r
1732 \r
1733                         if (rootDocument != editableDoc) {\r
1734                                 dom.unbind(rootDocument, 'mousemove', resizeElement);\r
1735                                 dom.unbind(rootDocument, 'mouseup', endResize);\r
1736                         }\r
1737 \r
1738                         // Remove ghost and update resize handle positions\r
1739                         dom.remove(selectedElmGhost);\r
1740                         showResizeRect(selectedElm);\r
1741                 }\r
1742 \r
1743                 function showResizeRect(targetElm) {\r
1744                         var position, targetWidth, targetHeight;\r
1745 \r
1746                         hideResizeRect();\r
1747 \r
1748                         // Get position and size of target\r
1749                         position = dom.getPos(targetElm);\r
1750                         selectedElmX = position.x;\r
1751                         selectedElmY = position.y;\r
1752                         targetWidth = targetElm.offsetWidth;\r
1753                         targetHeight = targetElm.offsetHeight;\r
1754 \r
1755                         // Reset width/height if user selects a new image/table\r
1756                         if (selectedElm != targetElm) {\r
1757                                 selectedElm = targetElm;\r
1758                                 width = height = 0;\r
1759                         }\r
1760 \r
1761                         each(resizeHandles, function(handle, name) {\r
1762                                 var handleElm;\r
1763 \r
1764                                 // Get existing or render resize handle\r
1765                                 handleElm = dom.get('mceResizeHandle' + name);\r
1766                                 if (!handleElm) {\r
1767                                         handleElm = dom.add(editableDoc.documentElement, 'div', {\r
1768                                                 id: 'mceResizeHandle' + name,\r
1769                                                 'class': 'mceResizeHandle',\r
1770                                                 style: 'cursor:' + name + '-resize; margin:0; padding:0'\r
1771                                         });\r
1772 \r
1773                                         dom.bind(handleElm, 'mousedown', function(e) {\r
1774                                                 e.preventDefault();\r
1775 \r
1776                                                 endResize();\r
1777 \r
1778                                                 startX = e.screenX;\r
1779                                                 startY = e.screenY;\r
1780                                                 startW = selectedElm.clientWidth;\r
1781                                                 startH = selectedElm.clientHeight;\r
1782                                                 ratio = startH / startW;\r
1783                                                 selectedHandle = handle;\r
1784 \r
1785                                                 selectedElmGhost = selectedElm.cloneNode(true);\r
1786                                                 dom.addClass(selectedElmGhost, 'mceClonedResizable');\r
1787                                                 dom.setStyles(selectedElmGhost, {\r
1788                                                         left: selectedElmX,\r
1789                                                         top: selectedElmY,\r
1790                                                         margin: 0\r
1791                                                 });\r
1792 \r
1793                                                 editableDoc.documentElement.appendChild(selectedElmGhost);\r
1794 \r
1795                                                 dom.bind(editableDoc, 'mousemove', resizeElement);\r
1796                                                 dom.bind(editableDoc, 'mouseup', endResize);\r
1797 \r
1798                                                 if (rootDocument != editableDoc) {\r
1799                                                         dom.bind(rootDocument, 'mousemove', resizeElement);\r
1800                                                         dom.bind(rootDocument, 'mouseup', endResize);\r
1801                                                 }\r
1802                                         });\r
1803                                 } else {\r
1804                                         dom.show(handleElm);\r
1805                                 }\r
1806 \r
1807                                 // Position element\r
1808                                 dom.setStyles(handleElm, {\r
1809                                         left: (targetWidth * handle[0] + selectedElmX) - (handleElm.offsetWidth / 2),\r
1810                                         top: (targetHeight * handle[1] + selectedElmY) - (handleElm.offsetHeight / 2)\r
1811                                 });\r
1812                         });\r
1813 \r
1814                         // Only add resize rectangle on WebKit and only on images\r
1815                         if (!tinymce.isOpera && selectedElm.nodeName == "IMG") {\r
1816                                 selectedElm.setAttribute('data-mce-selected', '1');\r
1817                         }\r
1818                 }\r
1819 \r
1820                 function hideResizeRect() {\r
1821                         if (selectedElm) {\r
1822                                 selectedElm.removeAttribute('data-mce-selected');\r
1823                         }\r
1824 \r
1825                         for (var name in resizeHandles) {\r
1826                                 dom.hide('mceResizeHandle' + name);\r
1827                         }\r
1828                 }\r
1829 \r
1830                 // Add CSS for resize handles, cloned element and selected\r
1831                 editor.contentStyles.push(\r
1832                         '.mceResizeHandle {' +\r
1833                                 'position: absolute;' +\r
1834                                 'border: 1px solid black;' +\r
1835                                 'background: #FFF;' +\r
1836                                 'width: 5px;' +\r
1837                                 'height: 5px;' +\r
1838                                 'z-index: 10000' +\r
1839                         '}' +\r
1840                         '.mceResizeHandle:hover {' +\r
1841                                 'background: #000' +\r
1842                         '}' +\r
1843                         'img[data-mce-selected] {' +\r
1844                                 'outline: 1px solid black' +\r
1845                         '}' +\r
1846                         'img.mceClonedResizable, table.mceClonedResizable {' +\r
1847                                 'position: absolute;' +\r
1848                                 'outline: 1px dashed black;' +\r
1849                                 'opacity: .5;' +\r
1850                                 'z-index: 10000' +\r
1851                         '}'\r
1852                 );\r
1853 \r
1854                 function updateResizeRect() {\r
1855                         var controlElm = dom.getParent(selection.getNode(), 'table,img');\r
1856 \r
1857                         // Remove data-mce-selected from all elements since they might have been copied using Ctrl+c/v\r
1858                         each(dom.select('img[data-mce-selected]'), function(img) {\r
1859                                 img.removeAttribute('data-mce-selected');\r
1860                         });\r
1861 \r
1862                         if (controlElm) {\r
1863                                 showResizeRect(controlElm);\r
1864                         } else {\r
1865                                 hideResizeRect();\r
1866                         }\r
1867                 }\r
1868 \r
1869                 // Show/hide resize rect when image is selected\r
1870                 editor.onNodeChange.add(updateResizeRect);\r
1871 \r
1872                 // Fixes WebKit quirk where it returns IMG on getNode if caret is after last image in container\r
1873                 dom.bind(editableDoc, 'selectionchange', updateResizeRect);\r
1874 \r
1875                 // Remove the internal attribute when serializing the DOM\r
1876                 editor.serializer.addAttributeFilter('data-mce-selected', function(nodes, name) {\r
1877                         var i = nodes.length;\r
1878 \r
1879                         while (i--) {\r
1880                                 nodes[i].attr(name, null);\r
1881                         }\r
1882                 });\r
1883         }\r
1884 \r
1885         function keepNoScriptContents() {\r
1886                 if (getDocumentMode() < 9) {\r
1887                         parser.addNodeFilter('noscript', function(nodes) {\r
1888                                 var i = nodes.length, node, textNode;\r
1889 \r
1890                                 while (i--) {\r
1891                                         node = nodes[i];\r
1892                                         textNode = node.firstChild;\r
1893 \r
1894                                         if (textNode) {\r
1895                                                 node.attr('data-mce-innertext', textNode.value);\r
1896                                         }\r
1897                                 }\r
1898                         });\r
1899 \r
1900                         serializer.addNodeFilter('noscript', function(nodes) {\r
1901                                 var i = nodes.length, node, textNode, value;\r
1902 \r
1903                                 while (i--) {\r
1904                                         node = nodes[i];\r
1905                                         textNode = nodes[i].firstChild;\r
1906 \r
1907                                         if (textNode) {\r
1908                                                 textNode.value = tinymce.html.Entities.decode(textNode.value);\r
1909                                         } else {\r
1910                                                 // Old IE can't retain noscript value so an attribute is used to store it\r
1911                                                 value = node.attributes.map['data-mce-innertext'];\r
1912                                                 if (value) {\r
1913                                                         node.attr('data-mce-innertext', null);\r
1914                                                         textNode = new tinymce.html.Node('#text', 3);\r
1915                                                         textNode.value = value;\r
1916                                                         textNode.raw = true;\r
1917                                                         node.append(textNode);\r
1918                                                 }\r
1919                                         }\r
1920                                 }\r
1921                         });\r
1922                 }\r
1923         }\r
1924 \r
1925         // All browsers\r
1926         disableBackspaceIntoATable();\r
1927         removeBlockQuoteOnBackSpace();\r
1928         emptyEditorWhenDeleting();\r
1929 \r
1930         // WebKit\r
1931         if (tinymce.isWebKit) {\r
1932                 keepInlineElementOnDeleteBackspace();\r
1933                 cleanupStylesWhenDeleting();\r
1934                 inputMethodFocus();\r
1935                 selectControlElements();\r
1936                 setDefaultBlockType();\r
1937 \r
1938                 // iOS\r
1939                 if (tinymce.isIDevice) {\r
1940                         selectionChangeNodeChanged();\r
1941                 } else {\r
1942                         fakeImageResize();\r
1943                         selectAll();\r
1944                 }\r
1945         }\r
1946 \r
1947         // IE\r
1948         if (tinymce.isIE) {\r
1949                 removeHrOnBackspace();\r
1950                 ensureBodyHasRoleApplication();\r
1951                 addNewLinesBeforeBrInPre();\r
1952                 removePreSerializedStylesWhenSelectingControls();\r
1953                 deleteControlItemOnBackSpace();\r
1954                 renderEmptyBlocksFix();\r
1955                 keepNoScriptContents();\r
1956         }\r
1957 \r
1958         // Gecko\r
1959         if (tinymce.isGecko) {\r
1960                 removeHrOnBackspace();\r
1961                 focusBody();\r
1962                 removeStylesWhenDeletingAccrossBlockElements();\r
1963                 setGeckoEditingOptions();\r
1964                 addBrAfterLastLinks();\r
1965                 removeGhostSelection();\r
1966         }\r
1967 \r
1968         // Opera\r
1969         if (tinymce.isOpera) {\r
1970                 fakeImageResize();\r
1971         }\r
1972 };\r
1973 (function(tinymce) {\r
1974         var namedEntities, baseEntities, reverseEntities,\r
1975                 attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1976                 textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,\r
1977                 rawCharsRegExp = /[<>&\"\']/g,\r
1978                 entityRegExp = /&(#x|#)?([\w]+);/g,\r
1979                 asciiMap = {\r
1980                                 128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",\r
1981                                 135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",\r
1982                                 142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",\r
1983                                 150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",\r
1984                                 156 : "\u0153", 158 : "\u017E", 159 : "\u0178"\r
1985                 };\r
1986 \r
1987         // Raw entities\r
1988         baseEntities = {\r
1989                 '\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code\r
1990                 "'" : '&#39;',\r
1991                 '<' : '&lt;',\r
1992                 '>' : '&gt;',\r
1993                 '&' : '&amp;'\r
1994         };\r
1995 \r
1996         // Reverse lookup table for raw entities\r
1997         reverseEntities = {\r
1998                 '&lt;' : '<',\r
1999                 '&gt;' : '>',\r
2000                 '&amp;' : '&',\r
2001                 '&quot;' : '"',\r
2002                 '&apos;' : "'"\r
2003         };\r
2004 \r
2005         // Decodes text by using the browser\r
2006         function nativeDecode(text) {\r
2007                 var elm;\r
2008 \r
2009                 elm = document.createElement("div");\r
2010                 elm.innerHTML = text;\r
2011 \r
2012                 return elm.textContent || elm.innerText || text;\r
2013         };\r
2014 \r
2015         // Build a two way lookup table for the entities\r
2016         function buildEntitiesLookup(items, radix) {\r
2017                 var i, chr, entity, lookup = {};\r
2018 \r
2019                 if (items) {\r
2020                         items = items.split(',');\r
2021                         radix = radix || 10;\r
2022 \r
2023                         // Build entities lookup table\r
2024                         for (i = 0; i < items.length; i += 2) {\r
2025                                 chr = String.fromCharCode(parseInt(items[i], radix));\r
2026 \r
2027                                 // Only add non base entities\r
2028                                 if (!baseEntities[chr]) {\r
2029                                         entity = '&' + items[i + 1] + ';';\r
2030                                         lookup[chr] = entity;\r
2031                                         lookup[entity] = chr;\r
2032                                 }\r
2033                         }\r
2034 \r
2035                         return lookup;\r
2036                 }\r
2037         };\r
2038 \r
2039         // Unpack entities lookup where the numbers are in radix 32 to reduce the size\r
2040         namedEntities = buildEntitiesLookup(\r
2041                 '50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +\r
2042                 '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +\r
2043                 '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +\r
2044                 '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +\r
2045                 '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +\r
2046                 '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +\r
2047                 '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +\r
2048                 '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +\r
2049                 '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +\r
2050                 '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +\r
2051                 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +\r
2052                 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +\r
2053                 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +\r
2054                 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +\r
2055                 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +\r
2056                 '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +\r
2057                 '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +\r
2058                 '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +\r
2059                 '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +\r
2060                 '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +\r
2061                 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +\r
2062                 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +\r
2063                 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +\r
2064                 '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +\r
2065                 '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);\r
2066 \r
2067         tinymce.html = tinymce.html || {};\r
2068 \r
2069         tinymce.html.Entities = {\r
2070                 encodeRaw : function(text, attr) {\r
2071                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
2072                                 return baseEntities[chr] || chr;\r
2073                         });\r
2074                 },\r
2075 \r
2076                 encodeAllRaw : function(text) {\r
2077                         return ('' + text).replace(rawCharsRegExp, function(chr) {\r
2078                                 return baseEntities[chr] || chr;\r
2079                         });\r
2080                 },\r
2081 \r
2082                 encodeNumeric : function(text, attr) {\r
2083                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
2084                                 // Multi byte sequence convert it to a single entity\r
2085                                 if (chr.length > 1)\r
2086                                         return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';\r
2087 \r
2088                                 return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';\r
2089                         });\r
2090                 },\r
2091 \r
2092                 encodeNamed : function(text, attr, entities) {\r
2093                         entities = entities || namedEntities;\r
2094 \r
2095                         return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
2096                                 return baseEntities[chr] || entities[chr] || chr;\r
2097                         });\r
2098                 },\r
2099 \r
2100                 getEncodeFunc : function(name, entities) {\r
2101                         var Entities = tinymce.html.Entities;\r
2102 \r
2103                         entities = buildEntitiesLookup(entities) || namedEntities;\r
2104 \r
2105                         function encodeNamedAndNumeric(text, attr) {\r
2106                                 return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {\r
2107                                         return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;\r
2108                                 });\r
2109                         };\r
2110 \r
2111                         function encodeCustomNamed(text, attr) {\r
2112                                 return Entities.encodeNamed(text, attr, entities);\r
2113                         };\r
2114 \r
2115                         // Replace + with , to be compatible with previous TinyMCE versions\r
2116                         name = tinymce.makeMap(name.replace(/\+/g, ','));\r
2117 \r
2118                         // Named and numeric encoder\r
2119                         if (name.named && name.numeric)\r
2120                                 return encodeNamedAndNumeric;\r
2121 \r
2122                         // Named encoder\r
2123                         if (name.named) {\r
2124                                 // Custom names\r
2125                                 if (entities)\r
2126                                         return encodeCustomNamed;\r
2127 \r
2128                                 return Entities.encodeNamed;\r
2129                         }\r
2130 \r
2131                         // Numeric\r
2132                         if (name.numeric)\r
2133                                 return Entities.encodeNumeric;\r
2134 \r
2135                         // Raw encoder\r
2136                         return Entities.encodeRaw;\r
2137                 },\r
2138 \r
2139                 decode : function(text) {\r
2140                         return text.replace(entityRegExp, function(all, numeric, value) {\r
2141                                 if (numeric) {\r
2142                                         value = parseInt(value, numeric.length === 2 ? 16 : 10);\r
2143 \r
2144                                         // Support upper UTF\r
2145                                         if (value > 0xFFFF) {\r
2146                                                 value -= 0x10000;\r
2147 \r
2148                                                 return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));\r
2149                                         } else\r
2150                                                 return asciiMap[value] || String.fromCharCode(value);\r
2151                                 }\r
2152 \r
2153                                 return reverseEntities[all] || namedEntities[all] || nativeDecode(all);\r
2154                         });\r
2155                 }\r
2156         };\r
2157 })(tinymce);\r
2158 \r
2159 tinymce.html.Styles = function(settings, schema) {\r
2160         var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,\r
2161                 urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,\r
2162                 styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,\r
2163                 trimRightRegExp = /\s+$/,\r
2164                 urlColorRegExp = /rgb/,\r
2165                 undef, i, encodingLookup = {}, encodingItems;\r
2166 \r
2167         settings = settings || {};\r
2168 \r
2169         encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');\r
2170         for (i = 0; i < encodingItems.length; i++) {\r
2171                 encodingLookup[encodingItems[i]] = '\uFEFF' + i;\r
2172                 encodingLookup['\uFEFF' + i] = encodingItems[i];\r
2173         }\r
2174 \r
2175         function toHex(match, r, g, b) {\r
2176                 function hex(val) {\r
2177                         val = parseInt(val).toString(16);\r
2178 \r
2179                         return val.length > 1 ? val : '0' + val; // 0 -> 00\r
2180                 };\r
2181 \r
2182                 return '#' + hex(r) + hex(g) + hex(b);\r
2183         };\r
2184 \r
2185         return {\r
2186                 toHex : function(color) {\r
2187                         return color.replace(rgbRegExp, toHex);\r
2188                 },\r
2189 \r
2190                 parse : function(css) {\r
2191                         var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;\r
2192 \r
2193                         function compress(prefix, suffix) {\r
2194                                 var top, right, bottom, left;\r
2195 \r
2196                                 // Get values and check it it needs compressing\r
2197                                 top = styles[prefix + '-top' + suffix];\r
2198                                 if (!top)\r
2199                                         return;\r
2200 \r
2201                                 right = styles[prefix + '-right' + suffix];\r
2202                                 if (top != right)\r
2203                                         return;\r
2204 \r
2205                                 bottom = styles[prefix + '-bottom' + suffix];\r
2206                                 if (right != bottom)\r
2207                                         return;\r
2208 \r
2209                                 left = styles[prefix + '-left' + suffix];\r
2210                                 if (bottom != left)\r
2211                                         return;\r
2212 \r
2213                                 // Compress\r
2214                                 styles[prefix + suffix] = left;\r
2215                                 delete styles[prefix + '-top' + suffix];\r
2216                                 delete styles[prefix + '-right' + suffix];\r
2217                                 delete styles[prefix + '-bottom' + suffix];\r
2218                                 delete styles[prefix + '-left' + suffix];\r
2219                         };\r
2220 \r
2221                         function canCompress(key) {\r
2222                                 var value = styles[key], i;\r
2223 \r
2224                                 if (!value || value.indexOf(' ') < 0)\r
2225                                         return;\r
2226 \r
2227                                 value = value.split(' ');\r
2228                                 i = value.length;\r
2229                                 while (i--) {\r
2230                                         if (value[i] !== value[0])\r
2231                                                 return false;\r
2232                                 }\r
2233 \r
2234                                 styles[key] = value[0];\r
2235 \r
2236                                 return true;\r
2237                         };\r
2238 \r
2239                         function compress2(target, a, b, c) {\r
2240                                 if (!canCompress(a))\r
2241                                         return;\r
2242 \r
2243                                 if (!canCompress(b))\r
2244                                         return;\r
2245 \r
2246                                 if (!canCompress(c))\r
2247                                         return;\r
2248 \r
2249                                 // Compress\r
2250                                 styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];\r
2251                                 delete styles[a];\r
2252                                 delete styles[b];\r
2253                                 delete styles[c];\r
2254                         };\r
2255 \r
2256                         // Encodes the specified string by replacing all \" \' ; : with _<num>\r
2257                         function encode(str) {\r
2258                                 isEncoded = true;\r
2259 \r
2260                                 return encodingLookup[str];\r
2261                         };\r
2262 \r
2263                         // Decodes the specified string by replacing all _<num> with it's original value \" \' etc\r
2264                         // It will also decode the \" \' if keep_slashes is set to fale or omitted\r
2265                         function decode(str, keep_slashes) {\r
2266                                 if (isEncoded) {\r
2267                                         str = str.replace(/\uFEFF[0-9]/g, function(str) {\r
2268                                                 return encodingLookup[str];\r
2269                                         });\r
2270                                 }\r
2271 \r
2272                                 if (!keep_slashes)\r
2273                                         str = str.replace(/\\([\'\";:])/g, "$1");\r
2274 \r
2275                                 return str;\r
2276                         };\r
2277 \r
2278                         function processUrl(match, url, url2, url3, str, str2) {\r
2279                                 str = str || str2;\r
2280 \r
2281                                 if (str) {\r
2282                                         str = decode(str);\r
2283 \r
2284                                         // Force strings into single quote format\r
2285                                         return "'" + str.replace(/\'/g, "\\'") + "'";\r
2286                                 }\r
2287 \r
2288                                 url = decode(url || url2 || url3);\r
2289 \r
2290                                 // Convert the URL to relative/absolute depending on config\r
2291                                 if (urlConverter)\r
2292                                         url = urlConverter.call(urlConverterScope, url, 'style');\r
2293 \r
2294                                 // Output new URL format\r
2295                                 return "url('" + url.replace(/\'/g, "\\'") + "')";\r
2296                         };\r
2297 \r
2298                         if (css) {\r
2299                                 // Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing\r
2300                                 css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {\r
2301                                         return str.replace(/[;:]/g, encode);\r
2302                                 });\r
2303 \r
2304                                 // Parse styles\r
2305                                 while (matches = styleRegExp.exec(css)) {\r
2306                                         name = matches[1].replace(trimRightRegExp, '').toLowerCase();\r
2307                                         value = matches[2].replace(trimRightRegExp, '');\r
2308 \r
2309                                         if (name && value.length > 0) {\r
2310                                                 // Opera will produce 700 instead of bold in their style values\r
2311                                                 if (name === 'font-weight' && value === '700')\r
2312                                                         value = 'bold';\r
2313                                                 else if (name === 'color' || name === 'background-color') // Lowercase colors like RED\r
2314                                                         value = value.toLowerCase();            \r
2315 \r
2316                                                 // Convert RGB colors to HEX\r
2317                                                 value = value.replace(rgbRegExp, toHex);\r
2318 \r
2319                                                 // Convert URLs and force them into url('value') format\r
2320                                                 value = value.replace(urlOrStrRegExp, processUrl);\r
2321                                                 styles[name] = isEncoded ? decode(value, true) : value;\r
2322                                         }\r
2323 \r
2324                                         styleRegExp.lastIndex = matches.index + matches[0].length;\r
2325                                 }\r
2326 \r
2327                                 // Compress the styles to reduce it's size for example IE will expand styles\r
2328                                 compress("border", "");\r
2329                                 compress("border", "-width");\r
2330                                 compress("border", "-color");\r
2331                                 compress("border", "-style");\r
2332                                 compress("padding", "");\r
2333                                 compress("margin", "");\r
2334                                 compress2('border', 'border-width', 'border-style', 'border-color');\r
2335 \r
2336                                 // Remove pointless border, IE produces these\r
2337                                 if (styles.border === 'medium none')\r
2338                                         delete styles.border;\r
2339                         }\r
2340 \r
2341                         return styles;\r
2342                 },\r
2343 \r
2344                 serialize : function(styles, element_name) {\r
2345                         var css = '', name, value;\r
2346 \r
2347                         function serializeStyles(name) {\r
2348                                 var styleList, i, l, value;\r
2349 \r
2350                                 styleList = schema.styles[name];\r
2351                                 if (styleList) {\r
2352                                         for (i = 0, l = styleList.length; i < l; i++) {\r
2353                                                 name = styleList[i];\r
2354                                                 value = styles[name];\r
2355 \r
2356                                                 if (value !== undef && value.length > 0)\r
2357                                                         css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
2358                                         }\r
2359                                 }\r
2360                         };\r
2361 \r
2362                         // Serialize styles according to schema\r
2363                         if (element_name && schema && schema.styles) {\r
2364                                 // Serialize global styles and element specific styles\r
2365                                 serializeStyles('*');\r
2366                                 serializeStyles(element_name);\r
2367                         } else {\r
2368                                 // Output the styles in the order they are inside the object\r
2369                                 for (name in styles) {\r
2370                                         value = styles[name];\r
2371 \r
2372                                         if (value !== undef && value.length > 0)\r
2373                                                 css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';\r
2374                                 }\r
2375                         }\r
2376 \r
2377                         return css;\r
2378                 }\r
2379         };\r
2380 };\r
2381 \r
2382 (function(tinymce) {\r
2383         var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;\r
2384 \r
2385         function split(str, delim) {\r
2386                 return str.split(delim || ',');\r
2387         };\r
2388 \r
2389         function unpack(lookup, data) {\r
2390                 var key, elements = {};\r
2391 \r
2392                 function replace(value) {\r
2393                         return value.replace(/[A-Z]+/g, function(key) {\r
2394                                 return replace(lookup[key]);\r
2395                         });\r
2396                 };\r
2397 \r
2398                 // Unpack lookup\r
2399                 for (key in lookup) {\r
2400                         if (lookup.hasOwnProperty(key))\r
2401                                 lookup[key] = replace(lookup[key]);\r
2402                 }\r
2403 \r
2404                 // Unpack and parse data into object map\r
2405                 replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {\r
2406                         attributes = split(attributes, '|');\r
2407 \r
2408                         elements[name] = {\r
2409                                 attributes : makeMap(attributes),\r
2410                                 attributesOrder : attributes,\r
2411                                 children : makeMap(children, '|', {'#comment' : {}})\r
2412                         }\r
2413                 });\r
2414 \r
2415                 return elements;\r
2416         };\r
2417 \r
2418         function getHTML5() {\r
2419                 var html5 = mapCache.html5;\r
2420 \r
2421                 if (!html5) {\r
2422                         html5 = mapCache.html5 = unpack({\r
2423                                         A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',\r
2424                                         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|' +\r
2425                                                 'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',\r
2426                                         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|' +\r
2427                                                 '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|' +\r
2428                                                 'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'\r
2429                                 }, 'html[A|manifest][body|head]' +\r
2430                                         'head[A][base|command|link|meta|noscript|script|style|title]' +\r
2431                                         'title[A][#]' +\r
2432                                         'base[A|href|target][]' +\r
2433                                         'link[A|href|rel|media|type|sizes][]' +\r
2434                                         'meta[A|http-equiv|name|content|charset][]' +\r
2435                                         'style[A|type|media|scoped][#]' +\r
2436                                         'script[A|charset|type|src|defer|async][#]' +\r
2437                                         'noscript[A][C]' +\r
2438                                         'body[A][C]' +\r
2439                                         'section[A][C]' +\r
2440                                         'nav[A][C]' +\r
2441                                         'article[A][C]' +\r
2442                                         'aside[A][C]' +\r
2443                                         'h1[A][B]' +\r
2444                                         'h2[A][B]' +\r
2445                                         'h3[A][B]' +\r
2446                                         'h4[A][B]' +\r
2447                                         'h5[A][B]' +\r
2448                                         'h6[A][B]' +\r
2449                                         'hgroup[A][h1|h2|h3|h4|h5|h6]' +\r
2450                                         'header[A][C]' +\r
2451                                         'footer[A][C]' +\r
2452                                         'address[A][C]' +\r
2453                                         'p[A][B]' +\r
2454                                         'br[A][]' +\r
2455                                         'pre[A][B]' +\r
2456                                         'dialog[A][dd|dt]' +\r
2457                                         'blockquote[A|cite][C]' +\r
2458                                         'ol[A|start|reversed][li]' +\r
2459                                         'ul[A][li]' +\r
2460                                         'li[A|value][C]' +\r
2461                                         'dl[A][dd|dt]' +\r
2462                                         'dt[A][B]' +\r
2463                                         'dd[A][C]' +\r
2464                                         'a[A|href|target|ping|rel|media|type][B]' +\r
2465                                         'em[A][B]' +\r
2466                                         'strong[A][B]' +\r
2467                                         'small[A][B]' +\r
2468                                         'cite[A][B]' +\r
2469                                         'q[A|cite][B]' +\r
2470                                         'dfn[A][B]' +\r
2471                                         'abbr[A][B]' +\r
2472                                         'code[A][B]' +\r
2473                                         'var[A][B]' +\r
2474                                         'samp[A][B]' +\r
2475                                         'kbd[A][B]' +\r
2476                                         'sub[A][B]' +\r
2477                                         'sup[A][B]' +\r
2478                                         'i[A][B]' +\r
2479                                         'b[A][B]' +\r
2480                                         'mark[A][B]' +\r
2481                                         'progress[A|value|max][B]' +\r
2482                                         'meter[A|value|min|max|low|high|optimum][B]' +\r
2483                                         'time[A|datetime][B]' +\r
2484                                         'ruby[A][B|rt|rp]' +\r
2485                                         'rt[A][B]' +\r
2486                                         'rp[A][B]' +\r
2487                                         'bdo[A][B]' +\r
2488                                         'span[A][B]' +\r
2489                                         'ins[A|cite|datetime][B]' +\r
2490                                         'del[A|cite|datetime][B]' +\r
2491                                         'figure[A][C|legend|figcaption]' +\r
2492                                         'figcaption[A][C]' +\r
2493                                         'img[A|alt|src|height|width|usemap|ismap][]' +\r
2494                                         'iframe[A|name|src|height|width|sandbox|seamless][]' +\r
2495                                         'embed[A|src|height|width|type][]' +\r
2496                                         'object[A|data|type|height|width|usemap|name|form|classid][param]' +\r
2497                                         'param[A|name|value][]' +\r
2498                                         'details[A|open][C|legend]' +\r
2499                                         'command[A|type|label|icon|disabled|checked|radiogroup][]' +\r
2500                                         'menu[A|type|label][C|li]' +\r
2501                                         'legend[A][C|B]' +\r
2502                                         'div[A][C]' +\r
2503                                         'source[A|src|type|media][]' +\r
2504                                         'audio[A|src|autobuffer|autoplay|loop|controls][source]' +\r
2505                                         'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +\r
2506                                         'hr[A][]' +\r
2507                                         'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +\r
2508                                         'fieldset[A|disabled|form|name][C|legend]' +\r
2509                                         'label[A|form|for][B]' +\r
2510                                         'input[A|type|accept|alt|autocomplete|autofocus|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +\r
2511                                                 'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +\r
2512                                         'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +\r
2513                                         'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +\r
2514                                         'datalist[A][B|option]' +\r
2515                                         'optgroup[A|disabled|label][option]' +\r
2516                                         'option[A|disabled|selected|label|value][]' +\r
2517                                         'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +\r
2518                                         'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +\r
2519                                         'output[A|for|form|name][B]' +\r
2520                                         'canvas[A|width|height][]' +\r
2521                                         'map[A|name][B|C]' +\r
2522                                         'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +\r
2523                                         'mathml[A][]' +\r
2524                                         'svg[A][]' +\r
2525                                         'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +\r
2526                                         'caption[A][C]' +\r
2527                                         'colgroup[A|span][col]' +\r
2528                                         'col[A|span][]' +\r
2529                                         'thead[A][tr]' +\r
2530                                         'tfoot[A][tr]' +\r
2531                                         'tbody[A][tr]' +\r
2532                                         'tr[A][th|td]' +\r
2533                                         'th[A|headers|rowspan|colspan|scope][B]' +\r
2534                                         'td[A|headers|rowspan|colspan][C]' +\r
2535                                         'wbr[A][]'\r
2536                         );\r
2537                 }\r
2538 \r
2539                 return html5;\r
2540         };\r
2541 \r
2542         function getHTML4() {\r
2543                 var html4 = mapCache.html4;\r
2544 \r
2545                 if (!html4) {\r
2546                         // This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size\r
2547                         html4 = mapCache.html4 = unpack({\r
2548                                 Z : 'H|K|N|O|P',\r
2549                                 Y : 'X|form|R|Q',\r
2550                                 ZG : 'E|span|width|align|char|charoff|valign',\r
2551                                 X : 'p|T|div|U|W|isindex|fieldset|table',\r
2552                                 ZF : 'E|align|char|charoff|valign',\r
2553                                 W : 'pre|hr|blockquote|address|center|noframes',\r
2554                                 ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',\r
2555                                 ZD : '[E][S]',\r
2556                                 U : 'ul|ol|dl|menu|dir',\r
2557                                 ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',\r
2558                                 T : 'h1|h2|h3|h4|h5|h6',\r
2559                                 ZB : 'X|S|Q',\r
2560                                 S : 'R|P',\r
2561                                 ZA : 'a|G|J|M|O|P',\r
2562                                 R : 'a|H|K|N|O',\r
2563                                 Q : 'noscript|P',\r
2564                                 P : 'ins|del|script',\r
2565                                 O : 'input|select|textarea|label|button',\r
2566                                 N : 'M|L',\r
2567                                 M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',\r
2568                                 L : 'sub|sup',\r
2569                                 K : 'J|I',\r
2570                                 J : 'tt|i|b|u|s|strike',\r
2571                                 I : 'big|small|font|basefont',\r
2572                                 H : 'G|F',\r
2573                                 G : 'br|span|bdo',\r
2574                                 F : 'object|applet|img|map|iframe',\r
2575                                 E : 'A|B|C',\r
2576                                 D : 'accesskey|tabindex|onfocus|onblur',\r
2577                                 C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',\r
2578                                 B : 'lang|xml:lang|dir',\r
2579                                 A : 'id|class|style|title'\r
2580                         }, 'script[id|charset|type|language|src|defer|xml:space][]' + \r
2581                                 'style[B|id|type|media|title|xml:space][]' + \r
2582                                 'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + \r
2583                                 'param[id|name|value|valuetype|type][]' + \r
2584                                 'p[E|align][#|S]' + \r
2585                                 'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + \r
2586                                 'br[A|clear][]' + \r
2587                                 'span[E][#|S]' + \r
2588                                 'bdo[A|C|B][#|S]' + \r
2589                                 'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + \r
2590                                 'h1[E|align][#|S]' + \r
2591                                 'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + \r
2592                                 'map[B|C|A|name][X|form|Q|area]' + \r
2593                                 'h2[E|align][#|S]' + \r
2594                                 'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + \r
2595                                 'h3[E|align][#|S]' + \r
2596                                 'tt[E][#|S]' + \r
2597                                 'i[E][#|S]' + \r
2598                                 'b[E][#|S]' + \r
2599                                 'u[E][#|S]' + \r
2600                                 's[E][#|S]' + \r
2601                                 'strike[E][#|S]' + \r
2602                                 'big[E][#|S]' + \r
2603                                 'small[E][#|S]' + \r
2604                                 'font[A|B|size|color|face][#|S]' + \r
2605                                 'basefont[id|size|color|face][]' + \r
2606                                 'em[E][#|S]' + \r
2607                                 'strong[E][#|S]' + \r
2608                                 'dfn[E][#|S]' + \r
2609                                 'code[E][#|S]' + \r
2610                                 'q[E|cite][#|S]' + \r
2611                                 'samp[E][#|S]' + \r
2612                                 'kbd[E][#|S]' + \r
2613                                 'var[E][#|S]' + \r
2614                                 'cite[E][#|S]' + \r
2615                                 'abbr[E][#|S]' + \r
2616                                 'acronym[E][#|S]' + \r
2617                                 'sub[E][#|S]' + \r
2618                                 'sup[E][#|S]' + \r
2619                                 'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + \r
2620                                 'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + \r
2621                                 'optgroup[E|disabled|label][option]' + \r
2622                                 'option[E|selected|disabled|label|value][]' + \r
2623                                 'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + \r
2624                                 'label[E|for|accesskey|onfocus|onblur][#|S]' + \r
2625                                 'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + \r
2626                                 'h4[E|align][#|S]' + \r
2627                                 'ins[E|cite|datetime][#|Y]' + \r
2628                                 'h5[E|align][#|S]' + \r
2629                                 'del[E|cite|datetime][#|Y]' + \r
2630                                 'h6[E|align][#|S]' + \r
2631                                 'div[E|align][#|Y]' + \r
2632                                 'ul[E|type|compact][li]' + \r
2633                                 'li[E|type|value][#|Y]' + \r
2634                                 'ol[E|type|compact|start][li]' + \r
2635                                 'dl[E|compact][dt|dd]' + \r
2636                                 'dt[E][#|S]' + \r
2637                                 'dd[E][#|Y]' + \r
2638                                 'menu[E|compact][li]' + \r
2639                                 'dir[E|compact][li]' + \r
2640                                 'pre[E|width|xml:space][#|ZA]' + \r
2641                                 'hr[E|align|noshade|size|width][]' + \r
2642                                 'blockquote[E|cite][#|Y]' + \r
2643                                 'address[E][#|S|p]' + \r
2644                                 'center[E][#|Y]' + \r
2645                                 'noframes[E][#|Y]' + \r
2646                                 'isindex[A|B|prompt][]' + \r
2647                                 'fieldset[E][#|legend|Y]' + \r
2648                                 'legend[E|accesskey|align][#|S]' + \r
2649                                 'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + \r
2650                                 'caption[E|align][#|S]' + \r
2651                                 'col[ZG][]' + \r
2652                                 'colgroup[ZG][col]' + \r
2653                                 'thead[ZF][tr]' + \r
2654                                 'tr[ZF|bgcolor][th|td]' + \r
2655                                 'th[E|ZE][#|Y]' + \r
2656                                 'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + \r
2657                                 'noscript[E][#|Y]' + \r
2658                                 'td[E|ZE][#|Y]' + \r
2659                                 'tfoot[ZF][tr]' + \r
2660                                 'tbody[ZF][tr]' + \r
2661                                 'area[E|D|shape|coords|href|nohref|alt|target][]' + \r
2662                                 'base[id|href|target][]' + \r
2663                                 'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'\r
2664                         );\r
2665                 }\r
2666 \r
2667                 return html4;\r
2668         };\r
2669 \r
2670         tinymce.html.Schema = function(settings) {\r
2671                 var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;\r
2672                 var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};\r
2673 \r
2674                 // Creates an lookup table map object for the specified option or the default value\r
2675                 function createLookupTable(option, default_value, extend) {\r
2676                         var value = settings[option];\r
2677 \r
2678                         if (!value) {\r
2679                                 // Get cached default map or make it if needed\r
2680                                 value = mapCache[option];\r
2681 \r
2682                                 if (!value) {\r
2683                                         value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));\r
2684                                         value = tinymce.extend(value, extend);\r
2685 \r
2686                                         mapCache[option] = value;\r
2687                                 }\r
2688                         } else {\r
2689                                 // Create custom map\r
2690                                 value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));\r
2691                         }\r
2692 \r
2693                         return value;\r
2694                 };\r
2695 \r
2696                 settings = settings || {};\r
2697                 schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();\r
2698 \r
2699                 // Allow all elements and attributes if verify_html is set to false\r
2700                 if (settings.verify_html === false)\r
2701                         settings.valid_elements = '*[*]';\r
2702 \r
2703                 // Build styles list\r
2704                 if (settings.valid_styles) {\r
2705                         validStyles = {};\r
2706 \r
2707                         // Convert styles into a rule list\r
2708                         each(settings.valid_styles, function(value, key) {\r
2709                                 validStyles[key] = tinymce.explode(value);\r
2710                         });\r
2711                 }\r
2712 \r
2713                 // Setup map objects\r
2714                 whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea');\r
2715                 selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');\r
2716                 shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');\r
2717                 boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');\r
2718                 nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);\r
2719                 textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' + \r
2720                                                 'blockquote center dir fieldset header footer article section hgroup aside nav figure');\r
2721                 blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + \r
2722                                                 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option datalist select optgroup', textBlockElementsMap);\r
2723 \r
2724                 // Converts a wildcard expression string to a regexp for example *a will become /.*a/.\r
2725                 function patternToRegExp(str) {\r
2726                         return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');\r
2727                 };\r
2728 \r
2729                 // Parses the specified valid_elements string and adds to the current rules\r
2730                 // This function is a bit hard to read since it's heavily optimized for speed\r
2731                 function addValidElements(valid_elements) {\r
2732                         var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,\r
2733                                 prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,\r
2734                                 elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,\r
2735                                 attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,\r
2736                                 hasPatternsRegExp = /[*?+]/;\r
2737 \r
2738                         if (valid_elements) {\r
2739                                 // Split valid elements into an array with rules\r
2740                                 valid_elements = split(valid_elements);\r
2741 \r
2742                                 if (elements['@']) {\r
2743                                         globalAttributes = elements['@'].attributes;\r
2744                                         globalAttributesOrder = elements['@'].attributesOrder;\r
2745                                 }\r
2746 \r
2747                                 // Loop all rules\r
2748                                 for (ei = 0, el = valid_elements.length; ei < el; ei++) {\r
2749                                         // Parse element rule\r
2750                                         matches = elementRuleRegExp.exec(valid_elements[ei]);\r
2751                                         if (matches) {\r
2752                                                 // Setup local names for matches\r
2753                                                 prefix = matches[1];\r
2754                                                 elementName = matches[2];\r
2755                                                 outputName = matches[3];\r
2756                                                 attrData = matches[4];\r
2757 \r
2758                                                 // Create new attributes and attributesOrder\r
2759                                                 attributes = {};\r
2760                                                 attributesOrder = [];\r
2761 \r
2762                                                 // Create the new element\r
2763                                                 element = {\r
2764                                                         attributes : attributes,\r
2765                                                         attributesOrder : attributesOrder\r
2766                                                 };\r
2767 \r
2768                                                 // Padd empty elements prefix\r
2769                                                 if (prefix === '#')\r
2770                                                         element.paddEmpty = true;\r
2771 \r
2772                                                 // Remove empty elements prefix\r
2773                                                 if (prefix === '-')\r
2774                                                         element.removeEmpty = true;\r
2775 \r
2776                                                 // Copy attributes from global rule into current rule\r
2777                                                 if (globalAttributes) {\r
2778                                                         for (key in globalAttributes)\r
2779                                                                 attributes[key] = globalAttributes[key];\r
2780 \r
2781                                                         attributesOrder.push.apply(attributesOrder, globalAttributesOrder);\r
2782                                                 }\r
2783 \r
2784                                                 // Attributes defined\r
2785                                                 if (attrData) {\r
2786                                                         attrData = split(attrData, '|');\r
2787                                                         for (ai = 0, al = attrData.length; ai < al; ai++) {\r
2788                                                                 matches = attrRuleRegExp.exec(attrData[ai]);\r
2789                                                                 if (matches) {\r
2790                                                                         attr = {};\r
2791                                                                         attrType = matches[1];\r
2792                                                                         attrName = matches[2].replace(/::/g, ':');\r
2793                                                                         prefix = matches[3];\r
2794                                                                         value = matches[4];\r
2795 \r
2796                                                                         // Required\r
2797                                                                         if (attrType === '!') {\r
2798                                                                                 element.attributesRequired = element.attributesRequired || [];\r
2799                                                                                 element.attributesRequired.push(attrName);\r
2800                                                                                 attr.required = true;\r
2801                                                                         }\r
2802 \r
2803                                                                         // Denied from global\r
2804                                                                         if (attrType === '-') {\r
2805                                                                                 delete attributes[attrName];\r
2806                                                                                 attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);\r
2807                                                                                 continue;\r
2808                                                                         }\r
2809 \r
2810                                                                         // Default value\r
2811                                                                         if (prefix) {\r
2812                                                                                 // Default value\r
2813                                                                                 if (prefix === '=') {\r
2814                                                                                         element.attributesDefault = element.attributesDefault || [];\r
2815                                                                                         element.attributesDefault.push({name: attrName, value: value});\r
2816                                                                                         attr.defaultValue = value;\r
2817                                                                                 }\r
2818 \r
2819                                                                                 // Forced value\r
2820                                                                                 if (prefix === ':') {\r
2821                                                                                         element.attributesForced = element.attributesForced || [];\r
2822                                                                                         element.attributesForced.push({name: attrName, value: value});\r
2823                                                                                         attr.forcedValue = value;\r
2824                                                                                 }\r
2825 \r
2826                                                                                 // Required values\r
2827                                                                                 if (prefix === '<')\r
2828                                                                                         attr.validValues = makeMap(value, '?');\r
2829                                                                         }\r
2830 \r
2831                                                                         // Check for attribute patterns\r
2832                                                                         if (hasPatternsRegExp.test(attrName)) {\r
2833                                                                                 element.attributePatterns = element.attributePatterns || [];\r
2834                                                                                 attr.pattern = patternToRegExp(attrName);\r
2835                                                                                 element.attributePatterns.push(attr);\r
2836                                                                         } else {\r
2837                                                                                 // Add attribute to order list if it doesn't already exist\r
2838                                                                                 if (!attributes[attrName])\r
2839                                                                                         attributesOrder.push(attrName);\r
2840 \r
2841                                                                                 attributes[attrName] = attr;\r
2842                                                                         }\r
2843                                                                 }\r
2844                                                         }\r
2845                                                 }\r
2846 \r
2847                                                 // Global rule, store away these for later usage\r
2848                                                 if (!globalAttributes && elementName == '@') {\r
2849                                                         globalAttributes = attributes;\r
2850                                                         globalAttributesOrder = attributesOrder;\r
2851                                                 }\r
2852 \r
2853                                                 // Handle substitute elements such as b/strong\r
2854                                                 if (outputName) {\r
2855                                                         element.outputName = elementName;\r
2856                                                         elements[outputName] = element;\r
2857                                                 }\r
2858 \r
2859                                                 // Add pattern or exact element\r
2860                                                 if (hasPatternsRegExp.test(elementName)) {\r
2861                                                         element.pattern = patternToRegExp(elementName);\r
2862                                                         patternElements.push(element);\r
2863                                                 } else\r
2864                                                         elements[elementName] = element;\r
2865                                         }\r
2866                                 }\r
2867                         }\r
2868                 };\r
2869 \r
2870                 function setValidElements(valid_elements) {\r
2871                         elements = {};\r
2872                         patternElements = [];\r
2873 \r
2874                         addValidElements(valid_elements);\r
2875 \r
2876                         each(schemaItems, function(element, name) {\r
2877                                 children[name] = element.children;\r
2878                         });\r
2879                 };\r
2880 \r
2881                 // Adds custom non HTML elements to the schema\r
2882                 function addCustomElements(custom_elements) {\r
2883                         var customElementRegExp = /^(~)?(.+)$/;\r
2884 \r
2885                         if (custom_elements) {\r
2886                                 each(split(custom_elements), function(rule) {\r
2887                                         var matches = customElementRegExp.exec(rule),\r
2888                                                 inline = matches[1] === '~',\r
2889                                                 cloneName = inline ? 'span' : 'div',\r
2890                                                 name = matches[2];\r
2891 \r
2892                                         children[name] = children[cloneName];\r
2893                                         customElementsMap[name] = cloneName;\r
2894 \r
2895                                         // If it's not marked as inline then add it to valid block elements\r
2896                                         if (!inline) {\r
2897                                                 blockElementsMap[name.toUpperCase()] = {};\r
2898                                                 blockElementsMap[name] = {};\r
2899                                         }\r
2900 \r
2901                                         // Add elements clone if needed\r
2902                                         if (!elements[name]) {\r
2903                                                 elements[name] = elements[cloneName];\r
2904                                         }\r
2905 \r
2906                                         // Add custom elements at span/div positions\r
2907                                         each(children, function(element, child) {\r
2908                                                 if (element[cloneName])\r
2909                                                         element[name] = element[cloneName];\r
2910                                         });\r
2911                                 });\r
2912                         }\r
2913                 };\r
2914 \r
2915                 // Adds valid children to the schema object\r
2916                 function addValidChildren(valid_children) {\r
2917                         var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;\r
2918 \r
2919                         if (valid_children) {\r
2920                                 each(split(valid_children), function(rule) {\r
2921                                         var matches = childRuleRegExp.exec(rule), parent, prefix;\r
2922 \r
2923                                         if (matches) {\r
2924                                                 prefix = matches[1];\r
2925 \r
2926                                                 // Add/remove items from default\r
2927                                                 if (prefix)\r
2928                                                         parent = children[matches[2]];\r
2929                                                 else\r
2930                                                         parent = children[matches[2]] = {'#comment' : {}};\r
2931 \r
2932                                                 parent = children[matches[2]];\r
2933 \r
2934                                                 each(split(matches[3], '|'), function(child) {\r
2935                                                         if (prefix === '-')\r
2936                                                                 delete parent[child];\r
2937                                                         else\r
2938                                                                 parent[child] = {};\r
2939                                                 });\r
2940                                         }\r
2941                                 });\r
2942                         }\r
2943                 };\r
2944 \r
2945                 function getElementRule(name) {\r
2946                         var element = elements[name], i;\r
2947 \r
2948                         // Exact match found\r
2949                         if (element)\r
2950                                 return element;\r
2951 \r
2952                         // No exact match then try the patterns\r
2953                         i = patternElements.length;\r
2954                         while (i--) {\r
2955                                 element = patternElements[i];\r
2956 \r
2957                                 if (element.pattern.test(name))\r
2958                                         return element;\r
2959                         }\r
2960                 };\r
2961 \r
2962                 if (!settings.valid_elements) {\r
2963                         // No valid elements defined then clone the elements from the schema spec\r
2964                         each(schemaItems, function(element, name) {\r
2965                                 elements[name] = {\r
2966                                         attributes : element.attributes,\r
2967                                         attributesOrder : element.attributesOrder\r
2968                                 };\r
2969 \r
2970                                 children[name] = element.children;\r
2971                         });\r
2972 \r
2973                         // Switch these on HTML4\r
2974                         if (settings.schema != "html5") {\r
2975                                 each(split('strong/b,em/i'), function(item) {\r
2976                                         item = split(item, '/');\r
2977                                         elements[item[1]].outputName = item[0];\r
2978                                 });\r
2979                         }\r
2980 \r
2981                         // Add default alt attribute for images\r
2982                         elements.img.attributesDefault = [{name: 'alt', value: ''}];\r
2983 \r
2984                         // Remove these if they are empty by default\r
2985                         each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {\r
2986                                 if (elements[name]) {\r
2987                                         elements[name].removeEmpty = true;\r
2988                                 }\r
2989                         });\r
2990 \r
2991                         // Padd these by default\r
2992                         each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {\r
2993                                 elements[name].paddEmpty = true;\r
2994                         });\r
2995                 } else\r
2996                         setValidElements(settings.valid_elements);\r
2997 \r
2998                 addCustomElements(settings.custom_elements);\r
2999                 addValidChildren(settings.valid_children);\r
3000                 addValidElements(settings.extended_valid_elements);\r
3001 \r
3002                 // Todo: Remove this when we fix list handling to be valid\r
3003                 addValidChildren('+ol[ul|ol],+ul[ul|ol]');\r
3004 \r
3005                 // Delete invalid elements\r
3006                 if (settings.invalid_elements) {\r
3007                         tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {\r
3008                                 if (elements[item])\r
3009                                         delete elements[item];\r
3010                         });\r
3011                 }\r
3012 \r
3013                 // If the user didn't allow span only allow internal spans\r
3014                 if (!getElementRule('span'))\r
3015                         addValidElements('span[!data-mce-type|*]');\r
3016 \r
3017                 self.children = children;\r
3018 \r
3019                 self.styles = validStyles;\r
3020 \r
3021                 self.getBoolAttrs = function() {\r
3022                         return boolAttrMap;\r
3023                 };\r
3024 \r
3025                 self.getBlockElements = function() {\r
3026                         return blockElementsMap;\r
3027                 };\r
3028 \r
3029                 self.getTextBlockElements = function() {\r
3030                         return textBlockElementsMap;\r
3031                 };\r
3032 \r
3033                 self.getShortEndedElements = function() {\r
3034                         return shortEndedElementsMap;\r
3035                 };\r
3036 \r
3037                 self.getSelfClosingElements = function() {\r
3038                         return selfClosingElementsMap;\r
3039                 };\r
3040 \r
3041                 self.getNonEmptyElements = function() {\r
3042                         return nonEmptyElementsMap;\r
3043                 };\r
3044 \r
3045                 self.getWhiteSpaceElements = function() {\r
3046                         return whiteSpaceElementsMap;\r
3047                 };\r
3048 \r
3049                 self.isValidChild = function(name, child) {\r
3050                         var parent = children[name];\r
3051 \r
3052                         return !!(parent && parent[child]);\r
3053                 };\r
3054 \r
3055                 self.isValid = function(name, attr) {\r
3056                         var attrPatterns, i, rule = getElementRule(name);\r
3057 \r
3058                         // Check if it's a valid element\r
3059                         if (rule) {\r
3060                                 if (attr) {\r
3061                                         // Check if attribute name exists\r
3062                                         if (rule.attributes[attr]) {\r
3063                                                 return true;\r
3064                                         }\r
3065 \r
3066                                         // Check if attribute matches a regexp pattern\r
3067                                         attrPatterns = rule.attributePatterns;\r
3068                                         if (attrPatterns) {\r
3069                                                 i = attrPatterns.length;\r
3070                                                 while (i--) {\r
3071                                                         if (attrPatterns[i].pattern.test(name)) {\r
3072                                                                 return true;\r
3073                                                         }\r
3074                                                 }\r
3075                                         }\r
3076                                 } else {\r
3077                                         return true;\r
3078                                 }\r
3079                         }\r
3080 \r
3081                         // No match\r
3082                         return false;\r
3083                 };\r
3084                 \r
3085                 self.getElementRule = getElementRule;\r
3086 \r
3087                 self.getCustomElements = function() {\r
3088                         return customElementsMap;\r
3089                 };\r
3090 \r
3091                 self.addValidElements = addValidElements;\r
3092 \r
3093                 self.setValidElements = setValidElements;\r
3094 \r
3095                 self.addCustomElements = addCustomElements;\r
3096 \r
3097                 self.addValidChildren = addValidChildren;\r
3098 \r
3099                 self.elements = elements;\r
3100         };\r
3101 })(tinymce);\r
3102 \r
3103 (function(tinymce) {\r
3104         tinymce.html.SaxParser = function(settings, schema) {\r
3105                 var self = this, noop = function() {};\r
3106 \r
3107                 settings = settings || {};\r
3108                 self.schema = schema = schema || new tinymce.html.Schema();\r
3109 \r
3110                 if (settings.fix_self_closing !== false)\r
3111                         settings.fix_self_closing = true;\r
3112 \r
3113                 // Add handler functions from settings and setup default handlers\r
3114                 tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {\r
3115                         if (name)\r
3116                                 self[name] = settings[name] || noop;\r
3117                 });\r
3118 \r
3119                 self.parse = function(html) {\r
3120                         var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,\r
3121                                 shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,\r
3122                                 validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,\r
3123                                 tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;\r
3124 \r
3125                         function processEndTag(name) {\r
3126                                 var pos, i;\r
3127 \r
3128                                 // Find position of parent of the same type\r
3129                                 pos = stack.length;\r
3130                                 while (pos--) {\r
3131                                         if (stack[pos].name === name)\r
3132                                                 break;                                          \r
3133                                 }\r
3134 \r
3135                                 // Found parent\r
3136                                 if (pos >= 0) {\r
3137                                         // Close all the open elements\r
3138                                         for (i = stack.length - 1; i >= pos; i--) {\r
3139                                                 name = stack[i];\r
3140 \r
3141                                                 if (name.valid)\r
3142                                                         self.end(name.name);\r
3143                                         }\r
3144 \r
3145                                         // Remove the open elements from the stack\r
3146                                         stack.length = pos;\r
3147                                 }\r
3148                         };\r
3149 \r
3150                         function parseAttribute(match, name, value, val2, val3) {\r
3151                                 var attrRule, i;\r
3152 \r
3153                                 name = name.toLowerCase();\r
3154                                 value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute\r
3155 \r
3156                                 // Validate name and value\r
3157                                 if (validate && !isInternalElement && name.indexOf('data-') !== 0) {\r
3158                                         attrRule = validAttributesMap[name];\r
3159 \r
3160                                         // Find rule by pattern matching\r
3161                                         if (!attrRule && validAttributePatterns) {\r
3162                                                 i = validAttributePatterns.length;\r
3163                                                 while (i--) {\r
3164                                                         attrRule = validAttributePatterns[i];\r
3165                                                         if (attrRule.pattern.test(name))\r
3166                                                                 break;\r
3167                                                 }\r
3168 \r
3169                                                 // No rule matched\r
3170                                                 if (i === -1)\r
3171                                                         attrRule = null;\r
3172                                         }\r
3173 \r
3174                                         // No attribute rule found\r
3175                                         if (!attrRule)\r
3176                                                 return;\r
3177 \r
3178                                         // Validate value\r
3179                                         if (attrRule.validValues && !(value in attrRule.validValues))\r
3180                                                 return;\r
3181                                 }\r
3182 \r
3183                                 // Add attribute to list and map\r
3184                                 attrList.map[name] = value;\r
3185                                 attrList.push({\r
3186                                         name: name,\r
3187                                         value: value\r
3188                                 });\r
3189                         };\r
3190 \r
3191                         // Precompile RegExps and map objects\r
3192                         tokenRegExp = new RegExp('<(?:' +\r
3193                                 '(?:!--([\\w\\W]*?)-->)|' + // Comment\r
3194                                 '(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA\r
3195                                 '(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE\r
3196                                 '(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI\r
3197                                 '(?:\\/([^>]+)>)|' + // End element\r
3198                                 '(?:([A-Za-z0-9\\-\\:\\.]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element\r
3199                         ')', 'g');\r
3200 \r
3201                         attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:[^\"])*)\")|(?:\'((?:[^\'])*)\')|([^>\s]+)))?/g;\r
3202                         specialElements = {\r
3203                                 'script' : /<\/script[^>]*>/gi,\r
3204                                 'style' : /<\/style[^>]*>/gi,\r
3205                                 'noscript' : /<\/noscript[^>]*>/gi\r
3206                         };\r
3207 \r
3208                         // Setup lookup tables for empty elements and boolean attributes\r
3209                         shortEndedElements = schema.getShortEndedElements();\r
3210                         selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();\r
3211                         fillAttrsMap = schema.getBoolAttrs();\r
3212                         validate = settings.validate;\r
3213                         removeInternalElements = settings.remove_internals;\r
3214                         fixSelfClosing = settings.fix_self_closing;\r
3215                         isIE = tinymce.isIE;\r
3216                         invalidPrefixRegExp = /^:/;\r
3217 \r
3218                         while (matches = tokenRegExp.exec(html)) {\r
3219                                 // Text\r
3220                                 if (index < matches.index)\r
3221                                         self.text(decode(html.substr(index, matches.index - index)));\r
3222 \r
3223                                 if (value = matches[6]) { // End element\r
3224                                         value = value.toLowerCase();\r
3225 \r
3226                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
3227                                         if (isIE && invalidPrefixRegExp.test(value))\r
3228                                                 value = value.substr(1);\r
3229 \r
3230                                         processEndTag(value);\r
3231                                 } else if (value = matches[7]) { // Start element\r
3232                                         value = value.toLowerCase();\r
3233 \r
3234                                         // IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements\r
3235                                         if (isIE && invalidPrefixRegExp.test(value))\r
3236                                                 value = value.substr(1);\r
3237 \r
3238                                         isShortEnded = value in shortEndedElements;\r
3239 \r
3240                                         // Is self closing tag for example an <li> after an open <li>\r
3241                                         if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)\r
3242                                                 processEndTag(value);\r
3243 \r
3244                                         // Validate element\r
3245                                         if (!validate || (elementRule = schema.getElementRule(value))) {\r
3246                                                 isValidElement = true;\r
3247 \r
3248                                                 // Grab attributes map and patters when validation is enabled\r
3249                                                 if (validate) {\r
3250                                                         validAttributesMap = elementRule.attributes;\r
3251                                                         validAttributePatterns = elementRule.attributePatterns;\r
3252                                                 }\r
3253 \r
3254                                                 // Parse attributes\r
3255                                                 if (attribsValue = matches[8]) {\r
3256                                                         isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element\r
3257 \r
3258                                                         // If the element has internal attributes then remove it if we are told to do so\r
3259                                                         if (isInternalElement && removeInternalElements)\r
3260                                                                 isValidElement = false;\r
3261 \r
3262                                                         attrList = [];\r
3263                                                         attrList.map = {};\r
3264 \r
3265                                                         attribsValue.replace(attrRegExp, parseAttribute);\r
3266                                                 } else {\r
3267                                                         attrList = [];\r
3268                                                         attrList.map = {};\r
3269                                                 }\r
3270 \r
3271                                                 // Process attributes if validation is enabled\r
3272                                                 if (validate && !isInternalElement) {\r
3273                                                         attributesRequired = elementRule.attributesRequired;\r
3274                                                         attributesDefault = elementRule.attributesDefault;\r
3275                                                         attributesForced = elementRule.attributesForced;\r
3276 \r
3277                                                         // Handle forced attributes\r
3278                                                         if (attributesForced) {\r
3279                                                                 i = attributesForced.length;\r
3280                                                                 while (i--) {\r
3281                                                                         attr = attributesForced[i];\r
3282                                                                         name = attr.name;\r
3283                                                                         attrValue = attr.value;\r
3284 \r
3285                                                                         if (attrValue === '{$uid}')\r
3286                                                                                 attrValue = 'mce_' + idCount++;\r
3287 \r
3288                                                                         attrList.map[name] = attrValue;\r
3289                                                                         attrList.push({name: name, value: attrValue});\r
3290                                                                 }\r
3291                                                         }\r
3292 \r
3293                                                         // Handle default attributes\r
3294                                                         if (attributesDefault) {\r
3295                                                                 i = attributesDefault.length;\r
3296                                                                 while (i--) {\r
3297                                                                         attr = attributesDefault[i];\r
3298                                                                         name = attr.name;\r
3299 \r
3300                                                                         if (!(name in attrList.map)) {\r
3301                                                                                 attrValue = attr.value;\r
3302 \r
3303                                                                                 if (attrValue === '{$uid}')\r
3304                                                                                         attrValue = 'mce_' + idCount++;\r
3305 \r
3306                                                                                 attrList.map[name] = attrValue;\r
3307                                                                                 attrList.push({name: name, value: attrValue});\r
3308                                                                         }\r
3309                                                                 }\r
3310                                                         }\r
3311 \r
3312                                                         // Handle required attributes\r
3313                                                         if (attributesRequired) {\r
3314                                                                 i = attributesRequired.length;\r
3315                                                                 while (i--) {\r
3316                                                                         if (attributesRequired[i] in attrList.map)\r
3317                                                                                 break;\r
3318                                                                 }\r
3319 \r
3320                                                                 // None of the required attributes where found\r
3321                                                                 if (i === -1)\r
3322                                                                         isValidElement = false;\r
3323                                                         }\r
3324 \r
3325                                                         // Invalidate element if it's marked as bogus\r
3326                                                         if (attrList.map['data-mce-bogus'])\r
3327                                                                 isValidElement = false;\r
3328                                                 }\r
3329 \r
3330                                                 if (isValidElement)\r
3331                                                         self.start(value, attrList, isShortEnded);\r
3332                                         } else\r
3333                                                 isValidElement = false;\r
3334 \r
3335                                         // Treat script, noscript and style a bit different since they may include code that looks like elements\r
3336                                         if (endRegExp = specialElements[value]) {\r
3337                                                 endRegExp.lastIndex = index = matches.index + matches[0].length;\r
3338 \r
3339                                                 if (matches = endRegExp.exec(html)) {\r
3340                                                         if (isValidElement)\r
3341                                                                 text = html.substr(index, matches.index - index);\r
3342 \r
3343                                                         index = matches.index + matches[0].length;\r
3344                                                 } else {\r
3345                                                         text = html.substr(index);\r
3346                                                         index = html.length;\r
3347                                                 }\r
3348 \r
3349                                                 if (isValidElement && text.length > 0)\r
3350                                                         self.text(text, true);\r
3351 \r
3352                                                 if (isValidElement)\r
3353                                                         self.end(value);\r
3354 \r
3355                                                 tokenRegExp.lastIndex = index;\r
3356                                                 continue;\r
3357                                         }\r
3358 \r
3359                                         // Push value on to stack\r
3360                                         if (!isShortEnded) {\r
3361                                                 if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)\r
3362                                                         stack.push({name: value, valid: isValidElement});\r
3363                                                 else if (isValidElement)\r
3364                                                         self.end(value);\r
3365                                         }\r
3366                                 } else if (value = matches[1]) { // Comment\r
3367                                         self.comment(value);\r
3368                                 } else if (value = matches[2]) { // CDATA\r
3369                                         self.cdata(value);\r
3370                                 } else if (value = matches[3]) { // DOCTYPE\r
3371                                         self.doctype(value);\r
3372                                 } else if (value = matches[4]) { // PI\r
3373                                         self.pi(value, matches[5]);\r
3374                                 }\r
3375 \r
3376                                 index = matches.index + matches[0].length;\r
3377                         }\r
3378 \r
3379                         // Text\r
3380                         if (index < html.length)\r
3381                                 self.text(decode(html.substr(index)));\r
3382 \r
3383                         // Close any open elements\r
3384                         for (i = stack.length - 1; i >= 0; i--) {\r
3385                                 value = stack[i];\r
3386 \r
3387                                 if (value.valid)\r
3388                                         self.end(value.name);\r
3389                         }\r
3390                 };\r
3391         }\r
3392 })(tinymce);\r
3393 \r
3394 (function(tinymce) {\r
3395         var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {\r
3396                 '#text' : 3,\r
3397                 '#comment' : 8,\r
3398                 '#cdata' : 4,\r
3399                 '#pi' : 7,\r
3400                 '#doctype' : 10,\r
3401                 '#document-fragment' : 11\r
3402         };\r
3403 \r
3404         // Walks the tree left/right\r
3405         function walk(node, root_node, prev) {\r
3406                 var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';\r
3407 \r
3408                 // Walk into nodes if it has a start\r
3409                 if (node[startName])\r
3410                         return node[startName];\r
3411 \r
3412                 // Return the sibling if it has one\r
3413                 if (node !== root_node) {\r
3414                         sibling = node[siblingName];\r
3415 \r
3416                         if (sibling)\r
3417                                 return sibling;\r
3418 \r
3419                         // Walk up the parents to look for siblings\r
3420                         for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {\r
3421                                 sibling = parent[siblingName];\r
3422 \r
3423                                 if (sibling)\r
3424                                         return sibling;\r
3425                         }\r
3426                 }\r
3427         };\r
3428 \r
3429         function Node(name, type) {\r
3430                 this.name = name;\r
3431                 this.type = type;\r
3432 \r
3433                 if (type === 1) {\r
3434                         this.attributes = [];\r
3435                         this.attributes.map = {};\r
3436                 }\r
3437         }\r
3438 \r
3439         tinymce.extend(Node.prototype, {\r
3440                 replace : function(node) {\r
3441                         var self = this;\r
3442 \r
3443                         if (node.parent)\r
3444                                 node.remove();\r
3445 \r
3446                         self.insert(node, self);\r
3447                         self.remove();\r
3448 \r
3449                         return self;\r
3450                 },\r
3451 \r
3452                 attr : function(name, value) {\r
3453                         var self = this, attrs, i, undef;\r
3454 \r
3455                         if (typeof name !== "string") {\r
3456                                 for (i in name)\r
3457                                         self.attr(i, name[i]);\r
3458 \r
3459                                 return self;\r
3460                         }\r
3461 \r
3462                         if (attrs = self.attributes) {\r
3463                                 if (value !== undef) {\r
3464                                         // Remove attribute\r
3465                                         if (value === null) {\r
3466                                                 if (name in attrs.map) {\r
3467                                                         delete attrs.map[name];\r
3468 \r
3469                                                         i = attrs.length;\r
3470                                                         while (i--) {\r
3471                                                                 if (attrs[i].name === name) {\r
3472                                                                         attrs = attrs.splice(i, 1);\r
3473                                                                         return self;\r
3474                                                                 }\r
3475                                                         }\r
3476                                                 }\r
3477 \r
3478                                                 return self;\r
3479                                         }\r
3480 \r
3481                                         // Set attribute\r
3482                                         if (name in attrs.map) {\r
3483                                                 // Set attribute\r
3484                                                 i = attrs.length;\r
3485                                                 while (i--) {\r
3486                                                         if (attrs[i].name === name) {\r
3487                                                                 attrs[i].value = value;\r
3488                                                                 break;\r
3489                                                         }\r
3490                                                 }\r
3491                                         } else\r
3492                                                 attrs.push({name: name, value: value});\r
3493 \r
3494                                         attrs.map[name] = value;\r
3495 \r
3496                                         return self;\r
3497                                 } else {\r
3498                                         return attrs.map[name];\r
3499                                 }\r
3500                         }\r
3501                 },\r
3502 \r
3503                 clone : function() {\r
3504                         var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;\r
3505 \r
3506                         // Clone element attributes\r
3507                         if (selfAttrs = self.attributes) {\r
3508                                 cloneAttrs = [];\r
3509                                 cloneAttrs.map = {};\r
3510 \r
3511                                 for (i = 0, l = selfAttrs.length; i < l; i++) {\r
3512                                         selfAttr = selfAttrs[i];\r
3513 \r
3514                                         // Clone everything except id\r
3515                                         if (selfAttr.name !== 'id') {\r
3516                                                 cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};\r
3517                                                 cloneAttrs.map[selfAttr.name] = selfAttr.value;\r
3518                                         }\r
3519                                 }\r
3520 \r
3521                                 clone.attributes = cloneAttrs;\r
3522                         }\r
3523 \r
3524                         clone.value = self.value;\r
3525                         clone.shortEnded = self.shortEnded;\r
3526 \r
3527                         return clone;\r
3528                 },\r
3529 \r
3530                 wrap : function(wrapper) {\r
3531                         var self = this;\r
3532 \r
3533                         self.parent.insert(wrapper, self);\r
3534                         wrapper.append(self);\r
3535 \r
3536                         return self;\r
3537                 },\r
3538 \r
3539                 unwrap : function() {\r
3540                         var self = this, node, next;\r
3541 \r
3542                         for (node = self.firstChild; node; ) {\r
3543                                 next = node.next;\r
3544                                 self.insert(node, self, true);\r
3545                                 node = next;\r
3546                         }\r
3547 \r
3548                         self.remove();\r
3549                 },\r
3550 \r
3551                 remove : function() {\r
3552                         var self = this, parent = self.parent, next = self.next, prev = self.prev;\r
3553 \r
3554                         if (parent) {\r
3555                                 if (parent.firstChild === self) {\r
3556                                         parent.firstChild = next;\r
3557 \r
3558                                         if (next)\r
3559                                                 next.prev = null;\r
3560                                 } else {\r
3561                                         prev.next = next;\r
3562                                 }\r
3563 \r
3564                                 if (parent.lastChild === self) {\r
3565                                         parent.lastChild = prev;\r
3566 \r
3567                                         if (prev)\r
3568                                                 prev.next = null;\r
3569                                 } else {\r
3570                                         next.prev = prev;\r
3571                                 }\r
3572 \r
3573                                 self.parent = self.next = self.prev = null;\r
3574                         }\r
3575 \r
3576                         return self;\r
3577                 },\r
3578 \r
3579                 append : function(node) {\r
3580                         var self = this, last;\r
3581 \r
3582                         if (node.parent)\r
3583                                 node.remove();\r
3584 \r
3585                         last = self.lastChild;\r
3586                         if (last) {\r
3587                                 last.next = node;\r
3588                                 node.prev = last;\r
3589                                 self.lastChild = node;\r
3590                         } else\r
3591                                 self.lastChild = self.firstChild = node;\r
3592 \r
3593                         node.parent = self;\r
3594 \r
3595                         return node;\r
3596                 },\r
3597 \r
3598                 insert : function(node, ref_node, before) {\r
3599                         var parent;\r
3600 \r
3601                         if (node.parent)\r
3602                                 node.remove();\r
3603 \r
3604                         parent = ref_node.parent || this;\r
3605 \r
3606                         if (before) {\r
3607                                 if (ref_node === parent.firstChild)\r
3608                                         parent.firstChild = node;\r
3609                                 else\r
3610                                         ref_node.prev.next = node;\r
3611 \r
3612                                 node.prev = ref_node.prev;\r
3613                                 node.next = ref_node;\r
3614                                 ref_node.prev = node;\r
3615                         } else {\r
3616                                 if (ref_node === parent.lastChild)\r
3617                                         parent.lastChild = node;\r
3618                                 else\r
3619                                         ref_node.next.prev = node;\r
3620 \r
3621                                 node.next = ref_node.next;\r
3622                                 node.prev = ref_node;\r
3623                                 ref_node.next = node;\r
3624                         }\r
3625 \r
3626                         node.parent = parent;\r
3627 \r
3628                         return node;\r
3629                 },\r
3630 \r
3631                 getAll : function(name) {\r
3632                         var self = this, node, collection = [];\r
3633 \r
3634                         for (node = self.firstChild; node; node = walk(node, self)) {\r
3635                                 if (node.name === name)\r
3636                                         collection.push(node);\r
3637                         }\r
3638 \r
3639                         return collection;\r
3640                 },\r
3641 \r
3642                 empty : function() {\r
3643                         var self = this, nodes, i, node;\r
3644 \r
3645                         // Remove all children\r
3646                         if (self.firstChild) {\r
3647                                 nodes = [];\r
3648 \r
3649                                 // Collect the children\r
3650                                 for (node = self.firstChild; node; node = walk(node, self))\r
3651                                         nodes.push(node);\r
3652 \r
3653                                 // Remove the children\r
3654                                 i = nodes.length;\r
3655                                 while (i--) {\r
3656                                         node = nodes[i];\r
3657                                         node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;\r
3658                                 }\r
3659                         }\r
3660 \r
3661                         self.firstChild = self.lastChild = null;\r
3662 \r
3663                         return self;\r
3664                 },\r
3665 \r
3666                 isEmpty : function(elements) {\r
3667                         var self = this, node = self.firstChild, i, name;\r
3668 \r
3669                         if (node) {\r
3670                                 do {\r
3671                                         if (node.type === 1) {\r
3672                                                 // Ignore bogus elements\r
3673                                                 if (node.attributes.map['data-mce-bogus'])\r
3674                                                         continue;\r
3675 \r
3676                                                 // Keep empty elements like <img />\r
3677                                                 if (elements[node.name])\r
3678                                                         return false;\r
3679 \r
3680                                                 // Keep elements with data attributes or name attribute like <a name="1"></a>\r
3681                                                 i = node.attributes.length;\r
3682                                                 while (i--) {\r
3683                                                         name = node.attributes[i].name;\r
3684                                                         if (name === "name" || name.indexOf('data-mce-') === 0)\r
3685                                                                 return false;\r
3686                                                 }\r
3687                                         }\r
3688 \r
3689                                         // Keep comments\r
3690                                         if (node.type === 8)\r
3691                                                 return false;\r
3692                                         \r
3693                                         // Keep non whitespace text nodes\r
3694                                         if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))\r
3695                                                 return false;\r
3696                                 } while (node = walk(node, self));\r
3697                         }\r
3698 \r
3699                         return true;\r
3700                 },\r
3701 \r
3702                 walk : function(prev) {\r
3703                         return walk(this, null, prev);\r
3704                 }\r
3705         });\r
3706 \r
3707         tinymce.extend(Node, {\r
3708                 create : function(name, attrs) {\r
3709                         var node, attrName;\r
3710 \r
3711                         // Create node\r
3712                         node = new Node(name, typeLookup[name] || 1);\r
3713 \r
3714                         // Add attributes if needed\r
3715                         if (attrs) {\r
3716                                 for (attrName in attrs)\r
3717                                         node.attr(attrName, attrs[attrName]);\r
3718                         }\r
3719 \r
3720                         return node;\r
3721                 }\r
3722         });\r
3723 \r
3724         tinymce.html.Node = Node;\r
3725 })(tinymce);\r
3726 \r
3727 (function(tinymce) {\r
3728         var Node = tinymce.html.Node;\r
3729 \r
3730         tinymce.html.DomParser = function(settings, schema) {\r
3731                 var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};\r
3732 \r
3733                 settings = settings || {};\r
3734                 settings.validate = "validate" in settings ? settings.validate : true;\r
3735                 settings.root_name = settings.root_name || 'body';\r
3736                 self.schema = schema = schema || new tinymce.html.Schema();\r
3737 \r
3738                 function fixInvalidChildren(nodes) {\r
3739                         var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,\r
3740                                 childClone, nonEmptyElements, nonSplitableElements, textBlockElements, sibling, nextNode;\r
3741 \r
3742                         nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');\r
3743                         nonEmptyElements = schema.getNonEmptyElements();\r
3744                         textBlockElements = schema.getTextBlockElements();\r
3745 \r
3746                         for (ni = 0; ni < nodes.length; ni++) {\r
3747                                 node = nodes[ni];\r
3748 \r
3749                                 // Already removed or fixed\r
3750                                 if (!node.parent || node.fixed)\r
3751                                         continue;\r
3752 \r
3753                                 // If the invalid element is a text block and the text block is within a parent LI element\r
3754                                 // Then unwrap the first text block and convert other sibling text blocks to LI elements similar to Word/Open Office\r
3755                                 if (textBlockElements[node.name] && node.parent.name == 'li') {\r
3756                                         // Move sibling text blocks after LI element\r
3757                                         sibling = node.next;\r
3758                                         while (sibling) {\r
3759                                                 if (textBlockElements[sibling.name]) {\r
3760                                                         sibling.name = 'li';\r
3761                                                         sibling.fixed = true;\r
3762                                                         node.parent.insert(sibling, node.parent);\r
3763                                                 } else {\r
3764                                                         break;\r
3765                                                 }\r
3766 \r
3767                                                 sibling = sibling.next;\r
3768                                         }\r
3769 \r
3770                                         // Unwrap current text block\r
3771                                         node.unwrap(node);\r
3772                                         continue;\r
3773                                 }\r
3774 \r
3775                                 // Get list of all parent nodes until we find a valid parent to stick the child into\r
3776                                 parents = [node];\r
3777                                 for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)\r
3778                                         parents.push(parent);\r
3779 \r
3780                                 // Found a suitable parent\r
3781                                 if (parent && parents.length > 1) {\r
3782                                         // Reverse the array since it makes looping easier\r
3783                                         parents.reverse();\r
3784 \r
3785                                         // Clone the related parent and insert that after the moved node\r
3786                                         newParent = currentNode = self.filterNode(parents[0].clone());\r
3787 \r
3788                                         // Start cloning and moving children on the left side of the target node\r
3789                                         for (i = 0; i < parents.length - 1; i++) {\r
3790                                                 if (schema.isValidChild(currentNode.name, parents[i].name)) {\r
3791                                                         tempNode = self.filterNode(parents[i].clone());\r
3792                                                         currentNode.append(tempNode);\r
3793                                                 } else\r
3794                                                         tempNode = currentNode;\r
3795 \r
3796                                                 for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {\r
3797                                                         nextNode = childNode.next;\r
3798                                                         tempNode.append(childNode);\r
3799                                                         childNode = nextNode;\r
3800                                                 }\r
3801 \r
3802                                                 currentNode = tempNode;\r
3803                                         }\r
3804 \r
3805                                         if (!newParent.isEmpty(nonEmptyElements)) {\r
3806                                                 parent.insert(newParent, parents[0], true);\r
3807                                                 parent.insert(node, newParent);\r
3808                                         } else {\r
3809                                                 parent.insert(node, parents[0], true);\r
3810                                         }\r
3811 \r
3812                                         // Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>\r
3813                                         parent = parents[0];\r
3814                                         if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {\r
3815                                                 parent.empty().remove();\r
3816                                         }\r
3817                                 } else if (node.parent) {\r
3818                                         // If it's an LI try to find a UL/OL for it or wrap it\r
3819                                         if (node.name === 'li') {\r
3820                                                 sibling = node.prev;\r
3821                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
3822                                                         sibling.append(node);\r
3823                                                         continue;\r
3824                                                 }\r
3825 \r
3826                                                 sibling = node.next;\r
3827                                                 if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {\r
3828                                                         sibling.insert(node, sibling.firstChild, true);\r
3829                                                         continue;\r
3830                                                 }\r
3831 \r
3832                                                 node.wrap(self.filterNode(new Node('ul', 1)));\r
3833                                                 continue;\r
3834                                         }\r
3835 \r
3836                                         // Try wrapping the element in a DIV\r
3837                                         if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {\r
3838                                                 node.wrap(self.filterNode(new Node('div', 1)));\r
3839                                         } else {\r
3840                                                 // We failed wrapping it, then remove or unwrap it\r
3841                                                 if (node.name === 'style' || node.name === 'script')\r
3842                                                         node.empty().remove();\r
3843                                                 else\r
3844                                                         node.unwrap();\r
3845                                         }\r
3846                                 }\r
3847                         }\r
3848                 };\r
3849 \r
3850                 self.filterNode = function(node) {\r
3851                         var i, name, list;\r
3852 \r
3853                         // Run element filters\r
3854                         if (name in nodeFilters) {\r
3855                                 list = matchedNodes[name];\r
3856 \r
3857                                 if (list)\r
3858                                         list.push(node);\r
3859                                 else\r
3860                                         matchedNodes[name] = [node];\r
3861                         }\r
3862 \r
3863                         // Run attribute filters\r
3864                         i = attributeFilters.length;\r
3865                         while (i--) {\r
3866                                 name = attributeFilters[i].name;\r
3867 \r
3868                                 if (name in node.attributes.map) {\r
3869                                         list = matchedAttributes[name];\r
3870 \r
3871                                         if (list)\r
3872                                                 list.push(node);\r
3873                                         else\r
3874                                                 matchedAttributes[name] = [node];\r
3875                                 }\r
3876                         }\r
3877 \r
3878                         return node;\r
3879                 };\r
3880 \r
3881                 self.addNodeFilter = function(name, callback) {\r
3882                         tinymce.each(tinymce.explode(name), function(name) {\r
3883                                 var list = nodeFilters[name];\r
3884 \r
3885                                 if (!list)\r
3886                                         nodeFilters[name] = list = [];\r
3887 \r
3888                                 list.push(callback);\r
3889                         });\r
3890                 };\r
3891 \r
3892                 self.addAttributeFilter = function(name, callback) {\r
3893                         tinymce.each(tinymce.explode(name), function(name) {\r
3894                                 var i;\r
3895 \r
3896                                 for (i = 0; i < attributeFilters.length; i++) {\r
3897                                         if (attributeFilters[i].name === name) {\r
3898                                                 attributeFilters[i].callbacks.push(callback);\r
3899                                                 return;\r
3900                                         }\r
3901                                 }\r
3902 \r
3903                                 attributeFilters.push({name: name, callbacks: [callback]});\r
3904                         });\r
3905                 };\r
3906 \r
3907                 self.parse = function(html, args) {\r
3908                         var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,\r
3909                                 blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,\r
3910                                 endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;\r
3911 \r
3912                         args = args || {};\r
3913                         matchedNodes = {};\r
3914                         matchedAttributes = {};\r
3915                         blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());\r
3916                         nonEmptyElements = schema.getNonEmptyElements();\r
3917                         children = schema.children;\r
3918                         validate = settings.validate;\r
3919                         rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;\r
3920 \r
3921                         whiteSpaceElements = schema.getWhiteSpaceElements();\r
3922                         startWhiteSpaceRegExp = /^[ \t\r\n]+/;\r
3923                         endWhiteSpaceRegExp = /[ \t\r\n]+$/;\r
3924                         allWhiteSpaceRegExp = /[ \t\r\n]+/g;\r
3925                         isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;\r
3926 \r
3927                         function addRootBlocks() {\r
3928                                 var node = rootNode.firstChild, next, rootBlockNode;\r
3929 \r
3930                                 while (node) {\r
3931                                         next = node.next;\r
3932 \r
3933                                         if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {\r
3934                                                 if (!rootBlockNode) {\r
3935                                                         // Create a new root block element\r
3936                                                         rootBlockNode = createNode(rootBlockName, 1);\r
3937                                                         rootNode.insert(rootBlockNode, node);\r
3938                                                         rootBlockNode.append(node);\r
3939                                                 } else\r
3940                                                         rootBlockNode.append(node);\r
3941                                         } else {\r
3942                                                 rootBlockNode = null;\r
3943                                         }\r
3944 \r
3945                                         node = next;\r
3946                                 };\r
3947                         };\r
3948 \r
3949                         function createNode(name, type) {\r
3950                                 var node = new Node(name, type), list;\r
3951 \r
3952                                 if (name in nodeFilters) {\r
3953                                         list = matchedNodes[name];\r
3954 \r
3955                                         if (list)\r
3956                                                 list.push(node);\r
3957                                         else\r
3958                                                 matchedNodes[name] = [node];\r
3959                                 }\r
3960 \r
3961                                 return node;\r
3962                         };\r
3963 \r
3964                         function removeWhitespaceBefore(node) {\r
3965                                 var textNode, textVal, sibling;\r
3966 \r
3967                                 for (textNode = node.prev; textNode && textNode.type === 3; ) {\r
3968                                         textVal = textNode.value.replace(endWhiteSpaceRegExp, '');\r
3969 \r
3970                                         if (textVal.length > 0) {\r
3971                                                 textNode.value = textVal;\r
3972                                                 textNode = textNode.prev;\r
3973                                         } else {\r
3974                                                 sibling = textNode.prev;\r
3975                                                 textNode.remove();\r
3976                                                 textNode = sibling;\r
3977                                         }\r
3978                                 }\r
3979                         };\r
3980 \r
3981                         function cloneAndExcludeBlocks(input) {\r
3982                                 var name, output = {};\r
3983 \r
3984                                 for (name in input) {\r
3985                                         if (name !== 'li' && name != 'p') {\r
3986                                                 output[name] = input[name];\r
3987                                         }\r
3988                                 }\r
3989 \r
3990                                 return output;\r
3991                         };\r
3992 \r
3993                         parser = new tinymce.html.SaxParser({\r
3994                                 validate : validate,\r
3995 \r
3996                                 // Exclude P and LI from DOM parsing since it's treated better by the DOM parser\r
3997                                 self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),\r
3998 \r
3999                                 cdata: function(text) {\r
4000                                         node.append(createNode('#cdata', 4)).value = text;\r
4001                                 },\r
4002 \r
4003                                 text: function(text, raw) {\r
4004                                         var textNode;\r
4005 \r
4006                                         // Trim all redundant whitespace on non white space elements\r
4007                                         if (!isInWhiteSpacePreservedElement) {\r
4008                                                 text = text.replace(allWhiteSpaceRegExp, ' ');\r
4009 \r
4010                                                 if (node.lastChild && blockElements[node.lastChild.name])\r
4011                                                         text = text.replace(startWhiteSpaceRegExp, '');\r
4012                                         }\r
4013 \r
4014                                         // Do we need to create the node\r
4015                                         if (text.length !== 0) {\r
4016                                                 textNode = createNode('#text', 3);\r
4017                                                 textNode.raw = !!raw;\r
4018                                                 node.append(textNode).value = text;\r
4019                                         }\r
4020                                 },\r
4021 \r
4022                                 comment: function(text) {\r
4023                                         node.append(createNode('#comment', 8)).value = text;\r
4024                                 },\r
4025 \r
4026                                 pi: function(name, text) {\r
4027                                         node.append(createNode(name, 7)).value = text;\r
4028                                         removeWhitespaceBefore(node);\r
4029                                 },\r
4030 \r
4031                                 doctype: function(text) {\r
4032                                         var newNode;\r
4033                 \r
4034                                         newNode = node.append(createNode('#doctype', 10));\r
4035                                         newNode.value = text;\r
4036                                         removeWhitespaceBefore(node);\r
4037                                 },\r
4038 \r
4039                                 start: function(name, attrs, empty) {\r
4040                                         var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;\r
4041 \r
4042                                         elementRule = validate ? schema.getElementRule(name) : {};\r
4043                                         if (elementRule) {\r
4044                                                 newNode = createNode(elementRule.outputName || name, 1);\r
4045                                                 newNode.attributes = attrs;\r
4046                                                 newNode.shortEnded = empty;\r
4047 \r
4048                                                 node.append(newNode);\r
4049 \r
4050                                                 // Check if node is valid child of the parent node is the child is\r
4051                                                 // unknown we don't collect it since it's probably a custom element\r
4052                                                 parent = children[node.name];\r
4053                                                 if (parent && children[newNode.name] && !parent[newNode.name])\r
4054                                                         invalidChildren.push(newNode);\r
4055 \r
4056                                                 attrFiltersLen = attributeFilters.length;\r
4057                                                 while (attrFiltersLen--) {\r
4058                                                         attrName = attributeFilters[attrFiltersLen].name;\r
4059 \r
4060                                                         if (attrName in attrs.map) {\r
4061                                                                 list = matchedAttributes[attrName];\r
4062 \r
4063                                                                 if (list)\r
4064                                                                         list.push(newNode);\r
4065                                                                 else\r
4066                                                                         matchedAttributes[attrName] = [newNode];\r
4067                                                         }\r
4068                                                 }\r
4069 \r
4070                                                 // Trim whitespace before block\r
4071                                                 if (blockElements[name])\r
4072                                                         removeWhitespaceBefore(newNode);\r
4073 \r
4074                                                 // Change current node if the element wasn't empty i.e not <br /> or <img />\r
4075                                                 if (!empty)\r
4076                                                         node = newNode;\r
4077 \r
4078                                                 // Check if we are inside a whitespace preserved element\r
4079                                                 if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {\r
4080                                                         isInWhiteSpacePreservedElement = true;\r
4081                                                 }\r
4082                                         }\r
4083                                 },\r
4084 \r
4085                                 end: function(name) {\r
4086                                         var textNode, elementRule, text, sibling, tempNode;\r
4087 \r
4088                                         elementRule = validate ? schema.getElementRule(name) : {};\r
4089                                         if (elementRule) {\r
4090                                                 if (blockElements[name]) {\r
4091                                                         if (!isInWhiteSpacePreservedElement) {\r
4092                                                                 // Trim whitespace of the first node in a block\r
4093                                                                 textNode = node.firstChild;\r
4094                                                                 if (textNode && textNode.type === 3) {\r
4095                                                                         text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
4096 \r
4097                                                                         // Any characters left after trim or should we remove it\r
4098                                                                         if (text.length > 0) {\r
4099                                                                                 textNode.value = text;\r
4100                                                                                 textNode = textNode.next;\r
4101                                                                         } else {\r
4102                                                                                 sibling = textNode.next;\r
4103                                                                                 textNode.remove();\r
4104                                                                                 textNode = sibling;\r
4105                                                                         }\r
4106 \r
4107                                                                         // Remove any pure whitespace siblings\r
4108                                                                         while (textNode && textNode.type === 3) {\r
4109                                                                                 text = textNode.value;\r
4110                                                                                 sibling = textNode.next;\r
4111 \r
4112                                                                                 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {\r
4113                                                                                         textNode.remove();\r
4114                                                                                         textNode = sibling;\r
4115                                                                                 }\r
4116 \r
4117                                                                                 textNode = sibling;\r
4118                                                                         }\r
4119                                                                 }\r
4120 \r
4121                                                                 // Trim whitespace of the last node in a block\r
4122                                                                 textNode = node.lastChild;\r
4123                                                                 if (textNode && textNode.type === 3) {\r
4124                                                                         text = textNode.value.replace(endWhiteSpaceRegExp, '');\r
4125 \r
4126                                                                         // Any characters left after trim or should we remove it\r
4127                                                                         if (text.length > 0) {\r
4128                                                                                 textNode.value = text;\r
4129                                                                                 textNode = textNode.prev;\r
4130                                                                         } else {\r
4131                                                                                 sibling = textNode.prev;\r
4132                                                                                 textNode.remove();\r
4133                                                                                 textNode = sibling;\r
4134                                                                         }\r
4135 \r
4136                                                                         // Remove any pure whitespace siblings\r
4137                                                                         while (textNode && textNode.type === 3) {\r
4138                                                                                 text = textNode.value;\r
4139                                                                                 sibling = textNode.prev;\r
4140 \r
4141                                                                                 if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {\r
4142                                                                                         textNode.remove();\r
4143                                                                                         textNode = sibling;\r
4144                                                                                 }\r
4145 \r
4146                                                                                 textNode = sibling;\r
4147                                                                         }\r
4148                                                                 }\r
4149                                                         }\r
4150 \r
4151                                                         // Trim start white space\r
4152                                                         // Removed due to: #5424\r
4153                                                         /*textNode = node.prev;\r
4154                                                         if (textNode && textNode.type === 3) {\r
4155                                                                 text = textNode.value.replace(startWhiteSpaceRegExp, '');\r
4156 \r
4157                                                                 if (text.length > 0)\r
4158                                                                         textNode.value = text;\r
4159                                                                 else\r
4160                                                                         textNode.remove();\r
4161                                                         }*/\r
4162                                                 }\r
4163 \r
4164                                                 // Check if we exited a whitespace preserved element\r
4165                                                 if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {\r
4166                                                         isInWhiteSpacePreservedElement = false;\r
4167                                                 }\r
4168 \r
4169                                                 // Handle empty nodes\r
4170                                                 if (elementRule.removeEmpty || elementRule.paddEmpty) {\r
4171                                                         if (node.isEmpty(nonEmptyElements)) {\r
4172                                                                 if (elementRule.paddEmpty)\r
4173                                                                         node.empty().append(new Node('#text', '3')).value = '\u00a0';\r
4174                                                                 else {\r
4175                                                                         // Leave nodes that have a name like <a name="name">\r
4176                                                                         if (!node.attributes.map.name && !node.attributes.map.id) {\r
4177                                                                                 tempNode = node.parent;\r
4178                                                                                 node.empty().remove();\r
4179                                                                                 node = tempNode;\r
4180                                                                                 return;\r
4181                                                                         }\r
4182                                                                 }\r
4183                                                         }\r
4184                                                 }\r
4185 \r
4186                                                 node = node.parent;\r
4187                                         }\r
4188                                 }\r
4189                         }, schema);\r
4190 \r
4191                         rootNode = node = new Node(args.context || settings.root_name, 11);\r
4192 \r
4193                         parser.parse(html);\r
4194 \r
4195                         // Fix invalid children or report invalid children in a contextual parsing\r
4196                         if (validate && invalidChildren.length) {\r
4197                                 if (!args.context)\r
4198                                         fixInvalidChildren(invalidChildren);\r
4199                                 else\r
4200                                         args.invalid = true;\r
4201                         }\r
4202 \r
4203                         // Wrap nodes in the root into block elements if the root is body\r
4204                         if (rootBlockName && rootNode.name == 'body')\r
4205                                 addRootBlocks();\r
4206 \r
4207                         // Run filters only when the contents is valid\r
4208                         if (!args.invalid) {\r
4209                                 // Run node filters\r
4210                                 for (name in matchedNodes) {\r
4211                                         list = nodeFilters[name];\r
4212                                         nodes = matchedNodes[name];\r
4213 \r
4214                                         // Remove already removed children\r
4215                                         fi = nodes.length;\r
4216                                         while (fi--) {\r
4217                                                 if (!nodes[fi].parent)\r
4218                                                         nodes.splice(fi, 1);\r
4219                                         }\r
4220 \r
4221                                         for (i = 0, l = list.length; i < l; i++)\r
4222                                                 list[i](nodes, name, args);\r
4223                                 }\r
4224 \r
4225                                 // Run attribute filters\r
4226                                 for (i = 0, l = attributeFilters.length; i < l; i++) {\r
4227                                         list = attributeFilters[i];\r
4228 \r
4229                                         if (list.name in matchedAttributes) {\r
4230                                                 nodes = matchedAttributes[list.name];\r
4231 \r
4232                                                 // Remove already removed children\r
4233                                                 fi = nodes.length;\r
4234                                                 while (fi--) {\r
4235                                                         if (!nodes[fi].parent)\r
4236                                                                 nodes.splice(fi, 1);\r
4237                                                 }\r
4238 \r
4239                                                 for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)\r
4240                                                         list.callbacks[fi](nodes, list.name, args);\r
4241                                         }\r
4242                                 }\r
4243                         }\r
4244 \r
4245                         return rootNode;\r
4246                 };\r
4247 \r
4248                 // Remove <br> at end of block elements Gecko and WebKit injects BR elements to\r
4249                 // make it possible to place the caret inside empty blocks. This logic tries to remove\r
4250                 // these elements and keep br elements that where intended to be there intact\r
4251                 if (settings.remove_trailing_brs) {\r
4252                         self.addNodeFilter('br', function(nodes, name) {\r
4253                                 var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),\r
4254                                         nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;\r
4255 \r
4256                                 // Remove brs from body element as well\r
4257                                 blockElements.body = 1;\r
4258 \r
4259                                 // Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>\r
4260                                 for (i = 0; i < l; i++) {\r
4261                                         node = nodes[i];\r
4262                                         parent = node.parent;\r
4263 \r
4264                                         if (blockElements[node.parent.name] && node === parent.lastChild) {\r
4265                                                 // Loop all nodes to the left of the current node and check for other BR elements\r
4266                                                 // excluding bookmarks since they are invisible\r
4267                                                 prev = node.prev;\r
4268                                                 while (prev) {\r
4269                                                         prevName = prev.name;\r
4270 \r
4271                                                         // Ignore bookmarks\r
4272                                                         if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {\r
4273                                                                 // Found a non BR element\r
4274                                                                 if (prevName !== "br")\r
4275                                                                         break;\r
4276         \r
4277                                                                 // Found another br it's a <br><br> structure then don't remove anything\r
4278                                                                 if (prevName === 'br') {\r
4279                                                                         node = null;\r
4280                                                                         break;\r
4281                                                                 }\r
4282                                                         }\r
4283 \r
4284                                                         prev = prev.prev;\r
4285                                                 }\r
4286 \r
4287                                                 if (node) {\r
4288                                                         node.remove();\r
4289 \r
4290                                                         // Is the parent to be considered empty after we removed the BR\r
4291                                                         if (parent.isEmpty(nonEmptyElements)) {\r
4292                                                                 elementRule = schema.getElementRule(parent.name);\r
4293 \r
4294                                                                 // Remove or padd the element depending on schema rule\r
4295                                                                 if (elementRule) {\r
4296                                                                         if (elementRule.removeEmpty)\r
4297                                                                                 parent.remove();\r
4298                                                                         else if (elementRule.paddEmpty)\r
4299                                                                                 parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';\r
4300                                                                 }\r
4301                                                         }\r
4302                                                 }\r
4303                                         } else {\r
4304                                                 // 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
4305                                                 lastParent = node;\r
4306                                                 while (parent.firstChild === lastParent && parent.lastChild === lastParent) {\r
4307                                                         lastParent = parent;\r
4308 \r
4309                                                         if (blockElements[parent.name]) {\r
4310                                                                 break;\r
4311                                                         }\r
4312 \r
4313                                                         parent = parent.parent;\r
4314                                                 }\r
4315 \r
4316                                                 if (lastParent === parent) {\r
4317                                                         textNode = new tinymce.html.Node('#text', 3);\r
4318                                                         textNode.value = '\u00a0';\r
4319                                                         node.replace(textNode);\r
4320                                                 }\r
4321                                         }\r
4322                                 }\r
4323                         });\r
4324                 }\r
4325 \r
4326                 // Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.\r
4327                 if (!settings.allow_html_in_named_anchor) {\r
4328                         self.addAttributeFilter('id,name', function(nodes, name) {\r
4329                                 var i = nodes.length, sibling, prevSibling, parent, node;\r
4330 \r
4331                                 while (i--) {\r
4332                                         node = nodes[i];\r
4333                                         if (node.name === 'a' && node.firstChild && !node.attr('href')) {\r
4334                                                 parent = node.parent;\r
4335 \r
4336                                                 // Move children after current node\r
4337                                                 sibling = node.lastChild;\r
4338                                                 do {\r
4339                                                         prevSibling = sibling.prev;\r
4340                                                         parent.insert(sibling, node);\r
4341                                                         sibling = prevSibling;\r
4342                                                 } while (sibling);\r
4343                                         }\r
4344                                 }\r
4345                         });\r
4346                 }\r
4347         }\r
4348 })(tinymce);\r
4349 \r
4350 tinymce.html.Writer = function(settings) {\r
4351         var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;\r
4352 \r
4353         settings = settings || {};\r
4354         indent = settings.indent;\r
4355         indentBefore = tinymce.makeMap(settings.indent_before || '');\r
4356         indentAfter = tinymce.makeMap(settings.indent_after || '');\r
4357         encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);\r
4358         htmlOutput = settings.element_format == "html";\r
4359 \r
4360         return {\r
4361                 start: function(name, attrs, empty) {\r
4362                         var i, l, attr, value;\r
4363 \r
4364                         if (indent && indentBefore[name] && html.length > 0) {\r
4365                                 value = html[html.length - 1];\r
4366 \r
4367                                 if (value.length > 0 && value !== '\n')\r
4368                                         html.push('\n');\r
4369                         }\r
4370 \r
4371                         html.push('<', name);\r
4372 \r
4373                         if (attrs) {\r
4374                                 for (i = 0, l = attrs.length; i < l; i++) {\r
4375                                         attr = attrs[i];\r
4376                                         html.push(' ', attr.name, '="', encode(attr.value, true), '"');\r
4377                                 }\r
4378                         }\r
4379 \r
4380                         if (!empty || htmlOutput)\r
4381                                 html[html.length] = '>';\r
4382                         else\r
4383                                 html[html.length] = ' />';\r
4384 \r
4385                         if (empty && indent && indentAfter[name] && html.length > 0) {\r
4386                                 value = html[html.length - 1];\r
4387 \r
4388                                 if (value.length > 0 && value !== '\n')\r
4389                                         html.push('\n');\r
4390                         }\r
4391                 },\r
4392 \r
4393                 end: function(name) {\r
4394                         var value;\r
4395 \r
4396                         /*if (indent && indentBefore[name] && html.length > 0) {\r
4397                                 value = html[html.length - 1];\r
4398 \r
4399                                 if (value.length > 0 && value !== '\n')\r
4400                                         html.push('\n');\r
4401                         }*/\r
4402 \r
4403                         html.push('</', name, '>');\r
4404 \r
4405                         if (indent && indentAfter[name] && html.length > 0) {\r
4406                                 value = html[html.length - 1];\r
4407 \r
4408                                 if (value.length > 0 && value !== '\n')\r
4409                                         html.push('\n');\r
4410                         }\r
4411                 },\r
4412 \r
4413                 text: function(text, raw) {\r
4414                         if (text.length > 0)\r
4415                                 html[html.length] = raw ? text : encode(text);\r
4416                 },\r
4417 \r
4418                 cdata: function(text) {\r
4419                         html.push('<![CDATA[', text, ']]>');\r
4420                 },\r
4421 \r
4422                 comment: function(text) {\r
4423                         html.push('<!--', text, '-->');\r
4424                 },\r
4425 \r
4426                 pi: function(name, text) {\r
4427                         if (text)\r
4428                                 html.push('<?', name, ' ', text, '?>');\r
4429                         else\r
4430                                 html.push('<?', name, '?>');\r
4431 \r
4432                         if (indent)\r
4433                                 html.push('\n');\r
4434                 },\r
4435 \r
4436                 doctype: function(text) {\r
4437                         html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');\r
4438                 },\r
4439 \r
4440                 reset: function() {\r
4441                         html.length = 0;\r
4442                 },\r
4443 \r
4444                 getContent: function() {\r
4445                         return html.join('').replace(/\n$/, '');\r
4446                 }\r
4447         };\r
4448 };\r
4449 \r
4450 (function(tinymce) {\r
4451         tinymce.html.Serializer = function(settings, schema) {\r
4452                 var self = this, writer = new tinymce.html.Writer(settings);\r
4453 \r
4454                 settings = settings || {};\r
4455                 settings.validate = "validate" in settings ? settings.validate : true;\r
4456 \r
4457                 self.schema = schema = schema || new tinymce.html.Schema();\r
4458                 self.writer = writer;\r
4459 \r
4460                 self.serialize = function(node) {\r
4461                         var handlers, validate;\r
4462 \r
4463                         validate = settings.validate;\r
4464 \r
4465                         handlers = {\r
4466                                 // #text\r
4467                                 3: function(node, raw) {\r
4468                                         writer.text(node.value, node.raw);\r
4469                                 },\r
4470 \r
4471                                 // #comment\r
4472                                 8: function(node) {\r
4473                                         writer.comment(node.value);\r
4474                                 },\r
4475 \r
4476                                 // Processing instruction\r
4477                                 7: function(node) {\r
4478                                         writer.pi(node.name, node.value);\r
4479                                 },\r
4480 \r
4481                                 // Doctype\r
4482                                 10: function(node) {\r
4483                                         writer.doctype(node.value);\r
4484                                 },\r
4485 \r
4486                                 // CDATA\r
4487                                 4: function(node) {\r
4488                                         writer.cdata(node.value);\r
4489                                 },\r
4490 \r
4491                                 // Document fragment\r
4492                                 11: function(node) {\r
4493                                         if ((node = node.firstChild)) {\r
4494                                                 do {\r
4495                                                         walk(node);\r
4496                                                 } while (node = node.next);\r
4497                                         }\r
4498                                 }\r
4499                         };\r
4500 \r
4501                         writer.reset();\r
4502 \r
4503                         function walk(node) {\r
4504                                 var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;\r
4505 \r
4506                                 if (!handler) {\r
4507                                         name = node.name;\r
4508                                         isEmpty = node.shortEnded;\r
4509                                         attrs = node.attributes;\r
4510 \r
4511                                         // Sort attributes\r
4512                                         if (validate && attrs && attrs.length > 1) {\r
4513                                                 sortedAttrs = [];\r
4514                                                 sortedAttrs.map = {};\r
4515 \r
4516                                                 elementRule = schema.getElementRule(node.name);\r
4517                                                 for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {\r
4518                                                         attrName = elementRule.attributesOrder[i];\r
4519 \r
4520                                                         if (attrName in attrs.map) {\r
4521                                                                 attrValue = attrs.map[attrName];\r
4522                                                                 sortedAttrs.map[attrName] = attrValue;\r
4523                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
4524                                                         }\r
4525                                                 }\r
4526 \r
4527                                                 for (i = 0, l = attrs.length; i < l; i++) {\r
4528                                                         attrName = attrs[i].name;\r
4529 \r
4530                                                         if (!(attrName in sortedAttrs.map)) {\r
4531                                                                 attrValue = attrs.map[attrName];\r
4532                                                                 sortedAttrs.map[attrName] = attrValue;\r
4533                                                                 sortedAttrs.push({name: attrName, value: attrValue});\r
4534                                                         }\r
4535                                                 }\r
4536 \r
4537                                                 attrs = sortedAttrs;\r
4538                                         }\r
4539 \r
4540                                         writer.start(node.name, attrs, isEmpty);\r
4541 \r
4542                                         if (!isEmpty) {\r
4543                                                 if ((node = node.firstChild)) {\r
4544                                                         do {\r
4545                                                                 walk(node);\r
4546                                                         } while (node = node.next);\r
4547                                                 }\r
4548 \r
4549                                                 writer.end(name);\r
4550                                         }\r
4551                                 } else\r
4552                                         handler(node);\r
4553                         }\r
4554 \r
4555                         // Serialize element and treat all non elements as fragments\r
4556                         if (node.type == 1 && !settings.inner)\r
4557                                 walk(node);\r
4558                         else\r
4559                                 handlers[11](node);\r
4560 \r
4561                         return writer.getContent();\r
4562                 };\r
4563         }\r
4564 })(tinymce);\r
4565 \r
4566 // JSLint defined globals\r
4567 /*global tinymce:false, window:false */\r
4568 \r
4569 tinymce.dom = {};\r
4570 \r
4571 (function(namespace, expando) {\r
4572         var w3cEventModel = !!document.addEventListener;\r
4573 \r
4574         function addEvent(target, name, callback, capture) {\r
4575                 if (target.addEventListener) {\r
4576                         target.addEventListener(name, callback, capture || false);\r
4577                 } else if (target.attachEvent) {\r
4578                         target.attachEvent('on' + name, callback);\r
4579                 }\r
4580         }\r
4581 \r
4582         function removeEvent(target, name, callback, capture) {\r
4583                 if (target.removeEventListener) {\r
4584                         target.removeEventListener(name, callback, capture || false);\r
4585                 } else if (target.detachEvent) {\r
4586                         target.detachEvent('on' + name, callback);\r
4587                 }\r
4588         }\r
4589 \r
4590         function fix(original_event, data) {\r
4591                 var name, event = data || {};\r
4592 \r
4593                 // Dummy function that gets replaced on the delegation state functions\r
4594                 function returnFalse() {\r
4595                         return false;\r
4596                 }\r
4597 \r
4598                 // Dummy function that gets replaced on the delegation state functions\r
4599                 function returnTrue() {\r
4600                         return true;\r
4601                 }\r
4602 \r
4603                 // Copy all properties from the original event\r
4604                 for (name in original_event) {\r
4605                         // layerX/layerY is deprecated in Chrome and produces a warning\r
4606                         if (name !== "layerX" && name !== "layerY") {\r
4607                                 event[name] = original_event[name];\r
4608                         }\r
4609                 }\r
4610 \r
4611                 // Normalize target IE uses srcElement\r
4612                 if (!event.target) {\r
4613                         event.target = event.srcElement || document;\r
4614                 }\r
4615 \r
4616                 // Add preventDefault method\r
4617                 event.preventDefault = function() {\r
4618                         event.isDefaultPrevented = returnTrue;\r
4619 \r
4620                         // Execute preventDefault on the original event object\r
4621                         if (original_event) {\r
4622                                 if (original_event.preventDefault) {\r
4623                                         original_event.preventDefault();\r
4624                                 } else {\r
4625                                         original_event.returnValue = false; // IE\r
4626                                 }\r
4627                         }\r
4628                 };\r
4629 \r
4630                 // Add stopPropagation\r
4631                 event.stopPropagation = function() {\r
4632                         event.isPropagationStopped = returnTrue;\r
4633 \r
4634                         // Execute stopPropagation on the original event object\r
4635                         if (original_event) {\r
4636                                 if (original_event.stopPropagation) {\r
4637                                         original_event.stopPropagation();\r
4638                                 } else {\r
4639                                         original_event.cancelBubble = true; // IE\r
4640                                 }\r
4641                          }\r
4642                 };\r
4643 \r
4644                 // Add stopImmediatePropagation\r
4645                 event.stopImmediatePropagation = function() {\r
4646                         event.isImmediatePropagationStopped = returnTrue;\r
4647                         event.stopPropagation();\r
4648                 };\r
4649 \r
4650                 // Add event delegation states\r
4651                 if (!event.isDefaultPrevented) {\r
4652                         event.isDefaultPrevented = returnFalse;\r
4653                         event.isPropagationStopped = returnFalse;\r
4654                         event.isImmediatePropagationStopped = returnFalse;\r
4655                 }\r
4656 \r
4657                 return event;\r
4658         }\r
4659 \r
4660         function bindOnReady(win, callback, event_utils) {\r
4661                 var doc = win.document, event = {type: 'ready'};\r
4662 \r
4663                 // Gets called when the DOM is ready\r
4664                 function readyHandler() {\r
4665                         if (!event_utils.domLoaded) {\r
4666                                 event_utils.domLoaded = true;\r
4667                                 callback(event);\r
4668                         }\r
4669                 }\r
4670 \r
4671                 // Page already loaded then fire it directly\r
4672                 if (doc.readyState == "complete") {\r
4673                         readyHandler();\r
4674                         return;\r
4675                 }\r
4676 \r
4677                 // Use W3C method\r
4678                 if (w3cEventModel) {\r
4679                         addEvent(win, 'DOMContentLoaded', readyHandler);\r
4680                 } else {\r
4681                         // Use IE method\r
4682                         addEvent(doc, "readystatechange", function() {\r
4683                                 if (doc.readyState === "complete") {\r
4684                                         removeEvent(doc, "readystatechange", arguments.callee);\r
4685                                         readyHandler();\r
4686                                 }\r
4687                         });\r
4688 \r
4689                         // Wait until we can scroll, when we can the DOM is initialized\r
4690                         if (doc.documentElement.doScroll && win === win.top) {\r
4691                                 (function() {\r
4692                                         try {\r
4693                                                 // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.\r
4694                                                 // http://javascript.nwbox.com/IEContentLoaded/\r
4695                                                 doc.documentElement.doScroll("left");\r
4696                                         } catch (ex) {\r
4697                                                 setTimeout(arguments.callee, 0);\r
4698                                                 return;\r
4699                                         }\r
4700 \r
4701                                         readyHandler();\r
4702                                 })();\r
4703                         }\r
4704                 }\r
4705 \r
4706                 // Fallback if any of the above methods should fail for some odd reason\r
4707                 addEvent(win, 'load', readyHandler);\r
4708         }\r
4709 \r
4710         function EventUtils(proxy) {\r
4711                 var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;\r
4712 \r
4713                 hasMouseEnterLeave = "onmouseenter" in document.documentElement;\r
4714                 hasFocusIn = "onfocusin" in document.documentElement;\r
4715                 mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};\r
4716                 count = 1;\r
4717 \r
4718                 // State if the DOMContentLoaded was executed or not\r
4719                 self.domLoaded = false;\r
4720                 self.events = events;\r
4721 \r
4722                 function executeHandlers(evt, id) {\r
4723                         var callbackList, i, l, callback;\r
4724 \r
4725                         callbackList = events[id][evt.type];\r
4726                         if (callbackList) {\r
4727                                 for (i = 0, l = callbackList.length; i < l; i++) {\r
4728                                         callback = callbackList[i];\r
4729                                         \r
4730                                         // Check if callback exists might be removed if a unbind is called inside the callback\r
4731                                         if (callback && callback.func.call(callback.scope, evt) === false) {\r
4732                                                 evt.preventDefault();\r
4733                                         }\r
4734 \r
4735                                         // Should we stop propagation to immediate listeners\r
4736                                         if (evt.isImmediatePropagationStopped()) {\r
4737                                                 return;\r
4738                                         }\r
4739                                 }\r
4740                         }\r
4741                 }\r
4742 \r
4743                 self.bind = function(target, names, callback, scope) {\r
4744                         var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;\r
4745 \r
4746                         // Native event handler function patches the event and executes the callbacks for the expando\r
4747                         function defaultNativeHandler(evt) {\r
4748                                 executeHandlers(fix(evt || win.event), id);\r
4749                         }\r
4750 \r
4751                         // Don't bind to text nodes or comments\r
4752                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4753                                 return;\r
4754                         }\r
4755 \r
4756                         // Create or get events id for the target\r
4757                         if (!target[expando]) {\r
4758                                 id = count++;\r
4759                                 target[expando] = id;\r
4760                                 events[id] = {};\r
4761                         } else {\r
4762                                 id = target[expando];\r
4763 \r
4764                                 if (!events[id]) {\r
4765                                         events[id] = {};\r
4766                                 }\r
4767                         }\r
4768 \r
4769                         // Setup the specified scope or use the target as a default\r
4770                         scope = scope || target;\r
4771 \r
4772                         // Split names and bind each event, enables you to bind multiple events with one call\r
4773                         names = names.split(' ');\r
4774                         i = names.length;\r
4775                         while (i--) {\r
4776                                 name = names[i];\r
4777                                 nativeHandler = defaultNativeHandler;\r
4778                                 fakeName = capture = false;\r
4779 \r
4780                                 // Use ready instead of DOMContentLoaded\r
4781                                 if (name === "DOMContentLoaded") {\r
4782                                         name = "ready";\r
4783                                 }\r
4784 \r
4785                                 // DOM is already ready\r
4786                                 if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {\r
4787                                         self.domLoaded = true;\r
4788                                         callback.call(scope, fix({type: name}));\r
4789                                         continue;\r
4790                                 }\r
4791 \r
4792                                 // Handle mouseenter/mouseleaver\r
4793                                 if (!hasMouseEnterLeave) {\r
4794                                         fakeName = mouseEnterLeave[name];\r
4795 \r
4796                                         if (fakeName) {\r
4797                                                 nativeHandler = function(evt) {\r
4798                                                         var current, related;\r
4799 \r
4800                                                         current = evt.currentTarget;\r
4801                                                         related = evt.relatedTarget;\r
4802 \r
4803                                                         // 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
4804                                                         if (related && current.contains) {\r
4805                                                                 // Use contains for performance\r
4806                                                                 related = current.contains(related);\r
4807                                                         } else {\r
4808                                                                 while (related && related !== current) {\r
4809                                                                         related = related.parentNode;\r
4810                                                                 }\r
4811                                                         }\r
4812 \r
4813                                                         // Fire fake event\r
4814                                                         if (!related) {\r
4815                                                                 evt = fix(evt || win.event);\r
4816                                                                 evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';\r
4817                                                                 evt.target = current;\r
4818                                                                 executeHandlers(evt, id);\r
4819                                                         }\r
4820                                                 };\r
4821                                         }\r
4822                                 }\r
4823 \r
4824                                 // Fake bubbeling of focusin/focusout\r
4825                                 if (!hasFocusIn && (name === "focusin" || name === "focusout")) {\r
4826                                         capture = true;\r
4827                                         fakeName = name === "focusin" ? "focus" : "blur";\r
4828                                         nativeHandler = function(evt) {\r
4829                                                 evt = fix(evt || win.event);\r
4830                                                 evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';\r
4831                                                 executeHandlers(evt, id);\r
4832                                         };\r
4833                                 }\r
4834 \r
4835                                 // Setup callback list and bind native event\r
4836                                 callbackList = events[id][name];\r
4837                                 if (!callbackList) {\r
4838                                         events[id][name] = callbackList = [{func: callback, scope: scope}];\r
4839                                         callbackList.fakeName = fakeName;\r
4840                                         callbackList.capture = capture;\r
4841 \r
4842                                         // Add the nativeHandler to the callback list so that we can later unbind it\r
4843                                         callbackList.nativeHandler = nativeHandler;\r
4844                                         if (!w3cEventModel) {\r
4845                                                 callbackList.proxyHandler = proxy(id);\r
4846                                         }\r
4847 \r
4848                                         // Check if the target has native events support\r
4849                                         if (name === "ready") {\r
4850                                                 bindOnReady(target, nativeHandler, self);\r
4851                                         } else {\r
4852                                                 addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);\r
4853                                         }\r
4854                                 } else {\r
4855                                         // If it already has an native handler then just push the callback\r
4856                                         callbackList.push({func: callback, scope: scope});\r
4857                                 }\r
4858                         }\r
4859 \r
4860                         target = callbackList = 0; // Clean memory for IE\r
4861 \r
4862                         return callback;\r
4863                 };\r
4864 \r
4865                 self.unbind = function(target, names, callback) {\r
4866                         var id, callbackList, i, ci, name, eventMap;\r
4867 \r
4868                         // Don't bind to text nodes or comments\r
4869                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4870                                 return self;\r
4871                         }\r
4872 \r
4873                         // Unbind event or events if the target has the expando\r
4874                         id = target[expando];\r
4875                         if (id) {\r
4876                                 eventMap = events[id];\r
4877 \r
4878                                 // Specific callback\r
4879                                 if (names) {\r
4880                                         names = names.split(' ');\r
4881                                         i = names.length;\r
4882                                         while (i--) {\r
4883                                                 name = names[i];\r
4884                                                 callbackList = eventMap[name];\r
4885 \r
4886                                                 // Unbind the event if it exists in the map\r
4887                                                 if (callbackList) {\r
4888                                                         // Remove specified callback\r
4889                                                         if (callback) {\r
4890                                                                 ci = callbackList.length;\r
4891                                                                 while (ci--) {\r
4892                                                                         if (callbackList[ci].func === callback) {\r
4893                                                                                 callbackList.splice(ci, 1);\r
4894                                                                         }\r
4895                                                                 }\r
4896                                                         }\r
4897 \r
4898                                                         // Remove all callbacks if there isn't a specified callback or there is no callbacks left\r
4899                                                         if (!callback || callbackList.length === 0) {\r
4900                                                                 delete eventMap[name];\r
4901                                                                 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);\r
4902                                                         }\r
4903                                                 }\r
4904                                         }\r
4905                                 } else {\r
4906                                         // All events for a specific element\r
4907                                         for (name in eventMap) {\r
4908                                                 callbackList = eventMap[name];\r
4909                                                 removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);\r
4910                                         }\r
4911 \r
4912                                         eventMap = {};\r
4913                                 }\r
4914 \r
4915                                 // Check if object is empty, if it isn't then we won't remove the expando map\r
4916                                 for (name in eventMap) {\r
4917                                         return self;\r
4918                                 }\r
4919 \r
4920                                 // Delete event object\r
4921                                 delete events[id];\r
4922 \r
4923                                 // Remove expando from target\r
4924                                 try {\r
4925                                         // IE will fail here since it can't delete properties from window\r
4926                                         delete target[expando];\r
4927                                 } catch (ex) {\r
4928                                         // IE will set it to null\r
4929                                         target[expando] = null;\r
4930                                 }\r
4931                         }\r
4932 \r
4933                         return self;\r
4934                 };\r
4935 \r
4936                 self.fire = function(target, name, args) {\r
4937                         var id, event;\r
4938 \r
4939                         // Don't bind to text nodes or comments\r
4940                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4941                                 return self;\r
4942                         }\r
4943 \r
4944                         // Build event object by patching the args\r
4945                         event = fix(null, args);\r
4946                         event.type = name;\r
4947 \r
4948                         do {\r
4949                                 // Found an expando that means there is listeners to execute\r
4950                                 id = target[expando];\r
4951                                 if (id) {\r
4952                                         executeHandlers(event, id);\r
4953                                 }\r
4954 \r
4955                                 // Walk up the DOM\r
4956                                 target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;\r
4957                         } while (target && !event.isPropagationStopped());\r
4958 \r
4959                         return self;\r
4960                 };\r
4961 \r
4962                 self.clean = function(target) {\r
4963                         var i, children, unbind = self.unbind;\r
4964         \r
4965                         // Don't bind to text nodes or comments\r
4966                         if (!target || target.nodeType === 3 || target.nodeType === 8) {\r
4967                                 return self;\r
4968                         }\r
4969 \r
4970                         // Unbind any element on the specificed target\r
4971                         if (target[expando]) {\r
4972                                 unbind(target);\r
4973                         }\r
4974 \r
4975                         // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children\r
4976                         if (!target.getElementsByTagName) {\r
4977                                 target = target.document;\r
4978                         }\r
4979 \r
4980                         // Remove events from each child element\r
4981                         if (target && target.getElementsByTagName) {\r
4982                                 unbind(target);\r
4983 \r
4984                                 children = target.getElementsByTagName('*');\r
4985                                 i = children.length;\r
4986                                 while (i--) {\r
4987                                         target = children[i];\r
4988 \r
4989                                         if (target[expando]) {\r
4990                                                 unbind(target);\r
4991                                         }\r
4992                                 }\r
4993                         }\r
4994 \r
4995                         return self;\r
4996                 };\r
4997 \r
4998                 self.callNativeHandler = function(id, evt) {\r
4999                         if (events) {\r
5000                                 events[id][evt.type].nativeHandler(evt);\r
5001                         }\r
5002                 };\r
5003 \r
5004                 self.destory = function() {\r
5005                         events = {};\r
5006                 };\r
5007 \r
5008                 // Legacy function calls\r
5009 \r
5010                 self.add = function(target, events, func, scope) {\r
5011                         // Old API supported direct ID assignment\r
5012                         if (typeof(target) === "string") {\r
5013                                 target = document.getElementById(target);\r
5014                         }\r
5015 \r
5016                         // Old API supported multiple targets\r
5017                         if (target && target instanceof Array) {\r
5018                                 var i = target.length;\r
5019 \r
5020                                 while (i--) {\r
5021                                         self.add(target[i], events, func, scope);\r
5022                                 }\r
5023 \r
5024                                 return;\r
5025                         }\r
5026 \r
5027                         // Old API called ready init\r
5028                         if (events === "init") {\r
5029                                 events = "ready";\r
5030                         }\r
5031 \r
5032                         return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);\r
5033                 };\r
5034 \r
5035                 self.remove = function(target, events, func, scope) {\r
5036                         if (!target) {\r
5037                                 return self;\r
5038                         }\r
5039 \r
5040                         // Old API supported direct ID assignment\r
5041                         if (typeof(target) === "string") {\r
5042                                 target = document.getElementById(target);\r
5043                         }\r
5044 \r
5045                         // Old API supported multiple targets\r
5046                         if (target instanceof Array) {\r
5047                                 var i = target.length;\r
5048 \r
5049                                 while (i--) {\r
5050                                         self.remove(target[i], events, func, scope);\r
5051                                 }\r
5052 \r
5053                                 return self;\r
5054                         }\r
5055 \r
5056                         return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);\r
5057                 };\r
5058 \r
5059                 self.clear = function(target) {\r
5060                         // Old API supported direct ID assignment\r
5061                         if (typeof(target) === "string") {\r
5062                                 target = document.getElementById(target);\r
5063                         }\r
5064 \r
5065                         return self.clean(target);\r
5066                 };\r
5067 \r
5068                 self.cancel = function(e) {\r
5069                         if (e) {\r
5070                                 self.prevent(e);\r
5071                                 self.stop(e);\r
5072                         }\r
5073 \r
5074                         return false;\r
5075                 };\r
5076 \r
5077                 self.prevent = function(e) {\r
5078                         if (!e.preventDefault) {\r
5079                                 e = fix(e);\r
5080                         }\r
5081 \r
5082                         e.preventDefault();\r
5083 \r
5084                         return false;\r
5085                 };\r
5086 \r
5087                 self.stop = function(e) {\r
5088                         if (!e.stopPropagation) {\r
5089                                 e = fix(e);\r
5090                         }\r
5091 \r
5092                         e.stopPropagation();\r
5093 \r
5094                         return false;\r
5095                 };\r
5096         }\r
5097 \r
5098         namespace.EventUtils = EventUtils;\r
5099 \r
5100         namespace.Event = new EventUtils(function(id) {\r
5101                 return function(evt) {\r
5102                         tinymce.dom.Event.callNativeHandler(id, evt);\r
5103                 };\r
5104         });\r
5105 \r
5106         // Bind ready event when tinymce script is loaded\r
5107         namespace.Event.bind(window, 'ready', function() {});\r
5108 \r
5109         namespace = 0;\r
5110 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando\r
5111 \r
5112 tinymce.dom.TreeWalker = function(start_node, root_node) {\r
5113         var node = start_node;\r
5114 \r
5115         function findSibling(node, start_name, sibling_name, shallow) {\r
5116                 var sibling, parent;\r
5117 \r
5118                 if (node) {\r
5119                         // Walk into nodes if it has a start\r
5120                         if (!shallow && node[start_name])\r
5121                                 return node[start_name];\r
5122 \r
5123                         // Return the sibling if it has one\r
5124                         if (node != root_node) {\r
5125                                 sibling = node[sibling_name];\r
5126                                 if (sibling)\r
5127                                         return sibling;\r
5128 \r
5129                                 // Walk up the parents to look for siblings\r
5130                                 for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {\r
5131                                         sibling = parent[sibling_name];\r
5132                                         if (sibling)\r
5133                                                 return sibling;\r
5134                                 }\r
5135                         }\r
5136                 }\r
5137         };\r
5138 \r
5139         this.current = function() {\r
5140                 return node;\r
5141         };\r
5142 \r
5143         this.next = function(shallow) {\r
5144                 return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));\r
5145         };\r
5146 \r
5147         this.prev = function(shallow) {\r
5148                 return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));\r
5149         };\r
5150 };\r
5151 \r
5152 (function(tinymce) {\r
5153         // Shorten names\r
5154         var each = tinymce.each,\r
5155                 is = tinymce.is,\r
5156                 isWebKit = tinymce.isWebKit,\r
5157                 isIE = tinymce.isIE,\r
5158                 Entities = tinymce.html.Entities,\r
5159                 simpleSelectorRe = /^([a-z0-9],?)+$/i,\r
5160                 whiteSpaceRegExp = /^[ \t\r\n]*$/;\r
5161 \r
5162         tinymce.create('tinymce.dom.DOMUtils', {\r
5163                 doc : null,\r
5164                 root : null,\r
5165                 files : null,\r
5166                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,\r
5167                 props : {\r
5168                         "for" : "htmlFor",\r
5169                         "class" : "className",\r
5170                         className : "className",\r
5171                         checked : "checked",\r
5172                         disabled : "disabled",\r
5173                         maxlength : "maxLength",\r
5174                         readonly : "readOnly",\r
5175                         selected : "selected",\r
5176                         value : "value",\r
5177                         id : "id",\r
5178                         name : "name",\r
5179                         type : "type"\r
5180                 },\r
5181 \r
5182                 DOMUtils : function(d, s) {\r
5183                         var t = this, globalStyle, name, blockElementsMap;\r
5184 \r
5185                         t.doc = d;\r
5186                         t.win = window;\r
5187                         t.files = {};\r
5188                         t.cssFlicker = false;\r
5189                         t.counter = 0;\r
5190                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;\r
5191                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;\r
5192                         t.hasOuterHTML = "outerHTML" in d.createElement("a");\r
5193 \r
5194                         t.settings = s = tinymce.extend({\r
5195                                 keep_values : false,\r
5196                                 hex_colors : 1\r
5197                         }, s);\r
5198                         \r
5199                         t.schema = s.schema;\r
5200                         t.styles = new tinymce.html.Styles({\r
5201                                 url_converter : s.url_converter,\r
5202                                 url_converter_scope : s.url_converter_scope\r
5203                         }, s.schema);\r
5204 \r
5205                         // Fix IE6SP2 flicker and check it failed for pre SP2\r
5206                         if (tinymce.isIE6) {\r
5207                                 try {\r
5208                                         d.execCommand('BackgroundImageCache', false, true);\r
5209                                 } catch (e) {\r
5210                                         t.cssFlicker = true;\r
5211                                 }\r
5212                         }\r
5213 \r
5214                         t.fixDoc(d);\r
5215                         t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;\r
5216                         tinymce.addUnload(t.destroy, t);\r
5217                         blockElementsMap = s.schema ? s.schema.getBlockElements() : {};\r
5218 \r
5219                         t.isBlock = function(node) {\r
5220                                 // Fix for #5446\r
5221                                 if (!node) {\r
5222                                         return false;\r
5223                                 }\r
5224 \r
5225                                 // This function is called in module pattern style since it might be executed with the wrong this scope\r
5226                                 var type = node.nodeType;\r
5227 \r
5228                                 // If it's a node then check the type and use the nodeName\r
5229                                 if (type)\r
5230                                         return !!(type === 1 && blockElementsMap[node.nodeName]);\r
5231 \r
5232                                 return !!blockElementsMap[node];\r
5233                         };\r
5234                 },\r
5235 \r
5236                 fixDoc: function(doc) {\r
5237                         var settings = this.settings, name;\r
5238 \r
5239                         if (isIE && settings.schema) {\r
5240                                 // Add missing HTML 4/5 elements to IE\r
5241                                 ('abbr article aside audio canvas ' +\r
5242                                 'details figcaption figure footer ' +\r
5243                                 'header hgroup mark menu meter nav ' +\r
5244                                 'output progress section summary ' +\r
5245                                 'time video').replace(/\w+/g, function(name) {\r
5246                                         doc.createElement(name);\r
5247                                 });\r
5248 \r
5249                                 // Create all custom elements\r
5250                                 for (name in settings.schema.getCustomElements()) {\r
5251                                         doc.createElement(name);\r
5252                                 }\r
5253                         }\r
5254                 },\r
5255 \r
5256                 clone: function(node, deep) {\r
5257                         var self = this, clone, doc;\r
5258 \r
5259                         // TODO: Add feature detection here in the future\r
5260                         if (!isIE || node.nodeType !== 1 || deep) {\r
5261                                 return node.cloneNode(deep);\r
5262                         }\r
5263 \r
5264                         doc = self.doc;\r
5265 \r
5266                         // Make a HTML5 safe shallow copy\r
5267                         if (!deep) {\r
5268                                 clone = doc.createElement(node.nodeName);\r
5269 \r
5270                                 // Copy attribs\r
5271                                 each(self.getAttribs(node), function(attr) {\r
5272                                         self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));\r
5273                                 });\r
5274 \r
5275                                 return clone;\r
5276                         }\r
5277 /*\r
5278                         // Setup HTML5 patched document fragment\r
5279                         if (!self.frag) {\r
5280                                 self.frag = doc.createDocumentFragment();\r
5281                                 self.fixDoc(self.frag);\r
5282                         }\r
5283 \r
5284                         // Make a deep copy by adding it to the document fragment then removing it this removed the :section\r
5285                         clone = doc.createElement('div');\r
5286                         self.frag.appendChild(clone);\r
5287                         clone.innerHTML = node.outerHTML;\r
5288                         self.frag.removeChild(clone);\r
5289 */\r
5290                         return clone.firstChild;\r
5291                 },\r
5292 \r
5293                 getRoot : function() {\r
5294                         var t = this, s = t.settings;\r
5295 \r
5296                         return (s && t.get(s.root_element)) || t.doc.body;\r
5297                 },\r
5298 \r
5299                 getViewPort : function(w) {\r
5300                         var d, b;\r
5301 \r
5302                         w = !w ? this.win : w;\r
5303                         d = w.document;\r
5304                         b = this.boxModel ? d.documentElement : d.body;\r
5305 \r
5306                         // Returns viewport size excluding scrollbars\r
5307                         return {\r
5308                                 x : w.pageXOffset || b.scrollLeft,\r
5309                                 y : w.pageYOffset || b.scrollTop,\r
5310                                 w : w.innerWidth || b.clientWidth,\r
5311                                 h : w.innerHeight || b.clientHeight\r
5312                         };\r
5313                 },\r
5314 \r
5315                 getRect : function(e) {\r
5316                         var p, t = this, sr;\r
5317 \r
5318                         e = t.get(e);\r
5319                         p = t.getPos(e);\r
5320                         sr = t.getSize(e);\r
5321 \r
5322                         return {\r
5323                                 x : p.x,\r
5324                                 y : p.y,\r
5325                                 w : sr.w,\r
5326                                 h : sr.h\r
5327                         };\r
5328                 },\r
5329 \r
5330                 getSize : function(e) {\r
5331                         var t = this, w, h;\r
5332 \r
5333                         e = t.get(e);\r
5334                         w = t.getStyle(e, 'width');\r
5335                         h = t.getStyle(e, 'height');\r
5336 \r
5337                         // Non pixel value, then force offset/clientWidth\r
5338                         if (w.indexOf('px') === -1)\r
5339                                 w = 0;\r
5340 \r
5341                         // Non pixel value, then force offset/clientWidth\r
5342                         if (h.indexOf('px') === -1)\r
5343                                 h = 0;\r
5344 \r
5345                         return {\r
5346                                 w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,\r
5347                                 h : parseInt(h, 10) || e.offsetHeight || e.clientHeight\r
5348                         };\r
5349                 },\r
5350 \r
5351                 getParent : function(n, f, r) {\r
5352                         return this.getParents(n, f, r, false);\r
5353                 },\r
5354 \r
5355                 getParents : function(n, f, r, c) {\r
5356                         var t = this, na, se = t.settings, o = [];\r
5357 \r
5358                         n = t.get(n);\r
5359                         c = c === undefined;\r
5360 \r
5361                         if (se.strict_root)\r
5362                                 r = r || t.getRoot();\r
5363 \r
5364                         // Wrap node name as func\r
5365                         if (is(f, 'string')) {\r
5366                                 na = f;\r
5367 \r
5368                                 if (f === '*') {\r
5369                                         f = function(n) {return n.nodeType == 1;};\r
5370                                 } else {\r
5371                                         f = function(n) {\r
5372                                                 return t.is(n, na);\r
5373                                         };\r
5374                                 }\r
5375                         }\r
5376 \r
5377                         while (n) {\r
5378                                 if (n == r || !n.nodeType || n.nodeType === 9)\r
5379                                         break;\r
5380 \r
5381                                 if (!f || f(n)) {\r
5382                                         if (c)\r
5383                                                 o.push(n);\r
5384                                         else\r
5385                                                 return n;\r
5386                                 }\r
5387 \r
5388                                 n = n.parentNode;\r
5389                         }\r
5390 \r
5391                         return c ? o : null;\r
5392                 },\r
5393 \r
5394                 get : function(e) {\r
5395                         var n;\r
5396 \r
5397                         if (e && this.doc && typeof(e) == 'string') {\r
5398                                 n = e;\r
5399                                 e = this.doc.getElementById(e);\r
5400 \r
5401                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick\r
5402                                 if (e && e.id !== n)\r
5403                                         return this.doc.getElementsByName(n)[1];\r
5404                         }\r
5405 \r
5406                         return e;\r
5407                 },\r
5408 \r
5409                 getNext : function(node, selector) {\r
5410                         return this._findSib(node, selector, 'nextSibling');\r
5411                 },\r
5412 \r
5413                 getPrev : function(node, selector) {\r
5414                         return this._findSib(node, selector, 'previousSibling');\r
5415                 },\r
5416 \r
5417 \r
5418                 select : function(pa, s) {\r
5419                         var t = this;\r
5420 \r
5421                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);\r
5422                 },\r
5423 \r
5424                 is : function(n, selector) {\r
5425                         var i;\r
5426 \r
5427                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance\r
5428                         if (n.length === undefined) {\r
5429                                 // Simple all selector\r
5430                                 if (selector === '*')\r
5431                                         return n.nodeType == 1;\r
5432 \r
5433                                 // Simple selector just elements\r
5434                                 if (simpleSelectorRe.test(selector)) {\r
5435                                         selector = selector.toLowerCase().split(/,/);\r
5436                                         n = n.nodeName.toLowerCase();\r
5437 \r
5438                                         for (i = selector.length - 1; i >= 0; i--) {\r
5439                                                 if (selector[i] == n)\r
5440                                                         return true;\r
5441                                         }\r
5442 \r
5443                                         return false;\r
5444                                 }\r
5445                         }\r
5446 \r
5447                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;\r
5448                 },\r
5449 \r
5450 \r
5451                 add : function(p, n, a, h, c) {\r
5452                         var t = this;\r
5453 \r
5454                         return this.run(p, function(p) {\r
5455                                 var e, k;\r
5456 \r
5457                                 e = is(n, 'string') ? t.doc.createElement(n) : n;\r
5458                                 t.setAttribs(e, a);\r
5459 \r
5460                                 if (h) {\r
5461                                         if (h.nodeType)\r
5462                                                 e.appendChild(h);\r
5463                                         else\r
5464                                                 t.setHTML(e, h);\r
5465                                 }\r
5466 \r
5467                                 return !c ? p.appendChild(e) : e;\r
5468                         });\r
5469                 },\r
5470 \r
5471                 create : function(n, a, h) {\r
5472                         return this.add(this.doc.createElement(n), n, a, h, 1);\r
5473                 },\r
5474 \r
5475                 createHTML : function(n, a, h) {\r
5476                         var o = '', t = this, k;\r
5477 \r
5478                         o += '<' + n;\r
5479 \r
5480                         for (k in a) {\r
5481                                 if (a.hasOwnProperty(k))\r
5482                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';\r
5483                         }\r
5484 \r
5485                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime\r
5486                         if (typeof(h) != "undefined")\r
5487                                 return o + '>' + h + '</' + n + '>';\r
5488 \r
5489                         return o + ' />';\r
5490                 },\r
5491 \r
5492                 remove : function(node, keep_children) {\r
5493                         return this.run(node, function(node) {\r
5494                                 var child, parent = node.parentNode;\r
5495 \r
5496                                 if (!parent)\r
5497                                         return null;\r
5498 \r
5499                                 if (keep_children) {\r
5500                                         while (child = node.firstChild) {\r
5501                                                 // IE 8 will crash if you don't remove completely empty text nodes\r
5502                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)\r
5503                                                         parent.insertBefore(child, node);\r
5504                                                 else\r
5505                                                         node.removeChild(child);\r
5506                                         }\r
5507                                 }\r
5508 \r
5509                                 return parent.removeChild(node);\r
5510                         });\r
5511                 },\r
5512 \r
5513                 setStyle : function(n, na, v) {\r
5514                         var t = this;\r
5515 \r
5516                         return t.run(n, function(e) {\r
5517                                 var s, i;\r
5518 \r
5519                                 s = e.style;\r
5520 \r
5521                                 // Camelcase it, if needed\r
5522                                 na = na.replace(/-(\D)/g, function(a, b){\r
5523                                         return b.toUpperCase();\r
5524                                 });\r
5525 \r
5526                                 // Default px suffix on these\r
5527                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))\r
5528                                         v += 'px';\r
5529 \r
5530                                 switch (na) {\r
5531                                         case 'opacity':\r
5532                                                 // IE specific opacity\r
5533                                                 if (isIE) {\r
5534                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";\r
5535 \r
5536                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)\r
5537                                                                 s.display = 'inline-block';\r
5538                                                 }\r
5539 \r
5540                                                 // Fix for older browsers\r
5541                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';\r
5542                                                 break;\r
5543 \r
5544                                         case 'float':\r
5545                                                 isIE ? s.styleFloat = v : s.cssFloat = v;\r
5546                                                 break;\r
5547                                         \r
5548                                         default:\r
5549                                                 s[na] = v || '';\r
5550                                 }\r
5551 \r
5552                                 // Force update of the style data\r
5553                                 if (t.settings.update_styles)\r
5554                                         t.setAttrib(e, 'data-mce-style');\r
5555                         });\r
5556                 },\r
5557 \r
5558                 getStyle : function(n, na, c) {\r
5559                         n = this.get(n);\r
5560 \r
5561                         if (!n)\r
5562                                 return;\r
5563 \r
5564                         // Gecko\r
5565                         if (this.doc.defaultView && c) {\r
5566                                 // Remove camelcase\r
5567                                 na = na.replace(/[A-Z]/g, function(a){\r
5568                                         return '-' + a;\r
5569                                 });\r
5570 \r
5571                                 try {\r
5572                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);\r
5573                                 } catch (ex) {\r
5574                                         // Old safari might fail\r
5575                                         return null;\r
5576                                 }\r
5577                         }\r
5578 \r
5579                         // Camelcase it, if needed\r
5580                         na = na.replace(/-(\D)/g, function(a, b){\r
5581                                 return b.toUpperCase();\r
5582                         });\r
5583 \r
5584                         if (na == 'float')\r
5585                                 na = isIE ? 'styleFloat' : 'cssFloat';\r
5586 \r
5587                         // IE & Opera\r
5588                         if (n.currentStyle && c)\r
5589                                 return n.currentStyle[na];\r
5590 \r
5591                         return n.style ? n.style[na] : undefined;\r
5592                 },\r
5593 \r
5594                 setStyles : function(e, o) {\r
5595                         var t = this, s = t.settings, ol;\r
5596 \r
5597                         ol = s.update_styles;\r
5598                         s.update_styles = 0;\r
5599 \r
5600                         each(o, function(v, n) {\r
5601                                 t.setStyle(e, n, v);\r
5602                         });\r
5603 \r
5604                         // Update style info\r
5605                         s.update_styles = ol;\r
5606                         if (s.update_styles)\r
5607                                 t.setAttrib(e, s.cssText);\r
5608                 },\r
5609 \r
5610                 removeAllAttribs: function(e) {\r
5611                         return this.run(e, function(e) {\r
5612                                 var i, attrs = e.attributes;\r
5613                                 for (i = attrs.length - 1; i >= 0; i--) {\r
5614                                         e.removeAttributeNode(attrs.item(i));\r
5615                                 }\r
5616                         });\r
5617                 },\r
5618 \r
5619                 setAttrib : function(e, n, v) {\r
5620                         var t = this;\r
5621 \r
5622                         // Whats the point\r
5623                         if (!e || !n)\r
5624                                 return;\r
5625 \r
5626                         // Strict XML mode\r
5627                         if (t.settings.strict)\r
5628                                 n = n.toLowerCase();\r
5629 \r
5630                         return this.run(e, function(e) {\r
5631                                 var s = t.settings;\r
5632                                 var originalValue = e.getAttribute(n);\r
5633                                 if (v !== null) {\r
5634                                         switch (n) {\r
5635                                                 case "style":\r
5636                                                         if (!is(v, 'string')) {\r
5637                                                                 each(v, function(v, n) {\r
5638                                                                         t.setStyle(e, n, v);\r
5639                                                                 });\r
5640 \r
5641                                                                 return;\r
5642                                                         }\r
5643 \r
5644                                                         // No mce_style for elements with these since they might get resized by the user\r
5645                                                         if (s.keep_values) {\r
5646                                                                 if (v && !t._isRes(v))\r
5647                                                                         e.setAttribute('data-mce-style', v, 2);\r
5648                                                                 else\r
5649                                                                         e.removeAttribute('data-mce-style', 2);\r
5650                                                         }\r
5651 \r
5652                                                         e.style.cssText = v;\r
5653                                                         break;\r
5654 \r
5655                                                 case "class":\r
5656                                                         e.className = v || ''; // Fix IE null bug\r
5657                                                         break;\r
5658 \r
5659                                                 case "src":\r
5660                                                 case "href":\r
5661                                                         if (s.keep_values) {\r
5662                                                                 if (s.url_converter)\r
5663                                                                         v = s.url_converter.call(s.url_converter_scope || t, v, n, e);\r
5664 \r
5665                                                                 t.setAttrib(e, 'data-mce-' + n, v, 2);\r
5666                                                         }\r
5667 \r
5668                                                         break;\r
5669 \r
5670                                                 case "shape":\r
5671                                                         e.setAttribute('data-mce-style', v);\r
5672                                                         break;\r
5673                                         }\r
5674                                 }\r
5675                                 if (is(v) && v !== null && v.length !== 0)\r
5676                                         e.setAttribute(n, '' + v, 2);\r
5677                                 else\r
5678                                         e.removeAttribute(n, 2);\r
5679 \r
5680                                 // fire onChangeAttrib event for attributes that have changed\r
5681                                 if (tinyMCE.activeEditor && originalValue != v) {\r
5682                                         var ed = tinyMCE.activeEditor;\r
5683                                         ed.onSetAttrib.dispatch(ed, e, n, v);\r
5684                                 }\r
5685                         });\r
5686                 },\r
5687 \r
5688                 setAttribs : function(e, o) {\r
5689                         var t = this;\r
5690 \r
5691                         return this.run(e, function(e) {\r
5692                                 each(o, function(v, n) {\r
5693                                         t.setAttrib(e, n, v);\r
5694                                 });\r
5695                         });\r
5696                 },\r
5697 \r
5698                 getAttrib : function(e, n, dv) {\r
5699                         var v, t = this, undef;\r
5700 \r
5701                         e = t.get(e);\r
5702 \r
5703                         if (!e || e.nodeType !== 1)\r
5704                                 return dv === undef ? false : dv;\r
5705 \r
5706                         if (!is(dv))\r
5707                                 dv = '';\r
5708 \r
5709                         // Try the mce variant for these\r
5710                         if (/^(src|href|style|coords|shape)$/.test(n)) {\r
5711                                 v = e.getAttribute("data-mce-" + n);\r
5712 \r
5713                                 if (v)\r
5714                                         return v;\r
5715                         }\r
5716 \r
5717                         if (isIE && t.props[n]) {\r
5718                                 v = e[t.props[n]];\r
5719                                 v = v && v.nodeValue ? v.nodeValue : v;\r
5720                         }\r
5721 \r
5722                         if (!v)\r
5723                                 v = e.getAttribute(n, 2);\r
5724 \r
5725                         // Check boolean attribs\r
5726                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {\r
5727                                 if (e[t.props[n]] === true && v === '')\r
5728                                         return n;\r
5729 \r
5730                                 return v ? n : '';\r
5731                         }\r
5732 \r
5733                         // Inner input elements will override attributes on form elements\r
5734                         if (e.nodeName === "FORM" && e.getAttributeNode(n))\r
5735                                 return e.getAttributeNode(n).nodeValue;\r
5736 \r
5737                         if (n === 'style') {\r
5738                                 v = v || e.style.cssText;\r
5739 \r
5740                                 if (v) {\r
5741                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);\r
5742 \r
5743                                         if (t.settings.keep_values && !t._isRes(v))\r
5744                                                 e.setAttribute('data-mce-style', v);\r
5745                                 }\r
5746                         }\r
5747 \r
5748                         // Remove Apple and WebKit stuff\r
5749                         if (isWebKit && n === "class" && v)\r
5750                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');\r
5751 \r
5752                         // Handle IE issues\r
5753                         if (isIE) {\r
5754                                 switch (n) {\r
5755                                         case 'rowspan':\r
5756                                         case 'colspan':\r
5757                                                 // IE returns 1 as default value\r
5758                                                 if (v === 1)\r
5759                                                         v = '';\r
5760 \r
5761                                                 break;\r
5762 \r
5763                                         case 'size':\r
5764                                                 // IE returns +0 as default value for size\r
5765                                                 if (v === '+0' || v === 20 || v === 0)\r
5766                                                         v = '';\r
5767 \r
5768                                                 break;\r
5769 \r
5770                                         case 'width':\r
5771                                         case 'height':\r
5772                                         case 'vspace':\r
5773                                         case 'checked':\r
5774                                         case 'disabled':\r
5775                                         case 'readonly':\r
5776                                                 if (v === 0)\r
5777                                                         v = '';\r
5778 \r
5779                                                 break;\r
5780 \r
5781                                         case 'hspace':\r
5782                                                 // IE returns -1 as default value\r
5783                                                 if (v === -1)\r
5784                                                         v = '';\r
5785 \r
5786                                                 break;\r
5787 \r
5788                                         case 'maxlength':\r
5789                                         case 'tabindex':\r
5790                                                 // IE returns default value\r
5791                                                 if (v === 32768 || v === 2147483647 || v === '32768')\r
5792                                                         v = '';\r
5793 \r
5794                                                 break;\r
5795 \r
5796                                         case 'multiple':\r
5797                                         case 'compact':\r
5798                                         case 'noshade':\r
5799                                         case 'nowrap':\r
5800                                                 if (v === 65535)\r
5801                                                         return n;\r
5802 \r
5803                                                 return dv;\r
5804 \r
5805                                         case 'shape':\r
5806                                                 v = v.toLowerCase();\r
5807                                                 break;\r
5808 \r
5809                                         default:\r
5810                                                 // IE has odd anonymous function for event attributes\r
5811                                                 if (n.indexOf('on') === 0 && v)\r
5812                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);\r
5813                                 }\r
5814                         }\r
5815 \r
5816                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;\r
5817                 },\r
5818 \r
5819                 getPos : function(n, ro) {\r
5820                         var t = this, x = 0, y = 0, e, d = t.doc, r;\r
5821 \r
5822                         n = t.get(n);\r
5823                         ro = ro || d.body;\r
5824 \r
5825                         if (n) {\r
5826                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes\r
5827                                 if (n.getBoundingClientRect) {\r
5828                                         n = n.getBoundingClientRect();\r
5829                                         e = t.boxModel ? d.documentElement : d.body;\r
5830 \r
5831                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit\r
5832                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position\r
5833                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;\r
5834                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;\r
5835 \r
5836                                         return {x : x, y : y};\r
5837                                 }\r
5838 \r
5839                                 r = n;\r
5840                                 while (r && r != ro && r.nodeType) {\r
5841                                         x += r.offsetLeft || 0;\r
5842                                         y += r.offsetTop || 0;\r
5843                                         r = r.offsetParent;\r
5844                                 }\r
5845 \r
5846                                 r = n.parentNode;\r
5847                                 while (r && r != ro && r.nodeType) {\r
5848                                         x -= r.scrollLeft || 0;\r
5849                                         y -= r.scrollTop || 0;\r
5850                                         r = r.parentNode;\r
5851                                 }\r
5852                         }\r
5853 \r
5854                         return {x : x, y : y};\r
5855                 },\r
5856 \r
5857                 parseStyle : function(st) {\r
5858                         return this.styles.parse(st);\r
5859                 },\r
5860 \r
5861                 serializeStyle : function(o, name) {\r
5862                         return this.styles.serialize(o, name);\r
5863                 },\r
5864 \r
5865                 addStyle: function(cssText) {\r
5866                         var doc = this.doc, head;\r
5867 \r
5868                         // Create style element if needed\r
5869                         styleElm = doc.getElementById('mceDefaultStyles');\r
5870                         if (!styleElm) {\r
5871                                 styleElm = doc.createElement('style'),\r
5872                                 styleElm.id = 'mceDefaultStyles';\r
5873                                 styleElm.type = 'text/css';\r
5874 \r
5875                                 head = doc.getElementsByTagName('head')[0];\r
5876                                 if (head.firstChild) {\r
5877                                         head.insertBefore(styleElm, head.firstChild);\r
5878                                 } else {\r
5879                                         head.appendChild(styleElm);\r
5880                                 }\r
5881                         }\r
5882 \r
5883                         // Append style data to old or new style element\r
5884                         if (styleElm.styleSheet) {\r
5885                                 styleElm.styleSheet.cssText += cssText;\r
5886                         } else {\r
5887                                 styleElm.appendChild(doc.createTextNode(cssText));\r
5888                         }\r
5889                 },\r
5890 \r
5891                 loadCSS : function(u) {\r
5892                         var t = this, d = t.doc, head;\r
5893 \r
5894                         if (!u)\r
5895                                 u = '';\r
5896 \r
5897                         head = d.getElementsByTagName('head')[0];\r
5898 \r
5899                         each(u.split(','), function(u) {\r
5900                                 var link;\r
5901 \r
5902                                 if (t.files[u])\r
5903                                         return;\r
5904 \r
5905                                 t.files[u] = true;\r
5906                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});\r
5907 \r
5908                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug\r
5909                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading\r
5910                                 // It's ugly but it seems to work fine.\r
5911                                 if (isIE && d.documentMode && d.recalc) {\r
5912                                         link.onload = function() {\r
5913                                                 if (d.recalc)\r
5914                                                         d.recalc();\r
5915 \r
5916                                                 link.onload = null;\r
5917                                         };\r
5918                                 }\r
5919 \r
5920                                 head.appendChild(link);\r
5921                         });\r
5922                 },\r
5923 \r
5924                 addClass : function(e, c) {\r
5925                         return this.run(e, function(e) {\r
5926                                 var o;\r
5927 \r
5928                                 if (!c)\r
5929                                         return 0;\r
5930 \r
5931                                 if (this.hasClass(e, c))\r
5932                                         return e.className;\r
5933 \r
5934                                 o = this.removeClass(e, c);\r
5935 \r
5936                                 return e.className = (o != '' ? (o + ' ') : '') + c;\r
5937                         });\r
5938                 },\r
5939 \r
5940                 removeClass : function(e, c) {\r
5941                         var t = this, re;\r
5942 \r
5943                         return t.run(e, function(e) {\r
5944                                 var v;\r
5945 \r
5946                                 if (t.hasClass(e, c)) {\r
5947                                         if (!re)\r
5948                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");\r
5949 \r
5950                                         v = e.className.replace(re, ' ');\r
5951                                         v = tinymce.trim(v != ' ' ? v : '');\r
5952 \r
5953                                         e.className = v;\r
5954 \r
5955                                         // Empty class attr\r
5956                                         if (!v) {\r
5957                                                 e.removeAttribute('class');\r
5958                                                 e.removeAttribute('className');\r
5959                                         }\r
5960 \r
5961                                         return v;\r
5962                                 }\r
5963 \r
5964                                 return e.className;\r
5965                         });\r
5966                 },\r
5967 \r
5968                 hasClass : function(n, c) {\r
5969                         n = this.get(n);\r
5970 \r
5971                         if (!n || !c)\r
5972                                 return false;\r
5973 \r
5974                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;\r
5975                 },\r
5976 \r
5977                 show : function(e) {\r
5978                         return this.setStyle(e, 'display', 'block');\r
5979                 },\r
5980 \r
5981                 hide : function(e) {\r
5982                         return this.setStyle(e, 'display', 'none');\r
5983                 },\r
5984 \r
5985                 isHidden : function(e) {\r
5986                         e = this.get(e);\r
5987 \r
5988                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';\r
5989                 },\r
5990 \r
5991                 uniqueId : function(p) {\r
5992                         return (!p ? 'mce_' : p) + (this.counter++);\r
5993                 },\r
5994 \r
5995                 setHTML : function(element, html) {\r
5996                         var self = this;\r
5997 \r
5998                         return self.run(element, function(element) {\r
5999                                 if (isIE) {\r
6000                                         // Remove all child nodes, IE keeps empty text nodes in DOM\r
6001                                         while (element.firstChild)\r
6002                                                 element.removeChild(element.firstChild);\r
6003 \r
6004                                         try {\r
6005                                                 // IE will remove comments from the beginning\r
6006                                                 // unless you padd the contents with something\r
6007                                                 element.innerHTML = '<br />' + html;\r
6008                                                 element.removeChild(element.firstChild);\r
6009                                         } catch (ex) {\r
6010                                                 // 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
6011                                                 // This seems to fix this problem\r
6012 \r
6013                                                 // Create new div with HTML contents and a BR infront to keep comments\r
6014                                                 var newElement = self.create('div');\r
6015                                                 newElement.innerHTML = '<br />' + html;\r
6016 \r
6017                                                 // Add all children from div to target\r
6018                                                 each (tinymce.grep(newElement.childNodes), function(node, i) {\r
6019                                                         // Skip br element\r
6020                                                         if (i && element.canHaveHTML)\r
6021                                                                 element.appendChild(node);\r
6022                                                 });\r
6023                                         }\r
6024                                 } else\r
6025                                         element.innerHTML = html;\r
6026 \r
6027                                 return html;\r
6028                         });\r
6029                 },\r
6030 \r
6031                 getOuterHTML : function(elm) {\r
6032                         var doc, self = this;\r
6033 \r
6034                         elm = self.get(elm);\r
6035 \r
6036                         if (!elm)\r
6037                                 return null;\r
6038 \r
6039                         if (elm.nodeType === 1 && self.hasOuterHTML)\r
6040                                 return elm.outerHTML;\r
6041 \r
6042                         doc = (elm.ownerDocument || self.doc).createElement("body");\r
6043                         doc.appendChild(elm.cloneNode(true));\r
6044 \r
6045                         return doc.innerHTML;\r
6046                 },\r
6047 \r
6048                 setOuterHTML : function(e, h, d) {\r
6049                         var t = this;\r
6050 \r
6051                         function setHTML(e, h, d) {\r
6052                                 var n, tp;\r
6053 \r
6054                                 tp = d.createElement("body");\r
6055                                 tp.innerHTML = h;\r
6056 \r
6057                                 n = tp.lastChild;\r
6058                                 while (n) {\r
6059                                         t.insertAfter(n.cloneNode(true), e);\r
6060                                         n = n.previousSibling;\r
6061                                 }\r
6062 \r
6063                                 t.remove(e);\r
6064                         };\r
6065 \r
6066                         return this.run(e, function(e) {\r
6067                                 e = t.get(e);\r
6068 \r
6069                                 // Only set HTML on elements\r
6070                                 if (e.nodeType == 1) {\r
6071                                         d = d || e.ownerDocument || t.doc;\r
6072 \r
6073                                         if (isIE) {\r
6074                                                 try {\r
6075                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error\r
6076                                                         if (isIE && e.nodeType == 1)\r
6077                                                                 e.outerHTML = h;\r
6078                                                         else\r
6079                                                                 setHTML(e, h, d);\r
6080                                                 } catch (ex) {\r
6081                                                         // Fix for unknown runtime error\r
6082                                                         setHTML(e, h, d);\r
6083                                                 }\r
6084                                         } else\r
6085                                                 setHTML(e, h, d);\r
6086                                 }\r
6087                         });\r
6088                 },\r
6089 \r
6090                 decode : Entities.decode,\r
6091 \r
6092                 encode : Entities.encodeAllRaw,\r
6093 \r
6094                 insertAfter : function(node, reference_node) {\r
6095                         reference_node = this.get(reference_node);\r
6096 \r
6097                         return this.run(node, function(node) {\r
6098                                 var parent, nextSibling;\r
6099 \r
6100                                 parent = reference_node.parentNode;\r
6101                                 nextSibling = reference_node.nextSibling;\r
6102 \r
6103                                 if (nextSibling)\r
6104                                         parent.insertBefore(node, nextSibling);\r
6105                                 else\r
6106                                         parent.appendChild(node);\r
6107 \r
6108                                 return node;\r
6109                         });\r
6110                 },\r
6111 \r
6112                 replace : function(n, o, k) {\r
6113                         var t = this;\r
6114 \r
6115                         if (is(o, 'array'))\r
6116                                 n = n.cloneNode(true);\r
6117 \r
6118                         return t.run(o, function(o) {\r
6119                                 if (k) {\r
6120                                         each(tinymce.grep(o.childNodes), function(c) {\r
6121                                                 n.appendChild(c);\r
6122                                         });\r
6123                                 }\r
6124 \r
6125                                 return o.parentNode.replaceChild(n, o);\r
6126                         });\r
6127                 },\r
6128 \r
6129                 rename : function(elm, name) {\r
6130                         var t = this, newElm;\r
6131 \r
6132                         if (elm.nodeName != name.toUpperCase()) {\r
6133                                 // Rename block element\r
6134                                 newElm = t.create(name);\r
6135 \r
6136                                 // Copy attribs to new block\r
6137                                 each(t.getAttribs(elm), function(attr_node) {\r
6138                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));\r
6139                                 });\r
6140 \r
6141                                 // Replace block\r
6142                                 t.replace(newElm, elm, 1);\r
6143                         }\r
6144 \r
6145                         return newElm || elm;\r
6146                 },\r
6147 \r
6148                 findCommonAncestor : function(a, b) {\r
6149                         var ps = a, pe;\r
6150 \r
6151                         while (ps) {\r
6152                                 pe = b;\r
6153 \r
6154                                 while (pe && ps != pe)\r
6155                                         pe = pe.parentNode;\r
6156 \r
6157                                 if (ps == pe)\r
6158                                         break;\r
6159 \r
6160                                 ps = ps.parentNode;\r
6161                         }\r
6162 \r
6163                         if (!ps && a.ownerDocument)\r
6164                                 return a.ownerDocument.documentElement;\r
6165 \r
6166                         return ps;\r
6167                 },\r
6168 \r
6169                 toHex : function(s) {\r
6170                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);\r
6171 \r
6172                         function hex(s) {\r
6173                                 s = parseInt(s, 10).toString(16);\r
6174 \r
6175                                 return s.length > 1 ? s : '0' + s; // 0 -> 00\r
6176                         };\r
6177 \r
6178                         if (c) {\r
6179                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);\r
6180 \r
6181                                 return s;\r
6182                         }\r
6183 \r
6184                         return s;\r
6185                 },\r
6186 \r
6187                 getClasses : function() {\r
6188                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;\r
6189 \r
6190                         if (t.classes)\r
6191                                 return t.classes;\r
6192 \r
6193                         function addClasses(s) {\r
6194                                 // IE style imports\r
6195                                 each(s.imports, function(r) {\r
6196                                         addClasses(r);\r
6197                                 });\r
6198 \r
6199                                 each(s.cssRules || s.rules, function(r) {\r
6200                                         // Real type or fake it on IE\r
6201                                         switch (r.type || 1) {\r
6202                                                 // Rule\r
6203                                                 case 1:\r
6204                                                         if (r.selectorText) {\r
6205                                                                 each(r.selectorText.split(','), function(v) {\r
6206                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");\r
6207 \r
6208                                                                         // Is internal or it doesn't contain a class\r
6209                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))\r
6210                                                                                 return;\r
6211 \r
6212                                                                         // Remove everything but class name\r
6213                                                                         ov = v;\r
6214                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);\r
6215 \r
6216                                                                         // Filter classes\r
6217                                                                         if (f && !(v = f(v, ov)))\r
6218                                                                                 return;\r
6219 \r
6220                                                                         if (!lo[v]) {\r
6221                                                                                 cl.push({'class' : v});\r
6222                                                                                 lo[v] = 1;\r
6223                                                                         }\r
6224                                                                 });\r
6225                                                         }\r
6226                                                         break;\r
6227 \r
6228                                                 // Import\r
6229                                                 case 3:\r
6230                                                         addClasses(r.styleSheet);\r
6231                                                         break;\r
6232                                         }\r
6233                                 });\r
6234                         };\r
6235 \r
6236                         try {\r
6237                                 each(t.doc.styleSheets, addClasses);\r
6238                         } catch (ex) {\r
6239                                 // Ignore\r
6240                         }\r
6241 \r
6242                         if (cl.length > 0)\r
6243                                 t.classes = cl;\r
6244 \r
6245                         return cl;\r
6246                 },\r
6247 \r
6248                 run : function(e, f, s) {\r
6249                         var t = this, o;\r
6250 \r
6251                         if (t.doc && typeof(e) === 'string')\r
6252                                 e = t.get(e);\r
6253 \r
6254                         if (!e)\r
6255                                 return false;\r
6256 \r
6257                         s = s || this;\r
6258                         if (!e.nodeType && (e.length || e.length === 0)) {\r
6259                                 o = [];\r
6260 \r
6261                                 each(e, function(e, i) {\r
6262                                         if (e) {\r
6263                                                 if (typeof(e) == 'string')\r
6264                                                         e = t.doc.getElementById(e);\r
6265 \r
6266                                                 o.push(f.call(s, e, i));\r
6267                                         }\r
6268                                 });\r
6269 \r
6270                                 return o;\r
6271                         }\r
6272 \r
6273                         return f.call(s, e);\r
6274                 },\r
6275 \r
6276                 getAttribs : function(n) {\r
6277                         var o;\r
6278 \r
6279                         n = this.get(n);\r
6280 \r
6281                         if (!n)\r
6282                                 return [];\r
6283 \r
6284                         if (isIE) {\r
6285                                 o = [];\r
6286 \r
6287                                 // Object will throw exception in IE\r
6288                                 if (n.nodeName == 'OBJECT')\r
6289                                         return n.attributes;\r
6290 \r
6291                                 // IE doesn't keep the selected attribute if you clone option elements\r
6292                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))\r
6293                                         o.push({specified : 1, nodeName : 'selected'});\r
6294 \r
6295                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time\r
6296                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {\r
6297                                         o.push({specified : 1, nodeName : a});\r
6298                                 });\r
6299 \r
6300                                 return o;\r
6301                         }\r
6302 \r
6303                         return n.attributes;\r
6304                 },\r
6305 \r
6306                 isEmpty : function(node, elements) {\r
6307                         var self = this, i, attributes, type, walker, name, brCount = 0;\r
6308 \r
6309                         node = node.firstChild;\r
6310                         if (node) {\r
6311                                 walker = new tinymce.dom.TreeWalker(node, node.parentNode);\r
6312                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;\r
6313 \r
6314                                 do {\r
6315                                         type = node.nodeType;\r
6316 \r
6317                                         if (type === 1) {\r
6318                                                 // Ignore bogus elements\r
6319                                                 if (node.getAttribute('data-mce-bogus'))\r
6320                                                         continue;\r
6321 \r
6322                                                 // Keep empty elements like <img />\r
6323                                                 name = node.nodeName.toLowerCase();\r
6324                                                 if (elements && elements[name]) {\r
6325                                                         // Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>\r
6326                                                         if (name === 'br') {\r
6327                                                                 brCount++;\r
6328                                                                 continue;\r
6329                                                         }\r
6330 \r
6331                                                         return false;\r
6332                                                 }\r
6333 \r
6334                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>\r
6335                                                 attributes = self.getAttribs(node);\r
6336                                                 i = node.attributes.length;\r
6337                                                 while (i--) {\r
6338                                                         name = node.attributes[i].nodeName;\r
6339                                                         if (name === "name" || name === 'data-mce-bookmark')\r
6340                                                                 return false;\r
6341                                                 }\r
6342                                         }\r
6343 \r
6344                                         // Keep comment nodes\r
6345                                         if (type == 8)\r
6346                                                 return false;\r
6347 \r
6348                                         // Keep non whitespace text nodes\r
6349                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))\r
6350                                                 return false;\r
6351                                 } while (node = walker.next());\r
6352                         }\r
6353 \r
6354                         return brCount <= 1;\r
6355                 },\r
6356 \r
6357                 destroy : function(s) {\r
6358                         var t = this;\r
6359 \r
6360                         t.win = t.doc = t.root = t.events = t.frag = null;\r
6361 \r
6362                         // Manual destroy then remove unload handler\r
6363                         if (!s)\r
6364                                 tinymce.removeUnload(t.destroy);\r
6365                 },\r
6366 \r
6367                 createRng : function() {\r
6368                         var d = this.doc;\r
6369 \r
6370                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);\r
6371                 },\r
6372 \r
6373                 nodeIndex : function(node, normalized) {\r
6374                         var idx = 0, lastNodeType, lastNode, nodeType;\r
6375 \r
6376                         if (node) {\r
6377                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {\r
6378                                         nodeType = node.nodeType;\r
6379 \r
6380                                         // Normalize text nodes\r
6381                                         if (normalized && nodeType == 3) {\r
6382                                                 if (nodeType == lastNodeType || !node.nodeValue.length)\r
6383                                                         continue;\r
6384                                         }\r
6385                                         idx++;\r
6386                                         lastNodeType = nodeType;\r
6387                                 }\r
6388                         }\r
6389 \r
6390                         return idx;\r
6391                 },\r
6392 \r
6393                 split : function(pe, e, re) {\r
6394                         var t = this, r = t.createRng(), bef, aft, pa;\r
6395 \r
6396                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense\r
6397                         // but we don't want that in our code since it serves no purpose for the end user\r
6398                         // For example if this is chopped:\r
6399                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>\r
6400                         // would produce:\r
6401                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>\r
6402                         // this function will then trim of empty edges and produce:\r
6403                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>\r
6404                         function trim(node) {\r
6405                                 var i, children = node.childNodes, type = node.nodeType;\r
6406 \r
6407                                 function surroundedBySpans(node) {\r
6408                                         var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';\r
6409                                         var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';\r
6410                                         return previousIsSpan && nextIsSpan;\r
6411                                 }\r
6412 \r
6413                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')\r
6414                                         return;\r
6415 \r
6416                                 for (i = children.length - 1; i >= 0; i--)\r
6417                                         trim(children[i]);\r
6418 \r
6419                                 if (type != 9) {\r
6420                                         // Keep non whitespace text nodes\r
6421                                         if (type == 3 && node.nodeValue.length > 0) {\r
6422                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"\r
6423                                                 // Also keep text nodes with only spaces if surrounded by spans.\r
6424                                                 // eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b\r
6425                                                 var trimmedLength = tinymce.trim(node.nodeValue).length;\r
6426                                                 if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))\r
6427                                                         return;\r
6428                                         } else if (type == 1) {\r
6429                                                 // If the only child is a bookmark then move it up\r
6430                                                 children = node.childNodes;\r
6431                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')\r
6432                                                         node.parentNode.insertBefore(children[0], node);\r
6433 \r
6434                                                 // Keep non empty elements or img, hr etc\r
6435                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))\r
6436                                                         return;\r
6437                                         }\r
6438 \r
6439                                         t.remove(node);\r
6440                                 }\r
6441 \r
6442                                 return node;\r
6443                         };\r
6444 \r
6445                         if (pe && e) {\r
6446                                 // Get before chunk\r
6447                                 r.setStart(pe.parentNode, t.nodeIndex(pe));\r
6448                                 r.setEnd(e.parentNode, t.nodeIndex(e));\r
6449                                 bef = r.extractContents();\r
6450 \r
6451                                 // Get after chunk\r
6452                                 r = t.createRng();\r
6453                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);\r
6454                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);\r
6455                                 aft = r.extractContents();\r
6456 \r
6457                                 // Insert before chunk\r
6458                                 pa = pe.parentNode;\r
6459                                 pa.insertBefore(trim(bef), pe);\r
6460 \r
6461                                 // Insert middle chunk\r
6462                                 if (re)\r
6463                                 pa.replaceChild(re, e);\r
6464                         else\r
6465                                 pa.insertBefore(e, pe);\r
6466 \r
6467                                 // Insert after chunk\r
6468                                 pa.insertBefore(trim(aft), pe);\r
6469                                 t.remove(pe);\r
6470 \r
6471                                 return re || e;\r
6472                         }\r
6473                 },\r
6474 \r
6475                 bind : function(target, name, func, scope) {\r
6476                         return this.events.add(target, name, func, scope || this);\r
6477                 },\r
6478 \r
6479                 unbind : function(target, name, func) {\r
6480                         return this.events.remove(target, name, func);\r
6481                 },\r
6482 \r
6483                 fire : function(target, name, evt) {\r
6484                         return this.events.fire(target, name, evt);\r
6485                 },\r
6486 \r
6487                 // Returns the content editable state of a node\r
6488                 getContentEditable: function(node) {\r
6489                         var contentEditable;\r
6490 \r
6491                         // Check type\r
6492                         if (node.nodeType != 1) {\r
6493                                 return null;\r
6494                         }\r
6495 \r
6496                         // Check for fake content editable\r
6497                         contentEditable = node.getAttribute("data-mce-contenteditable");\r
6498                         if (contentEditable && contentEditable !== "inherit") {\r
6499                                 return contentEditable;\r
6500                         }\r
6501 \r
6502                         // Check for real content editable\r
6503                         return node.contentEditable !== "inherit" ? node.contentEditable : null;\r
6504                 },\r
6505 \r
6506 \r
6507                 _findSib : function(node, selector, name) {\r
6508                         var t = this, f = selector;\r
6509 \r
6510                         if (node) {\r
6511                                 // If expression make a function of it using is\r
6512                                 if (is(f, 'string')) {\r
6513                                         f = function(node) {\r
6514                                                 return t.is(node, selector);\r
6515                                         };\r
6516                                 }\r
6517 \r
6518                                 // Loop all siblings\r
6519                                 for (node = node[name]; node; node = node[name]) {\r
6520                                         if (f(node))\r
6521                                                 return node;\r
6522                                 }\r
6523                         }\r
6524 \r
6525                         return null;\r
6526                 },\r
6527 \r
6528                 _isRes : function(c) {\r
6529                         // Is live resizble element\r
6530                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);\r
6531                 }\r
6532 \r
6533                 /*\r
6534                 walk : function(n, f, s) {\r
6535                         var d = this.doc, w;\r
6536 \r
6537                         if (d.createTreeWalker) {\r
6538                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);\r
6539 \r
6540                                 while ((n = w.nextNode()) != null)\r
6541                                         f.call(s || this, n);\r
6542                         } else\r
6543                                 tinymce.walk(n, f, 'childNodes', s);\r
6544                 }\r
6545                 */\r
6546 \r
6547                 /*\r
6548                 toRGB : function(s) {\r
6549                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);\r
6550 \r
6551                         if (c) {\r
6552                                 // #FFF -> #FFFFFF\r
6553                                 if (!is(c[3]))\r
6554                                         c[3] = c[2] = c[1];\r
6555 \r
6556                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";\r
6557                         }\r
6558 \r
6559                         return s;\r
6560                 }\r
6561                 */\r
6562         });\r
6563 \r
6564         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});\r
6565 })(tinymce);\r
6566 \r
6567 (function(ns) {\r
6568         // Range constructor\r
6569         function Range(dom) {\r
6570                 var t = this,\r
6571                         doc = dom.doc,\r
6572                         EXTRACT = 0,\r
6573                         CLONE = 1,\r
6574                         DELETE = 2,\r
6575                         TRUE = true,\r
6576                         FALSE = false,\r
6577                         START_OFFSET = 'startOffset',\r
6578                         START_CONTAINER = 'startContainer',\r
6579                         END_CONTAINER = 'endContainer',\r
6580                         END_OFFSET = 'endOffset',\r
6581                         extend = tinymce.extend,\r
6582                         nodeIndex = dom.nodeIndex;\r
6583 \r
6584                 extend(t, {\r
6585                         // Inital states\r
6586                         startContainer : doc,\r
6587                         startOffset : 0,\r
6588                         endContainer : doc,\r
6589                         endOffset : 0,\r
6590                         collapsed : TRUE,\r
6591                         commonAncestorContainer : doc,\r
6592 \r
6593                         // Range constants\r
6594                         START_TO_START : 0,\r
6595                         START_TO_END : 1,\r
6596                         END_TO_END : 2,\r
6597                         END_TO_START : 3,\r
6598 \r
6599                         // Public methods\r
6600                         setStart : setStart,\r
6601                         setEnd : setEnd,\r
6602                         setStartBefore : setStartBefore,\r
6603                         setStartAfter : setStartAfter,\r
6604                         setEndBefore : setEndBefore,\r
6605                         setEndAfter : setEndAfter,\r
6606                         collapse : collapse,\r
6607                         selectNode : selectNode,\r
6608                         selectNodeContents : selectNodeContents,\r
6609                         compareBoundaryPoints : compareBoundaryPoints,\r
6610                         deleteContents : deleteContents,\r
6611                         extractContents : extractContents,\r
6612                         cloneContents : cloneContents,\r
6613                         insertNode : insertNode,\r
6614                         surroundContents : surroundContents,\r
6615                         cloneRange : cloneRange,\r
6616                         toStringIE : toStringIE\r
6617                 });\r
6618 \r
6619                 function createDocumentFragment() {\r
6620                         return doc.createDocumentFragment();\r
6621                 };\r
6622 \r
6623                 function setStart(n, o) {\r
6624                         _setEndPoint(TRUE, n, o);\r
6625                 };\r
6626 \r
6627                 function setEnd(n, o) {\r
6628                         _setEndPoint(FALSE, n, o);\r
6629                 };\r
6630 \r
6631                 function setStartBefore(n) {\r
6632                         setStart(n.parentNode, nodeIndex(n));\r
6633                 };\r
6634 \r
6635                 function setStartAfter(n) {\r
6636                         setStart(n.parentNode, nodeIndex(n) + 1);\r
6637                 };\r
6638 \r
6639                 function setEndBefore(n) {\r
6640                         setEnd(n.parentNode, nodeIndex(n));\r
6641                 };\r
6642 \r
6643                 function setEndAfter(n) {\r
6644                         setEnd(n.parentNode, nodeIndex(n) + 1);\r
6645                 };\r
6646 \r
6647                 function collapse(ts) {\r
6648                         if (ts) {\r
6649                                 t[END_CONTAINER] = t[START_CONTAINER];\r
6650                                 t[END_OFFSET] = t[START_OFFSET];\r
6651                         } else {\r
6652                                 t[START_CONTAINER] = t[END_CONTAINER];\r
6653                                 t[START_OFFSET] = t[END_OFFSET];\r
6654                         }\r
6655 \r
6656                         t.collapsed = TRUE;\r
6657                 };\r
6658 \r
6659                 function selectNode(n) {\r
6660                         setStartBefore(n);\r
6661                         setEndAfter(n);\r
6662                 };\r
6663 \r
6664                 function selectNodeContents(n) {\r
6665                         setStart(n, 0);\r
6666                         setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);\r
6667                 };\r
6668 \r
6669                 function compareBoundaryPoints(h, r) {\r
6670                         var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],\r
6671                         rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;\r
6672 \r
6673                         // Check START_TO_START\r
6674                         if (h === 0)\r
6675                                 return _compareBoundaryPoints(sc, so, rsc, rso);\r
6676         \r
6677                         // Check START_TO_END\r
6678                         if (h === 1)\r
6679                                 return _compareBoundaryPoints(ec, eo, rsc, rso);\r
6680         \r
6681                         // Check END_TO_END\r
6682                         if (h === 2)\r
6683                                 return _compareBoundaryPoints(ec, eo, rec, reo);\r
6684         \r
6685                         // Check END_TO_START\r
6686                         if (h === 3) \r
6687                                 return _compareBoundaryPoints(sc, so, rec, reo);\r
6688                 };\r
6689 \r
6690                 function deleteContents() {\r
6691                         _traverse(DELETE);\r
6692                 };\r
6693 \r
6694                 function extractContents() {\r
6695                         return _traverse(EXTRACT);\r
6696                 };\r
6697 \r
6698                 function cloneContents() {\r
6699                         return _traverse(CLONE);\r
6700                 };\r
6701 \r
6702                 function insertNode(n) {\r
6703                         var startContainer = this[START_CONTAINER],\r
6704                                 startOffset = this[START_OFFSET], nn, o;\r
6705 \r
6706                         // Node is TEXT_NODE or CDATA\r
6707                         if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {\r
6708                                 if (!startOffset) {\r
6709                                         // At the start of text\r
6710                                         startContainer.parentNode.insertBefore(n, startContainer);\r
6711                                 } else if (startOffset >= startContainer.nodeValue.length) {\r
6712                                         // At the end of text\r
6713                                         dom.insertAfter(n, startContainer);\r
6714                                 } else {\r
6715                                         // Middle, need to split\r
6716                                         nn = startContainer.splitText(startOffset);\r
6717                                         startContainer.parentNode.insertBefore(n, nn);\r
6718                                 }\r
6719                         } else {\r
6720                                 // Insert element node\r
6721                                 if (startContainer.childNodes.length > 0)\r
6722                                         o = startContainer.childNodes[startOffset];\r
6723 \r
6724                                 if (o)\r
6725                                         startContainer.insertBefore(n, o);\r
6726                                 else\r
6727                                         startContainer.appendChild(n);\r
6728                         }\r
6729                 };\r
6730 \r
6731                 function surroundContents(n) {\r
6732                         var f = t.extractContents();\r
6733 \r
6734                         t.insertNode(n);\r
6735                         n.appendChild(f);\r
6736                         t.selectNode(n);\r
6737                 };\r
6738 \r
6739                 function cloneRange() {\r
6740                         return extend(new Range(dom), {\r
6741                                 startContainer : t[START_CONTAINER],\r
6742                                 startOffset : t[START_OFFSET],\r
6743                                 endContainer : t[END_CONTAINER],\r
6744                                 endOffset : t[END_OFFSET],\r
6745                                 collapsed : t.collapsed,\r
6746                                 commonAncestorContainer : t.commonAncestorContainer\r
6747                         });\r
6748                 };\r
6749 \r
6750                 // Private methods\r
6751 \r
6752                 function _getSelectedNode(container, offset) {\r
6753                         var child;\r
6754 \r
6755                         if (container.nodeType == 3 /* TEXT_NODE */)\r
6756                                 return container;\r
6757 \r
6758                         if (offset < 0)\r
6759                                 return container;\r
6760 \r
6761                         child = container.firstChild;\r
6762                         while (child && offset > 0) {\r
6763                                 --offset;\r
6764                                 child = child.nextSibling;\r
6765                         }\r
6766 \r
6767                         if (child)\r
6768                                 return child;\r
6769 \r
6770                         return container;\r
6771                 };\r
6772 \r
6773                 function _isCollapsed() {\r
6774                         return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);\r
6775                 };\r
6776 \r
6777                 function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {\r
6778                         var c, offsetC, n, cmnRoot, childA, childB;\r
6779                         \r
6780                         // In the first case the boundary-points have the same container. A is before B\r
6781                         // if its offset is less than the offset of B, A is equal to B if its offset is\r
6782                         // equal to the offset of B, and A is after B if its offset is greater than the\r
6783                         // offset of B.\r
6784                         if (containerA == containerB) {\r
6785                                 if (offsetA == offsetB)\r
6786                                         return 0; // equal\r
6787 \r
6788                                 if (offsetA < offsetB)\r
6789                                         return -1; // before\r
6790 \r
6791                                 return 1; // after\r
6792                         }\r
6793 \r
6794                         // In the second case a child node C of the container of A is an ancestor\r
6795                         // container of B. In this case, A is before B if the offset of A is less than or\r
6796                         // equal to the index of the child node C and A is after B otherwise.\r
6797                         c = containerB;\r
6798                         while (c && c.parentNode != containerA)\r
6799                                 c = c.parentNode;\r
6800 \r
6801                         if (c) {\r
6802                                 offsetC = 0;\r
6803                                 n = containerA.firstChild;\r
6804 \r
6805                                 while (n != c && offsetC < offsetA) {\r
6806                                         offsetC++;\r
6807                                         n = n.nextSibling;\r
6808                                 }\r
6809 \r
6810                                 if (offsetA <= offsetC)\r
6811                                         return -1; // before\r
6812 \r
6813                                 return 1; // after\r
6814                         }\r
6815 \r
6816                         // In the third case a child node C of the container of B is an ancestor container\r
6817                         // of A. In this case, A is before B if the index of the child node C is less than\r
6818                         // the offset of B and A is after B otherwise.\r
6819                         c = containerA;\r
6820                         while (c && c.parentNode != containerB) {\r
6821                                 c = c.parentNode;\r
6822                         }\r
6823 \r
6824                         if (c) {\r
6825                                 offsetC = 0;\r
6826                                 n = containerB.firstChild;\r
6827 \r
6828                                 while (n != c && offsetC < offsetB) {\r
6829                                         offsetC++;\r
6830                                         n = n.nextSibling;\r
6831                                 }\r
6832 \r
6833                                 if (offsetC < offsetB)\r
6834                                         return -1; // before\r
6835 \r
6836                                 return 1; // after\r
6837                         }\r
6838 \r
6839                         // In the fourth case, none of three other cases hold: the containers of A and B\r
6840                         // are siblings or descendants of sibling nodes. In this case, A is before B if\r
6841                         // the container of A is before the container of B in a pre-order traversal of the\r
6842                         // Ranges' context tree and A is after B otherwise.\r
6843                         cmnRoot = dom.findCommonAncestor(containerA, containerB);\r
6844                         childA = containerA;\r
6845 \r
6846                         while (childA && childA.parentNode != cmnRoot)\r
6847                                 childA = childA.parentNode;\r
6848 \r
6849                         if (!childA)\r
6850                                 childA = cmnRoot;\r
6851 \r
6852                         childB = containerB;\r
6853                         while (childB && childB.parentNode != cmnRoot)\r
6854                                 childB = childB.parentNode;\r
6855 \r
6856                         if (!childB)\r
6857                                 childB = cmnRoot;\r
6858 \r
6859                         if (childA == childB)\r
6860                                 return 0; // equal\r
6861 \r
6862                         n = cmnRoot.firstChild;\r
6863                         while (n) {\r
6864                                 if (n == childA)\r
6865                                         return -1; // before\r
6866 \r
6867                                 if (n == childB)\r
6868                                         return 1; // after\r
6869 \r
6870                                 n = n.nextSibling;\r
6871                         }\r
6872                 };\r
6873 \r
6874                 function _setEndPoint(st, n, o) {\r
6875                         var ec, sc;\r
6876 \r
6877                         if (st) {\r
6878                                 t[START_CONTAINER] = n;\r
6879                                 t[START_OFFSET] = o;\r
6880                         } else {\r
6881                                 t[END_CONTAINER] = n;\r
6882                                 t[END_OFFSET] = o;\r
6883                         }\r
6884 \r
6885                         // If one boundary-point of a Range is set to have a root container\r
6886                         // other than the current one for the Range, the Range is collapsed to\r
6887                         // the new position. This enforces the restriction that both boundary-\r
6888                         // points of a Range must have the same root container.\r
6889                         ec = t[END_CONTAINER];\r
6890                         while (ec.parentNode)\r
6891                                 ec = ec.parentNode;\r
6892 \r
6893                         sc = t[START_CONTAINER];\r
6894                         while (sc.parentNode)\r
6895                                 sc = sc.parentNode;\r
6896 \r
6897                         if (sc == ec) {\r
6898                                 // The start position of a Range is guaranteed to never be after the\r
6899                                 // end position. To enforce this restriction, if the start is set to\r
6900                                 // be at a position after the end, the Range is collapsed to that\r
6901                                 // position.\r
6902                                 if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)\r
6903                                         t.collapse(st);\r
6904                         } else\r
6905                                 t.collapse(st);\r
6906 \r
6907                         t.collapsed = _isCollapsed();\r
6908                         t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);\r
6909                 };\r
6910 \r
6911                 function _traverse(how) {\r
6912                         var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;\r
6913 \r
6914                         if (t[START_CONTAINER] == t[END_CONTAINER])\r
6915                                 return _traverseSameContainer(how);\r
6916 \r
6917                         for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
6918                                 if (p == t[START_CONTAINER])\r
6919                                         return _traverseCommonStartContainer(c, how);\r
6920 \r
6921                                 ++endContainerDepth;\r
6922                         }\r
6923 \r
6924                         for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {\r
6925                                 if (p == t[END_CONTAINER])\r
6926                                         return _traverseCommonEndContainer(c, how);\r
6927 \r
6928                                 ++startContainerDepth;\r
6929                         }\r
6930 \r
6931                         depthDiff = startContainerDepth - endContainerDepth;\r
6932 \r
6933                         startNode = t[START_CONTAINER];\r
6934                         while (depthDiff > 0) {\r
6935                                 startNode = startNode.parentNode;\r
6936                                 depthDiff--;\r
6937                         }\r
6938 \r
6939                         endNode = t[END_CONTAINER];\r
6940                         while (depthDiff < 0) {\r
6941                                 endNode = endNode.parentNode;\r
6942                                 depthDiff++;\r
6943                         }\r
6944 \r
6945                         // ascend the ancestor hierarchy until we have a common parent.\r
6946                         for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {\r
6947                                 startNode = sp;\r
6948                                 endNode = ep;\r
6949                         }\r
6950 \r
6951                         return _traverseCommonAncestors(startNode, endNode, how);\r
6952                 };\r
6953 \r
6954                  function _traverseSameContainer(how) {\r
6955                         var frag, s, sub, n, cnt, sibling, xferNode, start, len;\r
6956 \r
6957                         if (how != DELETE)\r
6958                                 frag = createDocumentFragment();\r
6959 \r
6960                         // If selection is empty, just return the fragment\r
6961                         if (t[START_OFFSET] == t[END_OFFSET])\r
6962                                 return frag;\r
6963 \r
6964                         // Text node needs special case handling\r
6965                         if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {\r
6966                                 // get the substring\r
6967                                 s = t[START_CONTAINER].nodeValue;\r
6968                                 sub = s.substring(t[START_OFFSET], t[END_OFFSET]);\r
6969 \r
6970                                 // set the original text node to its new value\r
6971                                 if (how != CLONE) {\r
6972                                         n = t[START_CONTAINER];\r
6973                                         start = t[START_OFFSET];\r
6974                                         len = t[END_OFFSET] - t[START_OFFSET];\r
6975 \r
6976                                         if (start === 0 && len >= n.nodeValue.length - 1) {\r
6977                                                 n.parentNode.removeChild(n);\r
6978                                         } else {\r
6979                                                 n.deleteData(start, len);\r
6980                                         }\r
6981 \r
6982                                         // Nothing is partially selected, so collapse to start point\r
6983                                         t.collapse(TRUE);\r
6984                                 }\r
6985 \r
6986                                 if (how == DELETE)\r
6987                                         return;\r
6988 \r
6989                                 if (sub.length > 0) {\r
6990                                         frag.appendChild(doc.createTextNode(sub));\r
6991                                 }\r
6992 \r
6993                                 return frag;\r
6994                         }\r
6995 \r
6996                         // Copy nodes between the start/end offsets.\r
6997                         n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);\r
6998                         cnt = t[END_OFFSET] - t[START_OFFSET];\r
6999 \r
7000                         while (n && cnt > 0) {\r
7001                                 sibling = n.nextSibling;\r
7002                                 xferNode = _traverseFullySelected(n, how);\r
7003 \r
7004                                 if (frag)\r
7005                                         frag.appendChild( xferNode );\r
7006 \r
7007                                 --cnt;\r
7008                                 n = sibling;\r
7009                         }\r
7010 \r
7011                         // Nothing is partially selected, so collapse to start point\r
7012                         if (how != CLONE)\r
7013                                 t.collapse(TRUE);\r
7014 \r
7015                         return frag;\r
7016                 };\r
7017 \r
7018                 function _traverseCommonStartContainer(endAncestor, how) {\r
7019                         var frag, n, endIdx, cnt, sibling, xferNode;\r
7020 \r
7021                         if (how != DELETE)\r
7022                                 frag = createDocumentFragment();\r
7023 \r
7024                         n = _traverseRightBoundary(endAncestor, how);\r
7025 \r
7026                         if (frag)\r
7027                                 frag.appendChild(n);\r
7028 \r
7029                         endIdx = nodeIndex(endAncestor);\r
7030                         cnt = endIdx - t[START_OFFSET];\r
7031 \r
7032                         if (cnt <= 0) {\r
7033                                 // Collapse to just before the endAncestor, which\r
7034                                 // is partially selected.\r
7035                                 if (how != CLONE) {\r
7036                                         t.setEndBefore(endAncestor);\r
7037                                         t.collapse(FALSE);\r
7038                                 }\r
7039 \r
7040                                 return frag;\r
7041                         }\r
7042 \r
7043                         n = endAncestor.previousSibling;\r
7044                         while (cnt > 0) {\r
7045                                 sibling = n.previousSibling;\r
7046                                 xferNode = _traverseFullySelected(n, how);\r
7047 \r
7048                                 if (frag)\r
7049                                         frag.insertBefore(xferNode, frag.firstChild);\r
7050 \r
7051                                 --cnt;\r
7052                                 n = sibling;\r
7053                         }\r
7054 \r
7055                         // Collapse to just before the endAncestor, which\r
7056                         // is partially selected.\r
7057                         if (how != CLONE) {\r
7058                                 t.setEndBefore(endAncestor);\r
7059                                 t.collapse(FALSE);\r
7060                         }\r
7061 \r
7062                         return frag;\r
7063                 };\r
7064 \r
7065                 function _traverseCommonEndContainer(startAncestor, how) {\r
7066                         var frag, startIdx, n, cnt, sibling, xferNode;\r
7067 \r
7068                         if (how != DELETE)\r
7069                                 frag = createDocumentFragment();\r
7070 \r
7071                         n = _traverseLeftBoundary(startAncestor, how);\r
7072                         if (frag)\r
7073                                 frag.appendChild(n);\r
7074 \r
7075                         startIdx = nodeIndex(startAncestor);\r
7076                         ++startIdx; // Because we already traversed it\r
7077 \r
7078                         cnt = t[END_OFFSET] - startIdx;\r
7079                         n = startAncestor.nextSibling;\r
7080                         while (n && cnt > 0) {\r
7081                                 sibling = n.nextSibling;\r
7082                                 xferNode = _traverseFullySelected(n, how);\r
7083 \r
7084                                 if (frag)\r
7085                                         frag.appendChild(xferNode);\r
7086 \r
7087                                 --cnt;\r
7088                                 n = sibling;\r
7089                         }\r
7090 \r
7091                         if (how != CLONE) {\r
7092                                 t.setStartAfter(startAncestor);\r
7093                                 t.collapse(TRUE);\r
7094                         }\r
7095 \r
7096                         return frag;\r
7097                 };\r
7098 \r
7099                 function _traverseCommonAncestors(startAncestor, endAncestor, how) {\r
7100                         var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;\r
7101 \r
7102                         if (how != DELETE)\r
7103                                 frag = createDocumentFragment();\r
7104 \r
7105                         n = _traverseLeftBoundary(startAncestor, how);\r
7106                         if (frag)\r
7107                                 frag.appendChild(n);\r
7108 \r
7109                         commonParent = startAncestor.parentNode;\r
7110                         startOffset = nodeIndex(startAncestor);\r
7111                         endOffset = nodeIndex(endAncestor);\r
7112                         ++startOffset;\r
7113 \r
7114                         cnt = endOffset - startOffset;\r
7115                         sibling = startAncestor.nextSibling;\r
7116 \r
7117                         while (cnt > 0) {\r
7118                                 nextSibling = sibling.nextSibling;\r
7119                                 n = _traverseFullySelected(sibling, how);\r
7120 \r
7121                                 if (frag)\r
7122                                         frag.appendChild(n);\r
7123 \r
7124                                 sibling = nextSibling;\r
7125                                 --cnt;\r
7126                         }\r
7127 \r
7128                         n = _traverseRightBoundary(endAncestor, how);\r
7129 \r
7130                         if (frag)\r
7131                                 frag.appendChild(n);\r
7132 \r
7133                         if (how != CLONE) {\r
7134                                 t.setStartAfter(startAncestor);\r
7135                                 t.collapse(TRUE);\r
7136                         }\r
7137 \r
7138                         return frag;\r
7139                 };\r
7140 \r
7141                 function _traverseRightBoundary(root, how) {\r
7142                         var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];\r
7143 \r
7144                         if (next == root)\r
7145                                 return _traverseNode(next, isFullySelected, FALSE, how);\r
7146 \r
7147                         parent = next.parentNode;\r
7148                         clonedParent = _traverseNode(parent, FALSE, FALSE, how);\r
7149 \r
7150                         while (parent) {\r
7151                                 while (next) {\r
7152                                         prevSibling = next.previousSibling;\r
7153                                         clonedChild = _traverseNode(next, isFullySelected, FALSE, how);\r
7154 \r
7155                                         if (how != DELETE)\r
7156                                                 clonedParent.insertBefore(clonedChild, clonedParent.firstChild);\r
7157 \r
7158                                         isFullySelected = TRUE;\r
7159                                         next = prevSibling;\r
7160                                 }\r
7161 \r
7162                                 if (parent == root)\r
7163                                         return clonedParent;\r
7164 \r
7165                                 next = parent.previousSibling;\r
7166                                 parent = parent.parentNode;\r
7167 \r
7168                                 clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);\r
7169 \r
7170                                 if (how != DELETE)\r
7171                                         clonedGrandParent.appendChild(clonedParent);\r
7172 \r
7173                                 clonedParent = clonedGrandParent;\r
7174                         }\r
7175                 };\r
7176 \r
7177                 function _traverseLeftBoundary(root, how) {\r
7178                         var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;\r
7179 \r
7180                         if (next == root)\r
7181                                 return _traverseNode(next, isFullySelected, TRUE, how);\r
7182 \r
7183                         parent = next.parentNode;\r
7184                         clonedParent = _traverseNode(parent, FALSE, TRUE, how);\r
7185 \r
7186                         while (parent) {\r
7187                                 while (next) {\r
7188                                         nextSibling = next.nextSibling;\r
7189                                         clonedChild = _traverseNode(next, isFullySelected, TRUE, how);\r
7190 \r
7191                                         if (how != DELETE)\r
7192                                                 clonedParent.appendChild(clonedChild);\r
7193 \r
7194                                         isFullySelected = TRUE;\r
7195                                         next = nextSibling;\r
7196                                 }\r
7197 \r
7198                                 if (parent == root)\r
7199                                         return clonedParent;\r
7200 \r
7201                                 next = parent.nextSibling;\r
7202                                 parent = parent.parentNode;\r
7203 \r
7204                                 clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);\r
7205 \r
7206                                 if (how != DELETE)\r
7207                                         clonedGrandParent.appendChild(clonedParent);\r
7208 \r
7209                                 clonedParent = clonedGrandParent;\r
7210                         }\r
7211                 };\r
7212 \r
7213                 function _traverseNode(n, isFullySelected, isLeft, how) {\r
7214                         var txtValue, newNodeValue, oldNodeValue, offset, newNode;\r
7215 \r
7216                         if (isFullySelected)\r
7217                                 return _traverseFullySelected(n, how);\r
7218 \r
7219                         if (n.nodeType == 3 /* TEXT_NODE */) {\r
7220                                 txtValue = n.nodeValue;\r
7221 \r
7222                                 if (isLeft) {\r
7223                                         offset = t[START_OFFSET];\r
7224                                         newNodeValue = txtValue.substring(offset);\r
7225                                         oldNodeValue = txtValue.substring(0, offset);\r
7226                                 } else {\r
7227                                         offset = t[END_OFFSET];\r
7228                                         newNodeValue = txtValue.substring(0, offset);\r
7229                                         oldNodeValue = txtValue.substring(offset);\r
7230                                 }\r
7231 \r
7232                                 if (how != CLONE)\r
7233                                         n.nodeValue = oldNodeValue;\r
7234 \r
7235                                 if (how == DELETE)\r
7236                                         return;\r
7237 \r
7238                                 newNode = dom.clone(n, FALSE);\r
7239                                 newNode.nodeValue = newNodeValue;\r
7240 \r
7241                                 return newNode;\r
7242                         }\r
7243 \r
7244                         if (how == DELETE)\r
7245                                 return;\r
7246 \r
7247                         return dom.clone(n, FALSE);\r
7248                 };\r
7249 \r
7250                 function _traverseFullySelected(n, how) {\r
7251                         if (how != DELETE)\r
7252                                 return how == CLONE ? dom.clone(n, TRUE) : n;\r
7253 \r
7254                         n.parentNode.removeChild(n);\r
7255                 };\r
7256 \r
7257                 function toStringIE() {\r
7258                         return dom.create('body', null, cloneContents()).outerText;\r
7259                 }\r
7260                 \r
7261                 return t;\r
7262         };\r
7263 \r
7264         ns.Range = Range;\r
7265 \r
7266         // Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype\r
7267         Range.prototype.toString = function() {\r
7268                 return this.toStringIE();\r
7269         };\r
7270 })(tinymce.dom);\r
7271 \r
7272 (function() {\r
7273         function Selection(selection) {\r
7274                 var self = this, dom = selection.dom, TRUE = true, FALSE = false;\r
7275 \r
7276                 function getPosition(rng, start) {\r
7277                         var checkRng, startIndex = 0, endIndex, inside,\r
7278                                 children, child, offset, index, position = -1, parent;\r
7279 \r
7280                         // Setup test range, collapse it and get the parent\r
7281                         checkRng = rng.duplicate();\r
7282                         checkRng.collapse(start);\r
7283                         parent = checkRng.parentElement();\r
7284 \r
7285                         // Check if the selection is within the right document\r
7286                         if (parent.ownerDocument !== selection.dom.doc)\r
7287                                 return;\r
7288 \r
7289                         // IE will report non editable elements as it's parent so look for an editable one\r
7290                         while (parent.contentEditable === "false") {\r
7291                                 parent = parent.parentNode;\r
7292                         }\r
7293 \r
7294                         // If parent doesn't have any children then return that we are inside the element\r
7295                         if (!parent.hasChildNodes()) {\r
7296                                 return {node : parent, inside : 1};\r
7297                         }\r
7298 \r
7299                         // Setup node list and endIndex\r
7300                         children = parent.children;\r
7301                         endIndex = children.length - 1;\r
7302 \r
7303                         // Perform a binary search for the position\r
7304                         while (startIndex <= endIndex) {\r
7305                                 index = Math.floor((startIndex + endIndex) / 2);\r
7306 \r
7307                                 // Move selection to node and compare the ranges\r
7308                                 child = children[index];\r
7309                                 checkRng.moveToElementText(child);\r
7310                                 position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);\r
7311 \r
7312                                 // Before/after or an exact match\r
7313                                 if (position > 0) {\r
7314                                         endIndex = index - 1;\r
7315                                 } else if (position < 0) {\r
7316                                         startIndex = index + 1;\r
7317                                 } else {\r
7318                                         return {node : child};\r
7319                                 }\r
7320                         }\r
7321 \r
7322                         // Check if child position is before or we didn't find a position\r
7323                         if (position < 0) {\r
7324                                 // No element child was found use the parent element and the offset inside that\r
7325                                 if (!child) {\r
7326                                         checkRng.moveToElementText(parent);\r
7327                                         checkRng.collapse(true);\r
7328                                         child = parent;\r
7329                                         inside = true;\r
7330                                 } else\r
7331                                         checkRng.collapse(false);\r
7332 \r
7333                                 // 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
7334                                 // We need to walk char by char since rng.text or rng.htmlText will trim line endings\r
7335                                 offset = 0;\r
7336                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {\r
7337                                         if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {\r
7338                                                 break;\r
7339                                         }\r
7340 \r
7341                                         offset++;\r
7342                                 }\r
7343                         } else {\r
7344                                 // Child position is after the selection endpoint\r
7345                                 checkRng.collapse(true);\r
7346 \r
7347                                 // 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
7348                                 offset = 0;\r
7349                                 while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {\r
7350                                         if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {\r
7351                                                 break;\r
7352                                         }\r
7353 \r
7354                                         offset++;\r
7355                                 }\r
7356                         }\r
7357 \r
7358                         return {node : child, position : position, offset : offset, inside : inside};\r
7359                 };\r
7360 \r
7361                 // Returns a W3C DOM compatible range object by using the IE Range API\r
7362                 function getRange() {\r
7363                         var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;\r
7364 \r
7365                         // If selection is outside the current document just return an empty range\r
7366                         element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();\r
7367                         if (element.ownerDocument != dom.doc)\r
7368                                 return domRange;\r
7369 \r
7370                         collapsed = selection.isCollapsed();\r
7371 \r
7372                         // Handle control selection\r
7373                         if (ieRange.item) {\r
7374                                 domRange.setStart(element.parentNode, dom.nodeIndex(element));\r
7375                                 domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);\r
7376 \r
7377                                 return domRange;\r
7378                         }\r
7379 \r
7380                         function findEndPoint(start) {\r
7381                                 var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;\r
7382 \r
7383                                 container = endPoint.node;\r
7384                                 offset = endPoint.offset;\r
7385 \r
7386                                 if (endPoint.inside && !container.hasChildNodes()) {\r
7387                                         domRange[start ? 'setStart' : 'setEnd'](container, 0);\r
7388                                         return;\r
7389                                 }\r
7390 \r
7391                                 if (offset === undef) {\r
7392                                         domRange[start ? 'setStartBefore' : 'setEndAfter'](container);\r
7393                                         return;\r
7394                                 }\r
7395 \r
7396                                 if (endPoint.position < 0) {\r
7397                                         sibling = endPoint.inside ? container.firstChild : container.nextSibling;\r
7398 \r
7399                                         if (!sibling) {\r
7400                                                 domRange[start ? 'setStartAfter' : 'setEndAfter'](container);\r
7401                                                 return;\r
7402                                         }\r
7403 \r
7404                                         if (!offset) {\r
7405                                                 if (sibling.nodeType == 3)\r
7406                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, 0);\r
7407                                                 else\r
7408                                                         domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);\r
7409 \r
7410                                                 return;\r
7411                                         }\r
7412 \r
7413                                         // Find the text node and offset\r
7414                                         while (sibling) {\r
7415                                                 nodeValue = sibling.nodeValue;\r
7416                                                 textNodeOffset += nodeValue.length;\r
7417 \r
7418                                                 // We are at or passed the position we where looking for\r
7419                                                 if (textNodeOffset >= offset) {\r
7420                                                         container = sibling;\r
7421                                                         textNodeOffset -= offset;\r
7422                                                         textNodeOffset = nodeValue.length - textNodeOffset;\r
7423                                                         break;\r
7424                                                 }\r
7425 \r
7426                                                 sibling = sibling.nextSibling;\r
7427                                         }\r
7428                                 } else {\r
7429                                         // Find the text node and offset\r
7430                                         sibling = container.previousSibling;\r
7431 \r
7432                                         if (!sibling)\r
7433                                                 return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);\r
7434 \r
7435                                         // If there isn't any text to loop then use the first position\r
7436                                         if (!offset) {\r
7437                                                 if (container.nodeType == 3)\r
7438                                                         domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);\r
7439                                                 else\r
7440                                                         domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);\r
7441 \r
7442                                                 return;\r
7443                                         }\r
7444 \r
7445                                         while (sibling) {\r
7446                                                 textNodeOffset += sibling.nodeValue.length;\r
7447 \r
7448                                                 // We are at or passed the position we where looking for\r
7449                                                 if (textNodeOffset >= offset) {\r
7450                                                         container = sibling;\r
7451                                                         textNodeOffset -= offset;\r
7452                                                         break;\r
7453                                                 }\r
7454 \r
7455                                                 sibling = sibling.previousSibling;\r
7456                                         }\r
7457                                 }\r
7458 \r
7459                                 domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);\r
7460                         };\r
7461 \r
7462                         try {\r
7463                                 // Find start point\r
7464                                 findEndPoint(true);\r
7465 \r
7466                                 // Find end point if needed\r
7467                                 if (!collapsed)\r
7468                                         findEndPoint();\r
7469                         } catch (ex) {\r
7470                                 // IE has a nasty bug where text nodes might throw "invalid argument" when you\r
7471                                 // access the nodeValue or other properties of text nodes. This seems to happend when\r
7472                                 // text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.\r
7473                                 if (ex.number == -2147024809) {\r
7474                                         // Get the current selection\r
7475                                         bookmark = self.getBookmark(2);\r
7476 \r
7477                                         // Get start element\r
7478                                         tmpRange = ieRange.duplicate();\r
7479                                         tmpRange.collapse(true);\r
7480                                         element = tmpRange.parentElement();\r
7481 \r
7482                                         // Get end element\r
7483                                         if (!collapsed) {\r
7484                                                 tmpRange = ieRange.duplicate();\r
7485                                                 tmpRange.collapse(false);\r
7486                                                 element2 = tmpRange.parentElement();\r
7487                                                 element2.innerHTML = element2.innerHTML;\r
7488                                         }\r
7489 \r
7490                                         // Remove the broken elements\r
7491                                         element.innerHTML = element.innerHTML;\r
7492 \r
7493                                         // Restore the selection\r
7494                                         self.moveToBookmark(bookmark);\r
7495 \r
7496                                         // Since the range has moved we need to re-get it\r
7497                                         ieRange = selection.getRng();\r
7498 \r
7499                                         // Find start point\r
7500                                         findEndPoint(true);\r
7501 \r
7502                                         // Find end point if needed\r
7503                                         if (!collapsed)\r
7504                                                 findEndPoint();\r
7505                                 } else\r
7506                                         throw ex; // Throw other errors\r
7507                         }\r
7508 \r
7509                         return domRange;\r
7510                 };\r
7511 \r
7512                 this.getBookmark = function(type) {\r
7513                         var rng = selection.getRng(), start, end, bookmark = {};\r
7514 \r
7515                         function getIndexes(node) {\r
7516                                 var parent, root, children, i, indexes = [];\r
7517 \r
7518                                 parent = node.parentNode;\r
7519                                 root = dom.getRoot().parentNode;\r
7520 \r
7521                                 while (parent != root && parent.nodeType !== 9) {\r
7522                                         children = parent.children;\r
7523 \r
7524                                         i = children.length;\r
7525                                         while (i--) {\r
7526                                                 if (node === children[i]) {\r
7527                                                         indexes.push(i);\r
7528                                                         break;\r
7529                                                 }\r
7530                                         }\r
7531 \r
7532                                         node = parent;\r
7533                                         parent = parent.parentNode;\r
7534                                 }\r
7535 \r
7536                                 return indexes;\r
7537                         };\r
7538 \r
7539                         function getBookmarkEndPoint(start) {\r
7540                                 var position;\r
7541 \r
7542                                 position = getPosition(rng, start);\r
7543                                 if (position) {\r
7544                                         return {\r
7545                                                 position : position.position,\r
7546                                                 offset : position.offset,\r
7547                                                 indexes : getIndexes(position.node),\r
7548                                                 inside : position.inside\r
7549                                         };\r
7550                                 }\r
7551                         };\r
7552 \r
7553                         // Non ubstructive bookmark\r
7554                         if (type === 2) {\r
7555                                 // Handle text selection\r
7556                                 if (!rng.item) {\r
7557                                         bookmark.start = getBookmarkEndPoint(true);\r
7558 \r
7559                                         if (!selection.isCollapsed())\r
7560                                                 bookmark.end = getBookmarkEndPoint();\r
7561                                 } else\r
7562                                         bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};\r
7563                         }\r
7564 \r
7565                         return bookmark;\r
7566                 };\r
7567 \r
7568                 this.moveToBookmark = function(bookmark) {\r
7569                         var rng, body = dom.doc.body;\r
7570 \r
7571                         function resolveIndexes(indexes) {\r
7572                                 var node, i, idx, children;\r
7573 \r
7574                                 node = dom.getRoot();\r
7575                                 for (i = indexes.length - 1; i >= 0; i--) {\r
7576                                         children = node.children;\r
7577                                         idx = indexes[i];\r
7578 \r
7579                                         if (idx <= children.length - 1) {\r
7580                                                 node = children[idx];\r
7581                                         }\r
7582                                 }\r
7583 \r
7584                                 return node;\r
7585                         };\r
7586                         \r
7587                         function setBookmarkEndPoint(start) {\r
7588                                 var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;\r
7589 \r
7590                                 if (endPoint) {\r
7591                                         moveLeft = endPoint.position > 0;\r
7592 \r
7593                                         moveRng = body.createTextRange();\r
7594                                         moveRng.moveToElementText(resolveIndexes(endPoint.indexes));\r
7595 \r
7596                                         offset = endPoint.offset;\r
7597                                         if (offset !== undef) {\r
7598                                                 moveRng.collapse(endPoint.inside || moveLeft);\r
7599                                                 moveRng.moveStart('character', moveLeft ? -offset : offset);\r
7600                                         } else\r
7601                                                 moveRng.collapse(start);\r
7602 \r
7603                                         rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);\r
7604 \r
7605                                         if (start)\r
7606                                                 rng.collapse(true);\r
7607                                 }\r
7608                         };\r
7609 \r
7610                         if (bookmark.start) {\r
7611                                 if (bookmark.start.ctrl) {\r
7612                                         rng = body.createControlRange();\r
7613                                         rng.addElement(resolveIndexes(bookmark.start.indexes));\r
7614                                         rng.select();\r
7615                                 } else {\r
7616                                         rng = body.createTextRange();\r
7617                                         setBookmarkEndPoint(true);\r
7618                                         setBookmarkEndPoint();\r
7619                                         rng.select();\r
7620                                 }\r
7621                         }\r
7622                 };\r
7623 \r
7624                 this.addRange = function(rng) {\r
7625                         var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling,\r
7626                                 doc = selection.dom.doc, body = doc.body, nativeRng, ctrlElm;\r
7627 \r
7628                         function setEndPoint(start) {\r
7629                                 var container, offset, marker, tmpRng, nodes;\r
7630 \r
7631                                 marker = dom.create('a');\r
7632                                 container = start ? startContainer : endContainer;\r
7633                                 offset = start ? startOffset : endOffset;\r
7634                                 tmpRng = ieRng.duplicate();\r
7635 \r
7636                                 if (container == doc || container == doc.documentElement) {\r
7637                                         container = body;\r
7638                                         offset = 0;\r
7639                                 }\r
7640 \r
7641                                 if (container.nodeType == 3) {\r
7642                                         container.parentNode.insertBefore(marker, container);\r
7643                                         tmpRng.moveToElementText(marker);\r
7644                                         tmpRng.moveStart('character', offset);\r
7645                                         dom.remove(marker);\r
7646                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
7647                                 } else {\r
7648                                         nodes = container.childNodes;\r
7649 \r
7650                                         if (nodes.length) {\r
7651                                                 if (offset >= nodes.length) {\r
7652                                                         dom.insertAfter(marker, nodes[nodes.length - 1]);\r
7653                                                 } else {\r
7654                                                         container.insertBefore(marker, nodes[offset]);\r
7655                                                 }\r
7656 \r
7657                                                 tmpRng.moveToElementText(marker);\r
7658                                         } else if (container.canHaveHTML) {\r
7659                                                 // Empty node selection for example <div>|</div>\r
7660                                                 // Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open\r
7661                                                 container.innerHTML = '<span>\uFEFF</span>';\r
7662                                                 marker = container.firstChild;\r
7663                                                 tmpRng.moveToElementText(marker);\r
7664                                                 tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason\r
7665                                         }\r
7666 \r
7667                                         ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);\r
7668                                         dom.remove(marker);\r
7669                                 }\r
7670                         }\r
7671 \r
7672                         // Setup some shorter versions\r
7673                         startContainer = rng.startContainer;\r
7674                         startOffset = rng.startOffset;\r
7675                         endContainer = rng.endContainer;\r
7676                         endOffset = rng.endOffset;\r
7677                         ieRng = body.createTextRange();\r
7678 \r
7679                         // If single element selection then try making a control selection out of it\r
7680                         if (startContainer == endContainer && startContainer.nodeType == 1) {\r
7681                                 // Trick to place the caret inside an empty block element like <p></p>\r
7682                                 if (startOffset == endOffset && !startContainer.hasChildNodes()) {\r
7683                                         if (startContainer.canHaveHTML) {\r
7684                                                 // Check if previous sibling is an empty block if it is then we need to render it\r
7685                                                 // IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236\r
7686                                                 // Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>\r
7687                                                 sibling = startContainer.previousSibling;\r
7688                                                 if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {\r
7689                                                         sibling.innerHTML = '\uFEFF';\r
7690                                                 } else {\r
7691                                                         sibling = null;\r
7692                                                 }\r
7693 \r
7694                                                 startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';\r
7695                                                 ieRng.moveToElementText(startContainer.lastChild);\r
7696                                                 ieRng.select();\r
7697                                                 dom.doc.selection.clear();\r
7698                                                 startContainer.innerHTML = '';\r
7699 \r
7700                                                 if (sibling) {\r
7701                                                         sibling.innerHTML = '';\r
7702                                                 }\r
7703                                                 return;\r
7704                                         } else {\r
7705                                                 startOffset = dom.nodeIndex(startContainer);\r
7706                                                 startContainer = startContainer.parentNode;\r
7707                                         }\r
7708                                 }\r
7709 \r
7710                                 if (startOffset == endOffset - 1) {\r
7711                                         try {\r
7712                                                 ctrlElm = startContainer.childNodes[startOffset];\r
7713                                                 ctrlRng = body.createControlRange();\r
7714                                                 ctrlRng.addElement(ctrlElm);\r
7715                                                 ctrlRng.select();\r
7716 \r
7717                                                 // Check if the range produced is on the correct element and is a control range\r
7718                                                 // On IE 8 it will select the parent contentEditable container if you select an inner element see: #5398\r
7719                                                 nativeRng = selection.getRng();\r
7720                                                 if (nativeRng.item && ctrlElm === nativeRng.item(0)) {\r
7721                                                         return;\r
7722                                                 }\r
7723                                         } catch (ex) {\r
7724                                                 // Ignore\r
7725                                         }\r
7726                                 }\r
7727                         }\r
7728 \r
7729                         // Set start/end point of selection\r
7730                         setEndPoint(true);\r
7731                         setEndPoint();\r
7732 \r
7733                         // Select the new range and scroll it into view\r
7734                         ieRng.select();\r
7735                 };\r
7736 \r
7737                 // Expose range method\r
7738                 this.getRangeAt = getRange;\r
7739         };\r
7740 \r
7741         // Expose the selection object\r
7742         tinymce.dom.TridentSelection = Selection;\r
7743 })();\r
7744 \r
7745 \r
7746 /*\r
7747  * Sizzle CSS Selector Engine\r
7748  *  Copyright, The Dojo Foundation\r
7749  *  Released under the MIT, BSD, and GPL Licenses.\r
7750  *  More information: http://sizzlejs.com/\r
7751  */\r
7752 (function(){\r
7753 \r
7754 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,\r
7755         expando = "sizcache",\r
7756         done = 0,\r
7757         toString = Object.prototype.toString,\r
7758         hasDuplicate = false,\r
7759         baseHasDuplicate = true,\r
7760         rBackslash = /\\/g,\r
7761         rReturn = /\r\n/g,\r
7762         rNonWord = /\W/;\r
7763 \r
7764 // Here we check if the JavaScript engine is using some sort of\r
7765 // optimization where it does not always call our comparision\r
7766 // function. If that is the case, discard the hasDuplicate value.\r
7767 //   Thus far that includes Google Chrome.\r
7768 [0, 0].sort(function() {\r
7769         baseHasDuplicate = false;\r
7770         return 0;\r
7771 });\r
7772 \r
7773 var Sizzle = function( selector, context, results, seed ) {\r
7774         results = results || [];\r
7775         context = context || document;\r
7776 \r
7777         var origContext = context;\r
7778 \r
7779         if ( context.nodeType !== 1 && context.nodeType !== 9 ) {\r
7780                 return [];\r
7781         }\r
7782 \r
7783         if ( !selector || typeof selector !== "string" ) {\r
7784                 return results;\r
7785         }\r
7786 \r
7787         var m, set, checkSet, extra, ret, cur, pop, i,\r
7788                 prune = true,\r
7789                 contextXML = Sizzle.isXML( context ),\r
7790                 parts = [],\r
7791                 soFar = selector;\r
7792 \r
7793         // Reset the position of the chunker regexp (start from head)\r
7794         do {\r
7795                 chunker.exec( "" );\r
7796                 m = chunker.exec( soFar );\r
7797 \r
7798                 if ( m ) {\r
7799                         soFar = m[3];\r
7800 \r
7801                         parts.push( m[1] );\r
7802 \r
7803                         if ( m[2] ) {\r
7804                                 extra = m[3];\r
7805                                 break;\r
7806                         }\r
7807                 }\r
7808         } while ( m );\r
7809 \r
7810         if ( parts.length > 1 && origPOS.exec( selector ) ) {\r
7811 \r
7812                 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {\r
7813                         set = posProcess( parts[0] + parts[1], context, seed );\r
7814 \r
7815                 } else {\r
7816                         set = Expr.relative[ parts[0] ] ?\r
7817                                 [ context ] :\r
7818                                 Sizzle( parts.shift(), context );\r
7819 \r
7820                         while ( parts.length ) {\r
7821                                 selector = parts.shift();\r
7822 \r
7823                                 if ( Expr.relative[ selector ] ) {\r
7824                                         selector += parts.shift();\r
7825                                 }\r
7826 \r
7827                                 set = posProcess( selector, set, seed );\r
7828                         }\r
7829                 }\r
7830 \r
7831         } else {\r
7832                 // Take a shortcut and set the context if the root selector is an ID\r
7833                 // (but not if it'll be faster if the inner selector is an ID)\r
7834                 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&\r
7835                                 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {\r
7836 \r
7837                         ret = Sizzle.find( parts.shift(), context, contextXML );\r
7838                         context = ret.expr ?\r
7839                                 Sizzle.filter( ret.expr, ret.set )[0] :\r
7840                                 ret.set[0];\r
7841                 }\r
7842 \r
7843                 if ( context ) {\r
7844                         ret = seed ?\r
7845                                 { expr: parts.pop(), set: makeArray(seed) } :\r
7846                                 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );\r
7847 \r
7848                         set = ret.expr ?\r
7849                                 Sizzle.filter( ret.expr, ret.set ) :\r
7850                                 ret.set;\r
7851 \r
7852                         if ( parts.length > 0 ) {\r
7853                                 checkSet = makeArray( set );\r
7854 \r
7855                         } else {\r
7856                                 prune = false;\r
7857                         }\r
7858 \r
7859                         while ( parts.length ) {\r
7860                                 cur = parts.pop();\r
7861                                 pop = cur;\r
7862 \r
7863                                 if ( !Expr.relative[ cur ] ) {\r
7864                                         cur = "";\r
7865                                 } else {\r
7866                                         pop = parts.pop();\r
7867                                 }\r
7868 \r
7869                                 if ( pop == null ) {\r
7870                                         pop = context;\r
7871                                 }\r
7872 \r
7873                                 Expr.relative[ cur ]( checkSet, pop, contextXML );\r
7874                         }\r
7875 \r
7876                 } else {\r
7877                         checkSet = parts = [];\r
7878                 }\r
7879         }\r
7880 \r
7881         if ( !checkSet ) {\r
7882                 checkSet = set;\r
7883         }\r
7884 \r
7885         if ( !checkSet ) {\r
7886                 Sizzle.error( cur || selector );\r
7887         }\r
7888 \r
7889         if ( toString.call(checkSet) === "[object Array]" ) {\r
7890                 if ( !prune ) {\r
7891                         results.push.apply( results, checkSet );\r
7892 \r
7893                 } else if ( context && context.nodeType === 1 ) {\r
7894                         for ( i = 0; checkSet[i] != null; i++ ) {\r
7895                                 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {\r
7896                                         results.push( set[i] );\r
7897                                 }\r
7898                         }\r
7899 \r
7900                 } else {\r
7901                         for ( i = 0; checkSet[i] != null; i++ ) {\r
7902                                 if ( checkSet[i] && checkSet[i].nodeType === 1 ) {\r
7903                                         results.push( set[i] );\r
7904                                 }\r
7905                         }\r
7906                 }\r
7907 \r
7908         } else {\r
7909                 makeArray( checkSet, results );\r
7910         }\r
7911 \r
7912         if ( extra ) {\r
7913                 Sizzle( extra, origContext, results, seed );\r
7914                 Sizzle.uniqueSort( results );\r
7915         }\r
7916 \r
7917         return results;\r
7918 };\r
7919 \r
7920 Sizzle.uniqueSort = function( results ) {\r
7921         if ( sortOrder ) {\r
7922                 hasDuplicate = baseHasDuplicate;\r
7923                 results.sort( sortOrder );\r
7924 \r
7925                 if ( hasDuplicate ) {\r
7926                         for ( var i = 1; i < results.length; i++ ) {\r
7927                                 if ( results[i] === results[ i - 1 ] ) {\r
7928                                         results.splice( i--, 1 );\r
7929                                 }\r
7930                         }\r
7931                 }\r
7932         }\r
7933 \r
7934         return results;\r
7935 };\r
7936 \r
7937 Sizzle.matches = function( expr, set ) {\r
7938         return Sizzle( expr, null, null, set );\r
7939 };\r
7940 \r
7941 Sizzle.matchesSelector = function( node, expr ) {\r
7942         return Sizzle( expr, null, null, [node] ).length > 0;\r
7943 };\r
7944 \r
7945 Sizzle.find = function( expr, context, isXML ) {\r
7946         var set, i, len, match, type, left;\r
7947 \r
7948         if ( !expr ) {\r
7949                 return [];\r
7950         }\r
7951 \r
7952         for ( i = 0, len = Expr.order.length; i < len; i++ ) {\r
7953                 type = Expr.order[i];\r
7954 \r
7955                 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {\r
7956                         left = match[1];\r
7957                         match.splice( 1, 1 );\r
7958 \r
7959                         if ( left.substr( left.length - 1 ) !== "\\" ) {\r
7960                                 match[1] = (match[1] || "").replace( rBackslash, "" );\r
7961                                 set = Expr.find[ type ]( match, context, isXML );\r
7962 \r
7963                                 if ( set != null ) {\r
7964                                         expr = expr.replace( Expr.match[ type ], "" );\r
7965                                         break;\r
7966                                 }\r
7967                         }\r
7968                 }\r
7969         }\r
7970 \r
7971         if ( !set ) {\r
7972                 set = typeof context.getElementsByTagName !== "undefined" ?\r
7973                         context.getElementsByTagName( "*" ) :\r
7974                         [];\r
7975         }\r
7976 \r
7977         return { set: set, expr: expr };\r
7978 };\r
7979 \r
7980 Sizzle.filter = function( expr, set, inplace, not ) {\r
7981         var match, anyFound,\r
7982                 type, found, item, filter, left,\r
7983                 i, pass,\r
7984                 old = expr,\r
7985                 result = [],\r
7986                 curLoop = set,\r
7987                 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );\r
7988 \r
7989         while ( expr && set.length ) {\r
7990                 for ( type in Expr.filter ) {\r
7991                         if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {\r
7992                                 filter = Expr.filter[ type ];\r
7993                                 left = match[1];\r
7994 \r
7995                                 anyFound = false;\r
7996 \r
7997                                 match.splice(1,1);\r
7998 \r
7999                                 if ( left.substr( left.length - 1 ) === "\\" ) {\r
8000                                         continue;\r
8001                                 }\r
8002 \r
8003                                 if ( curLoop === result ) {\r
8004                                         result = [];\r
8005                                 }\r
8006 \r
8007                                 if ( Expr.preFilter[ type ] ) {\r
8008                                         match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );\r
8009 \r
8010                                         if ( !match ) {\r
8011                                                 anyFound = found = true;\r
8012 \r
8013                                         } else if ( match === true ) {\r
8014                                                 continue;\r
8015                                         }\r
8016                                 }\r
8017 \r
8018                                 if ( match ) {\r
8019                                         for ( i = 0; (item = curLoop[i]) != null; i++ ) {\r
8020                                                 if ( item ) {\r
8021                                                         found = filter( item, match, i, curLoop );\r
8022                                                         pass = not ^ found;\r
8023 \r
8024                                                         if ( inplace && found != null ) {\r
8025                                                                 if ( pass ) {\r
8026                                                                         anyFound = true;\r
8027 \r
8028                                                                 } else {\r
8029                                                                         curLoop[i] = false;\r
8030                                                                 }\r
8031 \r
8032                                                         } else if ( pass ) {\r
8033                                                                 result.push( item );\r
8034                                                                 anyFound = true;\r
8035                                                         }\r
8036                                                 }\r
8037                                         }\r
8038                                 }\r
8039 \r
8040                                 if ( found !== undefined ) {\r
8041                                         if ( !inplace ) {\r
8042                                                 curLoop = result;\r
8043                                         }\r
8044 \r
8045                                         expr = expr.replace( Expr.match[ type ], "" );\r
8046 \r
8047                                         if ( !anyFound ) {\r
8048                                                 return [];\r
8049                                         }\r
8050 \r
8051                                         break;\r
8052                                 }\r
8053                         }\r
8054                 }\r
8055 \r
8056                 // Improper expression\r
8057                 if ( expr === old ) {\r
8058                         if ( anyFound == null ) {\r
8059                                 Sizzle.error( expr );\r
8060 \r
8061                         } else {\r
8062                                 break;\r
8063                         }\r
8064                 }\r
8065 \r
8066                 old = expr;\r
8067         }\r
8068 \r
8069         return curLoop;\r
8070 };\r
8071 \r
8072 Sizzle.error = function( msg ) {\r
8073         throw new Error( "Syntax error, unrecognized expression: " + msg );\r
8074 };\r
8075 \r
8076 var getText = Sizzle.getText = function( elem ) {\r
8077     var i, node,\r
8078                 nodeType = elem.nodeType,\r
8079                 ret = "";\r
8080 \r
8081         if ( nodeType ) {\r
8082                 if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\r
8083                         // Use textContent || innerText for elements\r
8084                         if ( typeof elem.textContent === 'string' ) {\r
8085                                 return elem.textContent;\r
8086                         } else if ( typeof elem.innerText === 'string' ) {\r
8087                                 // Replace IE's carriage returns\r
8088                                 return elem.innerText.replace( rReturn, '' );\r
8089                         } else {\r
8090                                 // Traverse it's children\r
8091                                 for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {\r
8092                                         ret += getText( elem );\r
8093                                 }\r
8094                         }\r
8095                 } else if ( nodeType === 3 || nodeType === 4 ) {\r
8096                         return elem.nodeValue;\r
8097                 }\r
8098         } else {\r
8099 \r
8100                 // If no nodeType, this is expected to be an array\r
8101                 for ( i = 0; (node = elem[i]); i++ ) {\r
8102                         // Do not traverse comment nodes\r
8103                         if ( node.nodeType !== 8 ) {\r
8104                                 ret += getText( node );\r
8105                         }\r
8106                 }\r
8107         }\r
8108         return ret;\r
8109 };\r
8110 \r
8111 var Expr = Sizzle.selectors = {\r
8112         order: [ "ID", "NAME", "TAG" ],\r
8113 \r
8114         match: {\r
8115                 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
8116                 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,\r
8117                 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,\r
8118                 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,\r
8119                 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,\r
8120                 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,\r
8121                 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,\r
8122                 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/\r
8123         },\r
8124 \r
8125         leftMatch: {},\r
8126 \r
8127         attrMap: {\r
8128                 "class": "className",\r
8129                 "for": "htmlFor"\r
8130         },\r
8131 \r
8132         attrHandle: {\r
8133                 href: function( elem ) {\r
8134                         return elem.getAttribute( "href" );\r
8135                 },\r
8136                 type: function( elem ) {\r
8137                         return elem.getAttribute( "type" );\r
8138                 }\r
8139         },\r
8140 \r
8141         relative: {\r
8142                 "+": function(checkSet, part){\r
8143                         var isPartStr = typeof part === "string",\r
8144                                 isTag = isPartStr && !rNonWord.test( part ),\r
8145                                 isPartStrNotTag = isPartStr && !isTag;\r
8146 \r
8147                         if ( isTag ) {\r
8148                                 part = part.toLowerCase();\r
8149                         }\r
8150 \r
8151                         for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {\r
8152                                 if ( (elem = checkSet[i]) ) {\r
8153                                         while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}\r
8154 \r
8155                                         checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?\r
8156                                                 elem || false :\r
8157                                                 elem === part;\r
8158                                 }\r
8159                         }\r
8160 \r
8161                         if ( isPartStrNotTag ) {\r
8162                                 Sizzle.filter( part, checkSet, true );\r
8163                         }\r
8164                 },\r
8165 \r
8166                 ">": function( checkSet, part ) {\r
8167                         var elem,\r
8168                                 isPartStr = typeof part === "string",\r
8169                                 i = 0,\r
8170                                 l = checkSet.length;\r
8171 \r
8172                         if ( isPartStr && !rNonWord.test( part ) ) {\r
8173                                 part = part.toLowerCase();\r
8174 \r
8175                                 for ( ; i < l; i++ ) {\r
8176                                         elem = checkSet[i];\r
8177 \r
8178                                         if ( elem ) {\r
8179                                                 var parent = elem.parentNode;\r
8180                                                 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;\r
8181                                         }\r
8182                                 }\r
8183 \r
8184                         } else {\r
8185                                 for ( ; i < l; i++ ) {\r
8186                                         elem = checkSet[i];\r
8187 \r
8188                                         if ( elem ) {\r
8189                                                 checkSet[i] = isPartStr ?\r
8190                                                         elem.parentNode :\r
8191                                                         elem.parentNode === part;\r
8192                                         }\r
8193                                 }\r
8194 \r
8195                                 if ( isPartStr ) {\r
8196                                         Sizzle.filter( part, checkSet, true );\r
8197                                 }\r
8198                         }\r
8199                 },\r
8200 \r
8201                 "": function(checkSet, part, isXML){\r
8202                         var nodeCheck,\r
8203                                 doneName = done++,\r
8204                                 checkFn = dirCheck;\r
8205 \r
8206                         if ( typeof part === "string" && !rNonWord.test( part ) ) {\r
8207                                 part = part.toLowerCase();\r
8208                                 nodeCheck = part;\r
8209                                 checkFn = dirNodeCheck;\r
8210                         }\r
8211 \r
8212                         checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );\r
8213                 },\r
8214 \r
8215                 "~": function( checkSet, part, isXML ) {\r
8216                         var nodeCheck,\r
8217                                 doneName = done++,\r
8218                                 checkFn = dirCheck;\r
8219 \r
8220                         if ( typeof part === "string" && !rNonWord.test( part ) ) {\r
8221                                 part = part.toLowerCase();\r
8222                                 nodeCheck = part;\r
8223                                 checkFn = dirNodeCheck;\r
8224                         }\r
8225 \r
8226                         checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );\r
8227                 }\r
8228         },\r
8229 \r
8230         find: {\r
8231                 ID: function( match, context, isXML ) {\r
8232                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
8233                                 var m = context.getElementById(match[1]);\r
8234                                 // Check parentNode to catch when Blackberry 4.6 returns\r
8235                                 // nodes that are no longer in the document #6963\r
8236                                 return m && m.parentNode ? [m] : [];\r
8237                         }\r
8238                 },\r
8239 \r
8240                 NAME: function( match, context ) {\r
8241                         if ( typeof context.getElementsByName !== "undefined" ) {\r
8242                                 var ret = [],\r
8243                                         results = context.getElementsByName( match[1] );\r
8244 \r
8245                                 for ( var i = 0, l = results.length; i < l; i++ ) {\r
8246                                         if ( results[i].getAttribute("name") === match[1] ) {\r
8247                                                 ret.push( results[i] );\r
8248                                         }\r
8249                                 }\r
8250 \r
8251                                 return ret.length === 0 ? null : ret;\r
8252                         }\r
8253                 },\r
8254 \r
8255                 TAG: function( match, context ) {\r
8256                         if ( typeof context.getElementsByTagName !== "undefined" ) {\r
8257                                 return context.getElementsByTagName( match[1] );\r
8258                         }\r
8259                 }\r
8260         },\r
8261         preFilter: {\r
8262                 CLASS: function( match, curLoop, inplace, result, not, isXML ) {\r
8263                         match = " " + match[1].replace( rBackslash, "" ) + " ";\r
8264 \r
8265                         if ( isXML ) {\r
8266                                 return match;\r
8267                         }\r
8268 \r
8269                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {\r
8270                                 if ( elem ) {\r
8271                                         if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {\r
8272                                                 if ( !inplace ) {\r
8273                                                         result.push( elem );\r
8274                                                 }\r
8275 \r
8276                                         } else if ( inplace ) {\r
8277                                                 curLoop[i] = false;\r
8278                                         }\r
8279                                 }\r
8280                         }\r
8281 \r
8282                         return false;\r
8283                 },\r
8284 \r
8285                 ID: function( match ) {\r
8286                         return match[1].replace( rBackslash, "" );\r
8287                 },\r
8288 \r
8289                 TAG: function( match, curLoop ) {\r
8290                         return match[1].replace( rBackslash, "" ).toLowerCase();\r
8291                 },\r
8292 \r
8293                 CHILD: function( match ) {\r
8294                         if ( match[1] === "nth" ) {\r
8295                                 if ( !match[2] ) {\r
8296                                         Sizzle.error( match[0] );\r
8297                                 }\r
8298 \r
8299                                 match[2] = match[2].replace(/^\+|\s*/g, '');\r
8300 \r
8301                                 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'\r
8302                                 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(\r
8303                                         match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||\r
8304                                         !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);\r
8305 \r
8306                                 // calculate the numbers (first)n+(last) including if they are negative\r
8307                                 match[2] = (test[1] + (test[2] || 1)) - 0;\r
8308                                 match[3] = test[3] - 0;\r
8309                         }\r
8310                         else if ( match[2] ) {\r
8311                                 Sizzle.error( match[0] );\r
8312                         }\r
8313 \r
8314                         // TODO: Move to normal caching system\r
8315                         match[0] = done++;\r
8316 \r
8317                         return match;\r
8318                 },\r
8319 \r
8320                 ATTR: function( match, curLoop, inplace, result, not, isXML ) {\r
8321                         var name = match[1] = match[1].replace( rBackslash, "" );\r
8322 \r
8323                         if ( !isXML && Expr.attrMap[name] ) {\r
8324                                 match[1] = Expr.attrMap[name];\r
8325                         }\r
8326 \r
8327                         // Handle if an un-quoted value was used\r
8328                         match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );\r
8329 \r
8330                         if ( match[2] === "~=" ) {\r
8331                                 match[4] = " " + match[4] + " ";\r
8332                         }\r
8333 \r
8334                         return match;\r
8335                 },\r
8336 \r
8337                 PSEUDO: function( match, curLoop, inplace, result, not ) {\r
8338                         if ( match[1] === "not" ) {\r
8339                                 // If we're dealing with a complex expression, or a simple one\r
8340                                 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {\r
8341                                         match[3] = Sizzle(match[3], null, null, curLoop);\r
8342 \r
8343                                 } else {\r
8344                                         var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);\r
8345 \r
8346                                         if ( !inplace ) {\r
8347                                                 result.push.apply( result, ret );\r
8348                                         }\r
8349 \r
8350                                         return false;\r
8351                                 }\r
8352 \r
8353                         } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {\r
8354                                 return true;\r
8355                         }\r
8356 \r
8357                         return match;\r
8358                 },\r
8359 \r
8360                 POS: function( match ) {\r
8361                         match.unshift( true );\r
8362 \r
8363                         return match;\r
8364                 }\r
8365         },\r
8366 \r
8367         filters: {\r
8368                 enabled: function( elem ) {\r
8369                         return elem.disabled === false && elem.type !== "hidden";\r
8370                 },\r
8371 \r
8372                 disabled: function( elem ) {\r
8373                         return elem.disabled === true;\r
8374                 },\r
8375 \r
8376                 checked: function( elem ) {\r
8377                         return elem.checked === true;\r
8378                 },\r
8379 \r
8380                 selected: function( elem ) {\r
8381                         // Accessing this property makes selected-by-default\r
8382                         // options in Safari work properly\r
8383                         if ( elem.parentNode ) {\r
8384                                 elem.parentNode.selectedIndex;\r
8385                         }\r
8386 \r
8387                         return elem.selected === true;\r
8388                 },\r
8389 \r
8390                 parent: function( elem ) {\r
8391                         return !!elem.firstChild;\r
8392                 },\r
8393 \r
8394                 empty: function( elem ) {\r
8395                         return !elem.firstChild;\r
8396                 },\r
8397 \r
8398                 has: function( elem, i, match ) {\r
8399                         return !!Sizzle( match[3], elem ).length;\r
8400                 },\r
8401 \r
8402                 header: function( elem ) {\r
8403                         return (/h\d/i).test( elem.nodeName );\r
8404                 },\r
8405 \r
8406                 text: function( elem ) {\r
8407                         var attr = elem.getAttribute( "type" ), type = elem.type;\r
8408                         // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\r
8409                         // use getAttribute instead to test this case\r
8410                         return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );\r
8411                 },\r
8412 \r
8413                 radio: function( elem ) {\r
8414                         return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;\r
8415                 },\r
8416 \r
8417                 checkbox: function( elem ) {\r
8418                         return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;\r
8419                 },\r
8420 \r
8421                 file: function( elem ) {\r
8422                         return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;\r
8423                 },\r
8424 \r
8425                 password: function( elem ) {\r
8426                         return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;\r
8427                 },\r
8428 \r
8429                 submit: function( elem ) {\r
8430                         var name = elem.nodeName.toLowerCase();\r
8431                         return (name === "input" || name === "button") && "submit" === elem.type;\r
8432                 },\r
8433 \r
8434                 image: function( elem ) {\r
8435                         return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;\r
8436                 },\r
8437 \r
8438                 reset: function( elem ) {\r
8439                         var name = elem.nodeName.toLowerCase();\r
8440                         return (name === "input" || name === "button") && "reset" === elem.type;\r
8441                 },\r
8442 \r
8443                 button: function( elem ) {\r
8444                         var name = elem.nodeName.toLowerCase();\r
8445                         return name === "input" && "button" === elem.type || name === "button";\r
8446                 },\r
8447 \r
8448                 input: function( elem ) {\r
8449                         return (/input|select|textarea|button/i).test( elem.nodeName );\r
8450                 },\r
8451 \r
8452                 focus: function( elem ) {\r
8453                         return elem === elem.ownerDocument.activeElement;\r
8454                 }\r
8455         },\r
8456         setFilters: {\r
8457                 first: function( elem, i ) {\r
8458                         return i === 0;\r
8459                 },\r
8460 \r
8461                 last: function( elem, i, match, array ) {\r
8462                         return i === array.length - 1;\r
8463                 },\r
8464 \r
8465                 even: function( elem, i ) {\r
8466                         return i % 2 === 0;\r
8467                 },\r
8468 \r
8469                 odd: function( elem, i ) {\r
8470                         return i % 2 === 1;\r
8471                 },\r
8472 \r
8473                 lt: function( elem, i, match ) {\r
8474                         return i < match[3] - 0;\r
8475                 },\r
8476 \r
8477                 gt: function( elem, i, match ) {\r
8478                         return i > match[3] - 0;\r
8479                 },\r
8480 \r
8481                 nth: function( elem, i, match ) {\r
8482                         return match[3] - 0 === i;\r
8483                 },\r
8484 \r
8485                 eq: function( elem, i, match ) {\r
8486                         return match[3] - 0 === i;\r
8487                 }\r
8488         },\r
8489         filter: {\r
8490                 PSEUDO: function( elem, match, i, array ) {\r
8491                         var name = match[1],\r
8492                                 filter = Expr.filters[ name ];\r
8493 \r
8494                         if ( filter ) {\r
8495                                 return filter( elem, i, match, array );\r
8496 \r
8497                         } else if ( name === "contains" ) {\r
8498                                 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;\r
8499 \r
8500                         } else if ( name === "not" ) {\r
8501                                 var not = match[3];\r
8502 \r
8503                                 for ( var j = 0, l = not.length; j < l; j++ ) {\r
8504                                         if ( not[j] === elem ) {\r
8505                                                 return false;\r
8506                                         }\r
8507                                 }\r
8508 \r
8509                                 return true;\r
8510 \r
8511                         } else {\r
8512                                 Sizzle.error( name );\r
8513                         }\r
8514                 },\r
8515 \r
8516                 CHILD: function( elem, match ) {\r
8517                         var first, last,\r
8518                                 doneName, parent, cache,\r
8519                                 count, diff,\r
8520                                 type = match[1],\r
8521                                 node = elem;\r
8522 \r
8523                         switch ( type ) {\r
8524                                 case "only":\r
8525                                 case "first":\r
8526                                         while ( (node = node.previousSibling) ) {\r
8527                                                 if ( node.nodeType === 1 ) {\r
8528                                                         return false;\r
8529                                                 }\r
8530                                         }\r
8531 \r
8532                                         if ( type === "first" ) {\r
8533                                                 return true;\r
8534                                         }\r
8535 \r
8536                                         node = elem;\r
8537 \r
8538                                         /* falls through */\r
8539                                 case "last":\r
8540                                         while ( (node = node.nextSibling) ) {\r
8541                                                 if ( node.nodeType === 1 ) {\r
8542                                                         return false;\r
8543                                                 }\r
8544                                         }\r
8545 \r
8546                                         return true;\r
8547 \r
8548                                 case "nth":\r
8549                                         first = match[2];\r
8550                                         last = match[3];\r
8551 \r
8552                                         if ( first === 1 && last === 0 ) {\r
8553                                                 return true;\r
8554                                         }\r
8555 \r
8556                                         doneName = match[0];\r
8557                                         parent = elem.parentNode;\r
8558 \r
8559                                         if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {\r
8560                                                 count = 0;\r
8561 \r
8562                                                 for ( node = parent.firstChild; node; node = node.nextSibling ) {\r
8563                                                         if ( node.nodeType === 1 ) {\r
8564                                                                 node.nodeIndex = ++count;\r
8565                                                         }\r
8566                                                 }\r
8567 \r
8568                                                 parent[ expando ] = doneName;\r
8569                                         }\r
8570 \r
8571                                         diff = elem.nodeIndex - last;\r
8572 \r
8573                                         if ( first === 0 ) {\r
8574                                                 return diff === 0;\r
8575 \r
8576                                         } else {\r
8577                                                 return ( diff % first === 0 && diff / first >= 0 );\r
8578                                         }\r
8579                         }\r
8580                 },\r
8581 \r
8582                 ID: function( elem, match ) {\r
8583                         return elem.nodeType === 1 && elem.getAttribute("id") === match;\r
8584                 },\r
8585 \r
8586                 TAG: function( elem, match ) {\r
8587                         return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;\r
8588                 },\r
8589 \r
8590                 CLASS: function( elem, match ) {\r
8591                         return (" " + (elem.className || elem.getAttribute("class")) + " ")\r
8592                                 .indexOf( match ) > -1;\r
8593                 },\r
8594 \r
8595                 ATTR: function( elem, match ) {\r
8596                         var name = match[1],\r
8597                                 result = Sizzle.attr ?\r
8598                                         Sizzle.attr( elem, name ) :\r
8599                                         Expr.attrHandle[ name ] ?\r
8600                                         Expr.attrHandle[ name ]( elem ) :\r
8601                                         elem[ name ] != null ?\r
8602                                                 elem[ name ] :\r
8603                                                 elem.getAttribute( name ),\r
8604                                 value = result + "",\r
8605                                 type = match[2],\r
8606                                 check = match[4];\r
8607 \r
8608                         return result == null ?\r
8609                                 type === "!=" :\r
8610                                 !type && Sizzle.attr ?\r
8611                                 result != null :\r
8612                                 type === "=" ?\r
8613                                 value === check :\r
8614                                 type === "*=" ?\r
8615                                 value.indexOf(check) >= 0 :\r
8616                                 type === "~=" ?\r
8617                                 (" " + value + " ").indexOf(check) >= 0 :\r
8618                                 !check ?\r
8619                                 value && result !== false :\r
8620                                 type === "!=" ?\r
8621                                 value !== check :\r
8622                                 type === "^=" ?\r
8623                                 value.indexOf(check) === 0 :\r
8624                                 type === "$=" ?\r
8625                                 value.substr(value.length - check.length) === check :\r
8626                                 type === "|=" ?\r
8627                                 value === check || value.substr(0, check.length + 1) === check + "-" :\r
8628                                 false;\r
8629                 },\r
8630 \r
8631                 POS: function( elem, match, i, array ) {\r
8632                         var name = match[2],\r
8633                                 filter = Expr.setFilters[ name ];\r
8634 \r
8635                         if ( filter ) {\r
8636                                 return filter( elem, i, match, array );\r
8637                         }\r
8638                 }\r
8639         }\r
8640 };\r
8641 \r
8642 var origPOS = Expr.match.POS,\r
8643         fescape = function(all, num){\r
8644                 return "\\" + (num - 0 + 1);\r
8645         };\r
8646 \r
8647 for ( var type in Expr.match ) {\r
8648         Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );\r
8649         Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );\r
8650 }\r
8651 // Expose origPOS\r
8652 // "global" as in regardless of relation to brackets/parens\r
8653 Expr.match.globalPOS = origPOS;\r
8654 \r
8655 var makeArray = function( array, results ) {\r
8656         array = Array.prototype.slice.call( array, 0 );\r
8657 \r
8658         if ( results ) {\r
8659                 results.push.apply( results, array );\r
8660                 return results;\r
8661         }\r
8662 \r
8663         return array;\r
8664 };\r
8665 \r
8666 // Perform a simple check to determine if the browser is capable of\r
8667 // converting a NodeList to an array using builtin methods.\r
8668 // Also verifies that the returned array holds DOM nodes\r
8669 // (which is not the case in the Blackberry browser)\r
8670 try {\r
8671         Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;\r
8672 \r
8673 // Provide a fallback method if it does not work\r
8674 } catch( e ) {\r
8675         makeArray = function( array, results ) {\r
8676                 var i = 0,\r
8677                         ret = results || [];\r
8678 \r
8679                 if ( toString.call(array) === "[object Array]" ) {\r
8680                         Array.prototype.push.apply( ret, array );\r
8681 \r
8682                 } else {\r
8683                         if ( typeof array.length === "number" ) {\r
8684                                 for ( var l = array.length; i < l; i++ ) {\r
8685                                         ret.push( array[i] );\r
8686                                 }\r
8687 \r
8688                         } else {\r
8689                                 for ( ; array[i]; i++ ) {\r
8690                                         ret.push( array[i] );\r
8691                                 }\r
8692                         }\r
8693                 }\r
8694 \r
8695                 return ret;\r
8696         };\r
8697 }\r
8698 \r
8699 var sortOrder, siblingCheck;\r
8700 \r
8701 if ( document.documentElement.compareDocumentPosition ) {\r
8702         sortOrder = function( a, b ) {\r
8703                 if ( a === b ) {\r
8704                         hasDuplicate = true;\r
8705                         return 0;\r
8706                 }\r
8707 \r
8708                 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {\r
8709                         return a.compareDocumentPosition ? -1 : 1;\r
8710                 }\r
8711 \r
8712                 return a.compareDocumentPosition(b) & 4 ? -1 : 1;\r
8713         };\r
8714 \r
8715 } else {\r
8716         sortOrder = function( a, b ) {\r
8717                 // The nodes are identical, we can exit early\r
8718                 if ( a === b ) {\r
8719                         hasDuplicate = true;\r
8720                         return 0;\r
8721 \r
8722                 // Fallback to using sourceIndex (in IE) if it's available on both nodes\r
8723                 } else if ( a.sourceIndex && b.sourceIndex ) {\r
8724                         return a.sourceIndex - b.sourceIndex;\r
8725                 }\r
8726 \r
8727                 var al, bl,\r
8728                         ap = [],\r
8729                         bp = [],\r
8730                         aup = a.parentNode,\r
8731                         bup = b.parentNode,\r
8732                         cur = aup;\r
8733 \r
8734                 // If the nodes are siblings (or identical) we can do a quick check\r
8735                 if ( aup === bup ) {\r
8736                         return siblingCheck( a, b );\r
8737 \r
8738                 // If no parents were found then the nodes are disconnected\r
8739                 } else if ( !aup ) {\r
8740                         return -1;\r
8741 \r
8742                 } else if ( !bup ) {\r
8743                         return 1;\r
8744                 }\r
8745 \r
8746                 // Otherwise they're somewhere else in the tree so we need\r
8747                 // to build up a full list of the parentNodes for comparison\r
8748                 while ( cur ) {\r
8749                         ap.unshift( cur );\r
8750                         cur = cur.parentNode;\r
8751                 }\r
8752 \r
8753                 cur = bup;\r
8754 \r
8755                 while ( cur ) {\r
8756                         bp.unshift( cur );\r
8757                         cur = cur.parentNode;\r
8758                 }\r
8759 \r
8760                 al = ap.length;\r
8761                 bl = bp.length;\r
8762 \r
8763                 // Start walking down the tree looking for a discrepancy\r
8764                 for ( var i = 0; i < al && i < bl; i++ ) {\r
8765                         if ( ap[i] !== bp[i] ) {\r
8766                                 return siblingCheck( ap[i], bp[i] );\r
8767                         }\r
8768                 }\r
8769 \r
8770                 // We ended someplace up the tree so do a sibling check\r
8771                 return i === al ?\r
8772                         siblingCheck( a, bp[i], -1 ) :\r
8773                         siblingCheck( ap[i], b, 1 );\r
8774         };\r
8775 \r
8776         siblingCheck = function( a, b, ret ) {\r
8777                 if ( a === b ) {\r
8778                         return ret;\r
8779                 }\r
8780 \r
8781                 var cur = a.nextSibling;\r
8782 \r
8783                 while ( cur ) {\r
8784                         if ( cur === b ) {\r
8785                                 return -1;\r
8786                         }\r
8787 \r
8788                         cur = cur.nextSibling;\r
8789                 }\r
8790 \r
8791                 return 1;\r
8792         };\r
8793 }\r
8794 \r
8795 // Check to see if the browser returns elements by name when\r
8796 // querying by getElementById (and provide a workaround)\r
8797 (function(){\r
8798         // We're going to inject a fake input element with a specified name\r
8799         var form = document.createElement("div"),\r
8800                 id = "script" + (new Date()).getTime(),\r
8801                 root = document.documentElement;\r
8802 \r
8803         form.innerHTML = "<a name='" + id + "'/>";\r
8804 \r
8805         // Inject it into the root element, check its status, and remove it quickly\r
8806         root.insertBefore( form, root.firstChild );\r
8807 \r
8808         // The workaround has to do additional checks after a getElementById\r
8809         // Which slows things down for other browsers (hence the branching)\r
8810         if ( document.getElementById( id ) ) {\r
8811                 Expr.find.ID = function( match, context, isXML ) {\r
8812                         if ( typeof context.getElementById !== "undefined" && !isXML ) {\r
8813                                 var m = context.getElementById(match[1]);\r
8814 \r
8815                                 return m ?\r
8816                                         m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?\r
8817                                                 [m] :\r
8818                                                 undefined :\r
8819                                         [];\r
8820                         }\r
8821                 };\r
8822 \r
8823                 Expr.filter.ID = function( elem, match ) {\r
8824                         var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");\r
8825 \r
8826                         return elem.nodeType === 1 && node && node.nodeValue === match;\r
8827                 };\r
8828         }\r
8829 \r
8830         root.removeChild( form );\r
8831 \r
8832         // release memory in IE\r
8833         root = form = null;\r
8834 })();\r
8835 \r
8836 (function(){\r
8837         // Check to see if the browser returns only elements\r
8838         // when doing getElementsByTagName("*")\r
8839 \r
8840         // Create a fake element\r
8841         var div = document.createElement("div");\r
8842         div.appendChild( document.createComment("") );\r
8843 \r
8844         // Make sure no comments are found\r
8845         if ( div.getElementsByTagName("*").length > 0 ) {\r
8846                 Expr.find.TAG = function( match, context ) {\r
8847                         var results = context.getElementsByTagName( match[1] );\r
8848 \r
8849                         // Filter out possible comments\r
8850                         if ( match[1] === "*" ) {\r
8851                                 var tmp = [];\r
8852 \r
8853                                 for ( var i = 0; results[i]; i++ ) {\r
8854                                         if ( results[i].nodeType === 1 ) {\r
8855                                                 tmp.push( results[i] );\r
8856                                         }\r
8857                                 }\r
8858 \r
8859                                 results = tmp;\r
8860                         }\r
8861 \r
8862                         return results;\r
8863                 };\r
8864         }\r
8865 \r
8866         // Check to see if an attribute returns normalized href attributes\r
8867         div.innerHTML = "<a href='#'></a>";\r
8868 \r
8869         if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&\r
8870                         div.firstChild.getAttribute("href") !== "#" ) {\r
8871 \r
8872                 Expr.attrHandle.href = function( elem ) {\r
8873                         return elem.getAttribute( "href", 2 );\r
8874                 };\r
8875         }\r
8876 \r
8877         // release memory in IE\r
8878         div = null;\r
8879 })();\r
8880 \r
8881 if ( document.querySelectorAll ) {\r
8882         (function(){\r
8883                 var oldSizzle = Sizzle,\r
8884                         div = document.createElement("div"),\r
8885                         id = "__sizzle__";\r
8886 \r
8887                 div.innerHTML = "<p class='TEST'></p>";\r
8888 \r
8889                 // Safari can't handle uppercase or unicode characters when\r
8890                 // in quirks mode.\r
8891                 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {\r
8892                         return;\r
8893                 }\r
8894 \r
8895                 Sizzle = function( query, context, extra, seed ) {\r
8896                         context = context || document;\r
8897 \r
8898                         // Only use querySelectorAll on non-XML documents\r
8899                         // (ID selectors don't work in non-HTML documents)\r
8900                         if ( !seed && !Sizzle.isXML(context) ) {\r
8901                                 // See if we find a selector to speed up\r
8902                                 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );\r
8903 \r
8904                                 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {\r
8905                                         // Speed-up: Sizzle("TAG")\r
8906                                         if ( match[1] ) {\r
8907                                                 return makeArray( context.getElementsByTagName( query ), extra );\r
8908 \r
8909                                         // Speed-up: Sizzle(".CLASS")\r
8910                                         } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {\r
8911                                                 return makeArray( context.getElementsByClassName( match[2] ), extra );\r
8912                                         }\r
8913                                 }\r
8914 \r
8915                                 if ( context.nodeType === 9 ) {\r
8916                                         // Speed-up: Sizzle("body")\r
8917                                         // The body element only exists once, optimize finding it\r
8918                                         if ( query === "body" && context.body ) {\r
8919                                                 return makeArray( [ context.body ], extra );\r
8920 \r
8921                                         // Speed-up: Sizzle("#ID")\r
8922                                         } else if ( match && match[3] ) {\r
8923                                                 var elem = context.getElementById( match[3] );\r
8924 \r
8925                                                 // Check parentNode to catch when Blackberry 4.6 returns\r
8926                                                 // nodes that are no longer in the document #6963\r
8927                                                 if ( elem && elem.parentNode ) {\r
8928                                                         // Handle the case where IE and Opera return items\r
8929                                                         // by name instead of ID\r
8930                                                         if ( elem.id === match[3] ) {\r
8931                                                                 return makeArray( [ elem ], extra );\r
8932                                                         }\r
8933 \r
8934                                                 } else {\r
8935                                                         return makeArray( [], extra );\r
8936                                                 }\r
8937                                         }\r
8938 \r
8939                                         try {\r
8940                                                 return makeArray( context.querySelectorAll(query), extra );\r
8941                                         } catch(qsaError) {}\r
8942 \r
8943                                 // qSA works strangely on Element-rooted queries\r
8944                                 // We can work around this by specifying an extra ID on the root\r
8945                                 // and working up from there (Thanks to Andrew Dupont for the technique)\r
8946                                 // IE 8 doesn't work on object elements\r
8947                                 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {\r
8948                                         var oldContext = context,\r
8949                                                 old = context.getAttribute( "id" ),\r
8950                                                 nid = old || id,\r
8951                                                 hasParent = context.parentNode,\r
8952                                                 relativeHierarchySelector = /^\s*[+~]/.test( query );\r
8953 \r
8954                                         if ( !old ) {\r
8955                                                 context.setAttribute( "id", nid );\r
8956                                         } else {\r
8957                                                 nid = nid.replace( /'/g, "\\$&" );\r
8958                                         }\r
8959                                         if ( relativeHierarchySelector && hasParent ) {\r
8960                                                 context = context.parentNode;\r
8961                                         }\r
8962 \r
8963                                         try {\r
8964                                                 if ( !relativeHierarchySelector || hasParent ) {\r
8965                                                         return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );\r
8966                                                 }\r
8967 \r
8968                                         } catch(pseudoError) {\r
8969                                         } finally {\r
8970                                                 if ( !old ) {\r
8971                                                         oldContext.removeAttribute( "id" );\r
8972                                                 }\r
8973                                         }\r
8974                                 }\r
8975                         }\r
8976 \r
8977                         return oldSizzle(query, context, extra, seed);\r
8978                 };\r
8979 \r
8980                 for ( var prop in oldSizzle ) {\r
8981                         Sizzle[ prop ] = oldSizzle[ prop ];\r
8982                 }\r
8983 \r
8984                 // release memory in IE\r
8985                 div = null;\r
8986         })();\r
8987 }\r
8988 \r
8989 (function(){\r
8990         var html = document.documentElement,\r
8991                 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;\r
8992 \r
8993         if ( matches ) {\r
8994                 // Check to see if it's possible to do matchesSelector\r
8995                 // on a disconnected node (IE 9 fails this)\r
8996                 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),\r
8997                         pseudoWorks = false;\r
8998 \r
8999                 try {\r
9000                         // This should fail with an exception\r
9001                         // Gecko does not error, returns false instead\r
9002                         matches.call( document.documentElement, "[test!='']:sizzle" );\r
9003 \r
9004                 } catch( pseudoError ) {\r
9005                         pseudoWorks = true;\r
9006                 }\r
9007 \r
9008                 Sizzle.matchesSelector = function( node, expr ) {\r
9009                         // Make sure that attribute selectors are quoted\r
9010                         expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");\r
9011 \r
9012                         if ( !Sizzle.isXML( node ) ) {\r
9013                                 try {\r
9014                                         if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {\r
9015                                                 var ret = matches.call( node, expr );\r
9016 \r
9017                                                 // IE 9's matchesSelector returns false on disconnected nodes\r
9018                                                 if ( ret || !disconnectedMatch ||\r
9019                                                                 // As well, disconnected nodes are said to be in a document\r
9020                                                                 // fragment in IE 9, so check for that\r
9021                                                                 node.document && node.document.nodeType !== 11 ) {\r
9022                                                         return ret;\r
9023                                                 }\r
9024                                         }\r
9025                                 } catch(e) {}\r
9026                         }\r
9027 \r
9028                         return Sizzle(expr, null, null, [node]).length > 0;\r
9029                 };\r
9030         }\r
9031 })();\r
9032 \r
9033 (function(){\r
9034         var div = document.createElement("div");\r
9035 \r
9036         div.innerHTML = "<div class='test e'></div><div class='test'></div>";\r
9037 \r
9038         // Opera can't find a second classname (in 9.6)\r
9039         // Also, make sure that getElementsByClassName actually exists\r
9040         if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {\r
9041                 return;\r
9042         }\r
9043 \r
9044         // Safari caches class attributes, doesn't catch changes (in 3.2)\r
9045         div.lastChild.className = "e";\r
9046 \r
9047         if ( div.getElementsByClassName("e").length === 1 ) {\r
9048                 return;\r
9049         }\r
9050 \r
9051         Expr.order.splice(1, 0, "CLASS");\r
9052         Expr.find.CLASS = function( match, context, isXML ) {\r
9053                 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {\r
9054                         return context.getElementsByClassName(match[1]);\r
9055                 }\r
9056         };\r
9057 \r
9058         // release memory in IE\r
9059         div = null;\r
9060 })();\r
9061 \r
9062 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
9063         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
9064                 var elem = checkSet[i];\r
9065 \r
9066                 if ( elem ) {\r
9067                         var match = false;\r
9068 \r
9069                         elem = elem[dir];\r
9070 \r
9071                         while ( elem ) {\r
9072                                 if ( elem[ expando ] === doneName ) {\r
9073                                         match = checkSet[elem.sizset];\r
9074                                         break;\r
9075                                 }\r
9076 \r
9077                                 if ( elem.nodeType === 1 && !isXML ){\r
9078                                         elem[ expando ] = doneName;\r
9079                                         elem.sizset = i;\r
9080                                 }\r
9081 \r
9082                                 if ( elem.nodeName.toLowerCase() === cur ) {\r
9083                                         match = elem;\r
9084                                         break;\r
9085                                 }\r
9086 \r
9087                                 elem = elem[dir];\r
9088                         }\r
9089 \r
9090                         checkSet[i] = match;\r
9091                 }\r
9092         }\r
9093 }\r
9094 \r
9095 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {\r
9096         for ( var i = 0, l = checkSet.length; i < l; i++ ) {\r
9097                 var elem = checkSet[i];\r
9098 \r
9099                 if ( elem ) {\r
9100                         var match = false;\r
9101 \r
9102                         elem = elem[dir];\r
9103 \r
9104                         while ( elem ) {\r
9105                                 if ( elem[ expando ] === doneName ) {\r
9106                                         match = checkSet[elem.sizset];\r
9107                                         break;\r
9108                                 }\r
9109 \r
9110                                 if ( elem.nodeType === 1 ) {\r
9111                                         if ( !isXML ) {\r
9112                                                 elem[ expando ] = doneName;\r
9113                                                 elem.sizset = i;\r
9114                                         }\r
9115 \r
9116                                         if ( typeof cur !== "string" ) {\r
9117                                                 if ( elem === cur ) {\r
9118                                                         match = true;\r
9119                                                         break;\r
9120                                                 }\r
9121 \r
9122                                         } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {\r
9123                                                 match = elem;\r
9124                                                 break;\r
9125                                         }\r
9126                                 }\r
9127 \r
9128                                 elem = elem[dir];\r
9129                         }\r
9130 \r
9131                         checkSet[i] = match;\r
9132                 }\r
9133         }\r
9134 }\r
9135 \r
9136 if ( document.documentElement.contains ) {\r
9137         Sizzle.contains = function( a, b ) {\r
9138                 return a !== b && (a.contains ? a.contains(b) : true);\r
9139         };\r
9140 \r
9141 } else if ( document.documentElement.compareDocumentPosition ) {\r
9142         Sizzle.contains = function( a, b ) {\r
9143                 return !!(a.compareDocumentPosition(b) & 16);\r
9144         };\r
9145 \r
9146 } else {\r
9147         Sizzle.contains = function() {\r
9148                 return false;\r
9149         };\r
9150 }\r
9151 \r
9152 Sizzle.isXML = function( elem ) {\r
9153         // documentElement is verified for cases where it doesn't yet exist\r
9154         // (such as loading iframes in IE - #4833)\r
9155         var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;\r
9156 \r
9157         return documentElement ? documentElement.nodeName !== "HTML" : false;\r
9158 };\r
9159 \r
9160 var posProcess = function( selector, context, seed ) {\r
9161         var match,\r
9162                 tmpSet = [],\r
9163                 later = "",\r
9164                 root = context.nodeType ? [context] : context;\r
9165 \r
9166         // Position selectors must be done after the filter\r
9167         // And so must :not(positional) so we move all PSEUDOs to the end\r
9168         while ( (match = Expr.match.PSEUDO.exec( selector )) ) {\r
9169                 later += match[0];\r
9170                 selector = selector.replace( Expr.match.PSEUDO, "" );\r
9171         }\r
9172 \r
9173         selector = Expr.relative[selector] ? selector + "*" : selector;\r
9174 \r
9175         for ( var i = 0, l = root.length; i < l; i++ ) {\r
9176                 Sizzle( selector, root[i], tmpSet, seed );\r
9177         }\r
9178 \r
9179         return Sizzle.filter( later, tmpSet );\r
9180 };\r
9181 \r
9182 // EXPOSE\r
9183 \r
9184 window.tinymce.dom.Sizzle = Sizzle;\r
9185 \r
9186 })();\r
9187 \r
9188 \r
9189 (function(tinymce) {\r
9190         tinymce.dom.Element = function(id, settings) {\r
9191                 var t = this, dom, el;\r
9192 \r
9193                 t.settings = settings = settings || {};\r
9194                 t.id = id;\r
9195                 t.dom = dom = settings.dom || tinymce.DOM;\r
9196 \r
9197                 // Only IE leaks DOM references, this is a lot faster\r
9198                 if (!tinymce.isIE)\r
9199                         el = dom.get(t.id);\r
9200 \r
9201                 tinymce.each(\r
9202                                 ('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + \r
9203                                 'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + \r
9204                                 'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + \r
9205                                 'isHidden,setHTML,get').split(/,/), function(k) {\r
9206                                         t[k] = function() {\r
9207                                                 var a = [id], i;\r
9208 \r
9209                                                 for (i = 0; i < arguments.length; i++)\r
9210                                                         a.push(arguments[i]);\r
9211 \r
9212                                                 a = dom[k].apply(dom, a);\r
9213                                                 t.update(k);\r
9214 \r
9215                                                 return a;\r
9216                                         };\r
9217                         }\r
9218                 );\r
9219 \r
9220                 tinymce.extend(t, {\r
9221                         on : function(n, f, s) {\r
9222                                 return tinymce.dom.Event.add(t.id, n, f, s);\r
9223                         },\r
9224 \r
9225                         getXY : function() {\r
9226                                 return {\r
9227                                         x : parseInt(t.getStyle('left')),\r
9228                                         y : parseInt(t.getStyle('top'))\r
9229                                 };\r
9230                         },\r
9231 \r
9232                         getSize : function() {\r
9233                                 var n = dom.get(t.id);\r
9234 \r
9235                                 return {\r
9236                                         w : parseInt(t.getStyle('width') || n.clientWidth),\r
9237                                         h : parseInt(t.getStyle('height') || n.clientHeight)\r
9238                                 };\r
9239                         },\r
9240 \r
9241                         moveTo : function(x, y) {\r
9242                                 t.setStyles({left : x, top : y});\r
9243                         },\r
9244 \r
9245                         moveBy : function(x, y) {\r
9246                                 var p = t.getXY();\r
9247 \r
9248                                 t.moveTo(p.x + x, p.y + y);\r
9249                         },\r
9250 \r
9251                         resizeTo : function(w, h) {\r
9252                                 t.setStyles({width : w, height : h});\r
9253                         },\r
9254 \r
9255                         resizeBy : function(w, h) {\r
9256                                 var s = t.getSize();\r
9257 \r
9258                                 t.resizeTo(s.w + w, s.h + h);\r
9259                         },\r
9260 \r
9261                         update : function(k) {\r
9262                                 var b;\r
9263 \r
9264                                 if (tinymce.isIE6 && settings.blocker) {\r
9265                                         k = k || '';\r
9266 \r
9267                                         // Ignore getters\r
9268                                         if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)\r
9269                                                 return;\r
9270 \r
9271                                         // Remove blocker on remove\r
9272                                         if (k == 'remove') {\r
9273                                                 dom.remove(t.blocker);\r
9274                                                 return;\r
9275                                         }\r
9276 \r
9277                                         if (!t.blocker) {\r
9278                                                 t.blocker = dom.uniqueId();\r
9279                                                 b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});\r
9280                                                 dom.setStyle(b, 'opacity', 0);\r
9281                                         } else\r
9282                                                 b = dom.get(t.blocker);\r
9283 \r
9284                                         dom.setStyles(b, {\r
9285                                                 left : t.getStyle('left', 1),\r
9286                                                 top : t.getStyle('top', 1),\r
9287                                                 width : t.getStyle('width', 1),\r
9288                                                 height : t.getStyle('height', 1),\r
9289                                                 display : t.getStyle('display', 1),\r
9290                                                 zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1\r
9291                                         });\r
9292                                 }\r
9293                         }\r
9294                 });\r
9295         };\r
9296 })(tinymce);\r
9297 \r
9298 (function(tinymce) {\r
9299         function trimNl(s) {\r
9300                 return s.replace(/[\n\r]+/g, '');\r
9301         };\r
9302 \r
9303         // Shorten names\r
9304         var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;\r
9305 \r
9306         tinymce.create('tinymce.dom.Selection', {\r
9307                 Selection : function(dom, win, serializer, editor) {\r
9308                         var t = this;\r
9309 \r
9310                         t.dom = dom;\r
9311                         t.win = win;\r
9312                         t.serializer = serializer;\r
9313                         t.editor = editor;\r
9314 \r
9315                         // Add events\r
9316                         each([\r
9317                                 'onBeforeSetContent',\r
9318 \r
9319                                 'onBeforeGetContent',\r
9320 \r
9321                                 'onSetContent',\r
9322 \r
9323                                 'onGetContent'\r
9324                         ], function(e) {\r
9325                                 t[e] = new tinymce.util.Dispatcher(t);\r
9326                         });\r
9327 \r
9328                         // No W3C Range support\r
9329                         if (!t.win.getSelection)\r
9330                                 t.tridentSel = new tinymce.dom.TridentSelection(t);\r
9331 \r
9332                         if (tinymce.isIE && dom.boxModel)\r
9333                                 this._fixIESelection();\r
9334 \r
9335                         // Prevent leaks\r
9336                         tinymce.addUnload(t.destroy, t);\r
9337                 },\r
9338 \r
9339                 setCursorLocation: function(node, offset) {\r
9340                         var t = this; var r = t.dom.createRng();\r
9341                         r.setStart(node, offset);\r
9342                         r.setEnd(node, offset);\r
9343                         t.setRng(r);\r
9344                         t.collapse(false);\r
9345                 },\r
9346                 getContent : function(s) {\r
9347                         var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;\r
9348 \r
9349                         s = s || {};\r
9350                         wb = wa = '';\r
9351                         s.get = true;\r
9352                         s.format = s.format || 'html';\r
9353                         s.forced_root_block = '';\r
9354                         t.onBeforeGetContent.dispatch(t, s);\r
9355 \r
9356                         if (s.format == 'text')\r
9357                                 return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));\r
9358 \r
9359                         if (r.cloneContents) {\r
9360                                 n = r.cloneContents();\r
9361 \r
9362                                 if (n)\r
9363                                         e.appendChild(n);\r
9364                         } else if (is(r.item) || is(r.htmlText)) {\r
9365                                 // IE will produce invalid markup if elements are present that\r
9366                                 // it doesn't understand like custom elements or HTML5 elements.\r
9367                                 // Adding a BR in front of the contents and then remoiving it seems to fix it though.\r
9368                                 e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);\r
9369                                 e.removeChild(e.firstChild);\r
9370                         } else\r
9371                                 e.innerHTML = r.toString();\r
9372 \r
9373                         // Keep whitespace before and after\r
9374                         if (/^\s/.test(e.innerHTML))\r
9375                                 wb = ' ';\r
9376 \r
9377                         if (/\s+$/.test(e.innerHTML))\r
9378                                 wa = ' ';\r
9379 \r
9380                         s.getInner = true;\r
9381 \r
9382                         s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;\r
9383                         t.onGetContent.dispatch(t, s);\r
9384 \r
9385                         return s.content;\r
9386                 },\r
9387 \r
9388                 setContent : function(content, args) {\r
9389                         var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;\r
9390 \r
9391                         args = args || {format : 'html'};\r
9392                         args.set = true;\r
9393                         content = args.content = content;\r
9394 \r
9395                         // Dispatch before set content event\r
9396                         if (!args.no_events)\r
9397                                 self.onBeforeSetContent.dispatch(self, args);\r
9398 \r
9399                         content = args.content;\r
9400 \r
9401                         if (rng.insertNode) {\r
9402                                 // Make caret marker since insertNode places the caret in the beginning of text after insert\r
9403                                 content += '<span id="__caret">_</span>';\r
9404 \r
9405                                 // Delete and insert new node\r
9406                                 if (rng.startContainer == doc && rng.endContainer == doc) {\r
9407                                         // WebKit will fail if the body is empty since the range is then invalid and it can't insert contents\r
9408                                         doc.body.innerHTML = content;\r
9409                                 } else {\r
9410                                         rng.deleteContents();\r
9411 \r
9412                                         if (doc.body.childNodes.length === 0) {\r
9413                                                 doc.body.innerHTML = content;\r
9414                                         } else {\r
9415                                                 // createContextualFragment doesn't exists in IE 9 DOMRanges\r
9416                                                 if (rng.createContextualFragment) {\r
9417                                                         rng.insertNode(rng.createContextualFragment(content));\r
9418                                                 } else {\r
9419                                                         // Fake createContextualFragment call in IE 9\r
9420                                                         frag = doc.createDocumentFragment();\r
9421                                                         temp = doc.createElement('div');\r
9422 \r
9423                                                         frag.appendChild(temp);\r
9424                                                         temp.outerHTML = content;\r
9425 \r
9426                                                         rng.insertNode(frag);\r
9427                                                 }\r
9428                                         }\r
9429                                 }\r
9430 \r
9431                                 // Move to caret marker\r
9432                                 caretNode = self.dom.get('__caret');\r
9433 \r
9434                                 // Make sure we wrap it compleatly, Opera fails with a simple select call\r
9435                                 rng = doc.createRange();\r
9436                                 rng.setStartBefore(caretNode);\r
9437                                 rng.setEndBefore(caretNode);\r
9438                                 self.setRng(rng);\r
9439 \r
9440                                 // Remove the caret position\r
9441                                 self.dom.remove('__caret');\r
9442 \r
9443                                 try {\r
9444                                         self.setRng(rng);\r
9445                                 } catch (ex) {\r
9446                                         // Might fail on Opera for some odd reason\r
9447                                 }\r
9448                         } else {\r
9449                                 if (rng.item) {\r
9450                                         // Delete content and get caret text selection\r
9451                                         doc.execCommand('Delete', false, null);\r
9452                                         rng = self.getRng();\r
9453                                 }\r
9454 \r
9455                                 // Explorer removes spaces from the beginning of pasted contents\r
9456                                 if (/^\s+/.test(content)) {\r
9457                                         rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);\r
9458                                         self.dom.remove('__mce_tmp');\r
9459                                 } else\r
9460                                         rng.pasteHTML(content);\r
9461                         }\r
9462 \r
9463                         // Dispatch set content event\r
9464                         if (!args.no_events)\r
9465                                 self.onSetContent.dispatch(self, args);\r
9466                 },\r
9467 \r
9468                 getStart : function() {\r
9469                         var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;\r
9470 \r
9471                         if (rng.duplicate || rng.item) {\r
9472                                 // Control selection, return first item\r
9473                                 if (rng.item)\r
9474                                         return rng.item(0);\r
9475 \r
9476                                 // Get start element\r
9477                                 checkRng = rng.duplicate();\r
9478                                 checkRng.collapse(1);\r
9479                                 startElement = checkRng.parentElement();\r
9480                                 if (startElement.ownerDocument !== self.dom.doc) {\r
9481                                         startElement = self.dom.getRoot();\r
9482                                 }\r
9483 \r
9484                                 // Check if range parent is inside the start element, then return the inner parent element\r
9485                                 // This will fix issues when a single element is selected, IE would otherwise return the wrong start element\r
9486                                 parentElement = node = rng.parentElement();\r
9487                                 while (node = node.parentNode) {\r
9488                                         if (node == startElement) {\r
9489                                                 startElement = parentElement;\r
9490                                                 break;\r
9491                                         }\r
9492                                 }\r
9493 \r
9494                                 return startElement;\r
9495                         } else {\r
9496                                 startElement = rng.startContainer;\r
9497 \r
9498                                 if (startElement.nodeType == 1 && startElement.hasChildNodes())\r
9499                                         startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];\r
9500 \r
9501                                 if (startElement && startElement.nodeType == 3)\r
9502                                         return startElement.parentNode;\r
9503 \r
9504                                 return startElement;\r
9505                         }\r
9506                 },\r
9507 \r
9508                 getEnd : function() {\r
9509                         var self = this, rng = self.getRng(), endElement, endOffset;\r
9510 \r
9511                         if (rng.duplicate || rng.item) {\r
9512                                 if (rng.item)\r
9513                                         return rng.item(0);\r
9514 \r
9515                                 rng = rng.duplicate();\r
9516                                 rng.collapse(0);\r
9517                                 endElement = rng.parentElement();\r
9518                                 if (endElement.ownerDocument !== self.dom.doc) {\r
9519                                         endElement = self.dom.getRoot();\r
9520                                 }\r
9521 \r
9522                                 if (endElement && endElement.nodeName == 'BODY')\r
9523                                         return endElement.lastChild || endElement;\r
9524 \r
9525                                 return endElement;\r
9526                         } else {\r
9527                                 endElement = rng.endContainer;\r
9528                                 endOffset = rng.endOffset;\r
9529 \r
9530                                 if (endElement.nodeType == 1 && endElement.hasChildNodes())\r
9531                                         endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];\r
9532 \r
9533                                 if (endElement && endElement.nodeType == 3)\r
9534                                         return endElement.parentNode;\r
9535 \r
9536                                 return endElement;\r
9537                         }\r
9538                 },\r
9539 \r
9540                 getBookmark : function(type, normalized) {\r
9541                         var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;\r
9542 \r
9543                         function findIndex(name, element) {\r
9544                                 var index = 0;\r
9545 \r
9546                                 each(dom.select(name), function(node, i) {\r
9547                                         if (node == element)\r
9548                                                 index = i;\r
9549                                 });\r
9550 \r
9551                                 return index;\r
9552                         };\r
9553 \r
9554                         function normalizeTableCellSelection(rng) {\r
9555                                 function moveEndPoint(start) {\r
9556                                         var container, offset, childNodes, prefix = start ? 'start' : 'end';\r
9557 \r
9558                                         container = rng[prefix + 'Container'];\r
9559                                         offset = rng[prefix + 'Offset'];\r
9560 \r
9561                                         if (container.nodeType == 1 && container.nodeName == "TR") {\r
9562                                                 childNodes = container.childNodes;\r
9563                                                 container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];\r
9564                                                 if (container) {\r
9565                                                         offset = start ? 0 : container.childNodes.length;\r
9566                                                         rng['set' + (start ? 'Start' : 'End')](container, offset);\r
9567                                                 }\r
9568                                         }\r
9569                                 };\r
9570 \r
9571                                 moveEndPoint(true);\r
9572                                 moveEndPoint();\r
9573 \r
9574                                 return rng;\r
9575                         };\r
9576 \r
9577                         function getLocation() {\r
9578                                 var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};\r
9579 \r
9580                                 function getPoint(rng, start) {\r
9581                                         var container = rng[start ? 'startContainer' : 'endContainer'],\r
9582                                                 offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;\r
9583 \r
9584                                         if (container.nodeType == 3) {\r
9585                                                 if (normalized) {\r
9586                                                         for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)\r
9587                                                                 offset += node.nodeValue.length;\r
9588                                                 }\r
9589 \r
9590                                                 point.push(offset);\r
9591                                         } else {\r
9592                                                 childNodes = container.childNodes;\r
9593 \r
9594                                                 if (offset >= childNodes.length && childNodes.length) {\r
9595                                                         after = 1;\r
9596                                                         offset = Math.max(0, childNodes.length - 1);\r
9597                                                 }\r
9598 \r
9599                                                 point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);\r
9600                                         }\r
9601 \r
9602                                         for (; container && container != root; container = container.parentNode)\r
9603                                                 point.push(t.dom.nodeIndex(container, normalized));\r
9604 \r
9605                                         return point;\r
9606                                 };\r
9607 \r
9608                                 bookmark.start = getPoint(rng, true);\r
9609 \r
9610                                 if (!t.isCollapsed())\r
9611                                         bookmark.end = getPoint(rng);\r
9612 \r
9613                                 return bookmark;\r
9614                         };\r
9615 \r
9616                         if (type == 2) {\r
9617                                 if (t.tridentSel)\r
9618                                         return t.tridentSel.getBookmark(type);\r
9619 \r
9620                                 return getLocation();\r
9621                         }\r
9622 \r
9623                         // Handle simple range\r
9624                         if (type)\r
9625                                 return {rng : t.getRng()};\r
9626 \r
9627                         rng = t.getRng();\r
9628                         id = dom.uniqueId();\r
9629                         collapsed = tinyMCE.activeEditor.selection.isCollapsed();\r
9630                         styles = 'overflow:hidden;line-height:0px';\r
9631 \r
9632                         // Explorer method\r
9633                         if (rng.duplicate || rng.item) {\r
9634                                 // Text selection\r
9635                                 if (!rng.item) {\r
9636                                         rng2 = rng.duplicate();\r
9637 \r
9638                                         try {\r
9639                                                 // Insert start marker\r
9640                                                 rng.collapse();\r
9641                                                 rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');\r
9642 \r
9643                                                 // Insert end marker\r
9644                                                 if (!collapsed) {\r
9645                                                         rng2.collapse(false);\r
9646 \r
9647                                                         // Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>\r
9648                                                         rng.moveToElementText(rng2.parentElement());\r
9649                                                         if (rng.compareEndPoints('StartToEnd', rng2) === 0)\r
9650                                                                 rng2.move('character', -1);\r
9651 \r
9652                                                         rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');\r
9653                                                 }\r
9654                                         } catch (ex) {\r
9655                                                 // IE might throw unspecified error so lets ignore it\r
9656                                                 return null;\r
9657                                         }\r
9658                                 } else {\r
9659                                         // Control selection\r
9660                                         element = rng.item(0);\r
9661                                         name = element.nodeName;\r
9662 \r
9663                                         return {name : name, index : findIndex(name, element)};\r
9664                                 }\r
9665                         } else {\r
9666                                 element = t.getNode();\r
9667                                 name = element.nodeName;\r
9668                                 if (name == 'IMG')\r
9669                                         return {name : name, index : findIndex(name, element)};\r
9670 \r
9671                                 // W3C method\r
9672                                 rng2 = normalizeTableCellSelection(rng.cloneRange());\r
9673 \r
9674                                 // Insert end marker\r
9675                                 if (!collapsed) {\r
9676                                         rng2.collapse(false);\r
9677                                         rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));\r
9678                                 }\r
9679 \r
9680                                 rng = normalizeTableCellSelection(rng);\r
9681                                 rng.collapse(true);\r
9682                                 rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));\r
9683                         }\r
9684 \r
9685                         t.moveToBookmark({id : id, keep : 1});\r
9686 \r
9687                         return {id : id};\r
9688                 },\r
9689 \r
9690                 moveToBookmark : function(bookmark) {\r
9691                         var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;\r
9692 \r
9693                         function setEndPoint(start) {\r
9694                                 var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;\r
9695 \r
9696                                 if (point) {\r
9697                                         offset = point[0];\r
9698 \r
9699                                         // Find container node\r
9700                                         for (node = root, i = point.length - 1; i >= 1; i--) {\r
9701                                                 children = node.childNodes;\r
9702 \r
9703                                                 if (point[i] > children.length - 1)\r
9704                                                         return;\r
9705 \r
9706                                                 node = children[point[i]];\r
9707                                         }\r
9708 \r
9709                                         // Move text offset to best suitable location\r
9710                                         if (node.nodeType === 3)\r
9711                                                 offset = Math.min(point[0], node.nodeValue.length);\r
9712 \r
9713                                         // Move element offset to best suitable location\r
9714                                         if (node.nodeType === 1)\r
9715                                                 offset = Math.min(point[0], node.childNodes.length);\r
9716 \r
9717                                         // Set offset within container node\r
9718                                         if (start)\r
9719                                                 rng.setStart(node, offset);\r
9720                                         else\r
9721                                                 rng.setEnd(node, offset);\r
9722                                 }\r
9723 \r
9724                                 return true;\r
9725                         };\r
9726 \r
9727                         function restoreEndPoint(suffix) {\r
9728                                 var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;\r
9729 \r
9730                                 if (marker) {\r
9731                                         node = marker.parentNode;\r
9732 \r
9733                                         if (suffix == 'start') {\r
9734                                                 if (!keep) {\r
9735                                                         idx = dom.nodeIndex(marker);\r
9736                                                 } else {\r
9737                                                         node = marker.firstChild;\r
9738                                                         idx = 1;\r
9739                                                 }\r
9740 \r
9741                                                 startContainer = endContainer = node;\r
9742                                                 startOffset = endOffset = idx;\r
9743                                         } else {\r
9744                                                 if (!keep) {\r
9745                                                         idx = dom.nodeIndex(marker);\r
9746                                                 } else {\r
9747                                                         node = marker.firstChild;\r
9748                                                         idx = 1;\r
9749                                                 }\r
9750 \r
9751                                                 endContainer = node;\r
9752                                                 endOffset = idx;\r
9753                                         }\r
9754 \r
9755                                         if (!keep) {\r
9756                                                 prev = marker.previousSibling;\r
9757                                                 next = marker.nextSibling;\r
9758 \r
9759                                                 // Remove all marker text nodes\r
9760                                                 each(tinymce.grep(marker.childNodes), function(node) {\r
9761                                                         if (node.nodeType == 3)\r
9762                                                                 node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');\r
9763                                                 });\r
9764 \r
9765                                                 // Remove marker but keep children if for example contents where inserted into the marker\r
9766                                                 // Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature\r
9767                                                 while (marker = dom.get(bookmark.id + '_' + suffix))\r
9768                                                         dom.remove(marker, 1);\r
9769 \r
9770                                                 // If siblings are text nodes then merge them unless it's Opera since it some how removes the node\r
9771                                                 // 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
9772                                                 if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {\r
9773                                                         idx = prev.nodeValue.length;\r
9774                                                         prev.appendData(next.nodeValue);\r
9775                                                         dom.remove(next);\r
9776 \r
9777                                                         if (suffix == 'start') {\r
9778                                                                 startContainer = endContainer = prev;\r
9779                                                                 startOffset = endOffset = idx;\r
9780                                                         } else {\r
9781                                                                 endContainer = prev;\r
9782                                                                 endOffset = idx;\r
9783                                                         }\r
9784                                                 }\r
9785                                         }\r
9786                                 }\r
9787                         };\r
9788 \r
9789                         function addBogus(node) {\r
9790                                 // Adds a bogus BR element for empty block elements\r
9791                                 if (dom.isBlock(node) && !node.innerHTML && !isIE)\r
9792                                         node.innerHTML = '<br data-mce-bogus="1" />';\r
9793 \r
9794                                 return node;\r
9795                         };\r
9796 \r
9797                         if (bookmark) {\r
9798                                 if (bookmark.start) {\r
9799                                         rng = dom.createRng();\r
9800                                         root = dom.getRoot();\r
9801 \r
9802                                         if (t.tridentSel)\r
9803                                                 return t.tridentSel.moveToBookmark(bookmark);\r
9804 \r
9805                                         if (setEndPoint(true) && setEndPoint()) {\r
9806                                                 t.setRng(rng);\r
9807                                         }\r
9808                                 } else if (bookmark.id) {\r
9809                                         // Restore start/end points\r
9810                                         restoreEndPoint('start');\r
9811                                         restoreEndPoint('end');\r
9812 \r
9813                                         if (startContainer) {\r
9814                                                 rng = dom.createRng();\r
9815                                                 rng.setStart(addBogus(startContainer), startOffset);\r
9816                                                 rng.setEnd(addBogus(endContainer), endOffset);\r
9817                                                 t.setRng(rng);\r
9818                                         }\r
9819                                 } else if (bookmark.name) {\r
9820                                         t.select(dom.select(bookmark.name)[bookmark.index]);\r
9821                                 } else if (bookmark.rng)\r
9822                                         t.setRng(bookmark.rng);\r
9823                         }\r
9824                 },\r
9825 \r
9826                 select : function(node, content) {\r
9827                         var t = this, dom = t.dom, rng = dom.createRng(), idx;\r
9828 \r
9829                         function setPoint(node, start) {\r
9830                                 var walker = new TreeWalker(node, node);\r
9831 \r
9832                                 do {\r
9833                                         // Text node\r
9834                                         if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {\r
9835                                                 if (start)\r
9836                                                         rng.setStart(node, 0);\r
9837                                                 else\r
9838                                                         rng.setEnd(node, node.nodeValue.length);\r
9839 \r
9840                                                 return;\r
9841                                         }\r
9842 \r
9843                                         // BR element\r
9844                                         if (node.nodeName == 'BR') {\r
9845                                                 if (start)\r
9846                                                         rng.setStartBefore(node);\r
9847                                                 else\r
9848                                                         rng.setEndBefore(node);\r
9849 \r
9850                                                 return;\r
9851                                         }\r
9852                                 } while (node = (start ? walker.next() : walker.prev()));\r
9853                         };\r
9854 \r
9855                         if (node) {\r
9856                                 idx = dom.nodeIndex(node);\r
9857                                 rng.setStart(node.parentNode, idx);\r
9858                                 rng.setEnd(node.parentNode, idx + 1);\r
9859 \r
9860                                 // Find first/last text node or BR element\r
9861                                 if (content) {\r
9862                                         setPoint(node, 1);\r
9863                                         setPoint(node);\r
9864                                 }\r
9865 \r
9866                                 t.setRng(rng);\r
9867                         }\r
9868 \r
9869                         return node;\r
9870                 },\r
9871 \r
9872                 isCollapsed : function() {\r
9873                         var t = this, r = t.getRng(), s = t.getSel();\r
9874 \r
9875                         if (!r || r.item)\r
9876                                 return false;\r
9877 \r
9878                         if (r.compareEndPoints)\r
9879                                 return r.compareEndPoints('StartToEnd', r) === 0;\r
9880 \r
9881                         return !s || r.collapsed;\r
9882                 },\r
9883 \r
9884                 collapse : function(to_start) {\r
9885                         var self = this, rng = self.getRng(), node;\r
9886 \r
9887                         // Control range on IE\r
9888                         if (rng.item) {\r
9889                                 node = rng.item(0);\r
9890                                 rng = self.win.document.body.createTextRange();\r
9891                                 rng.moveToElementText(node);\r
9892                         }\r
9893 \r
9894                         rng.collapse(!!to_start);\r
9895                         self.setRng(rng);\r
9896                 },\r
9897 \r
9898                 getSel : function() {\r
9899                         var t = this, w = this.win;\r
9900 \r
9901                         return w.getSelection ? w.getSelection() : w.document.selection;\r
9902                 },\r
9903 \r
9904                 getRng : function(w3c) {\r
9905                         var self = this, selection, rng, elm, doc = self.win.document;\r
9906 \r
9907                         // Found tridentSel object then we need to use that one\r
9908                         if (w3c && self.tridentSel) {\r
9909                                 return self.tridentSel.getRangeAt(0);\r
9910                         }\r
9911 \r
9912                         try {\r
9913                                 if (selection = self.getSel()) {\r
9914                                         rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());\r
9915                                 }\r
9916                         } catch (ex) {\r
9917                                 // IE throws unspecified error here if TinyMCE is placed in a frame/iframe\r
9918                         }\r
9919 \r
9920                         // We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet\r
9921                         if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {\r
9922                                 elm = doc.selection.createRange().item(0);\r
9923                                 rng = doc.createRange();\r
9924                                 rng.setStartBefore(elm);\r
9925                                 rng.setEndAfter(elm);\r
9926                         }\r
9927 \r
9928                         // No range found then create an empty one\r
9929                         // This can occur when the editor is placed in a hidden container element on Gecko\r
9930                         // Or on IE when there was an exception\r
9931                         if (!rng) {\r
9932                                 rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();\r
9933                         }\r
9934 \r
9935                         // If range is at start of document then move it to start of body\r
9936                         if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {\r
9937                                 elm = self.dom.getRoot();\r
9938                                 rng.setStart(elm, 0);\r
9939                                 rng.setEnd(elm, 0);\r
9940                         }\r
9941 \r
9942                         if (self.selectedRange && self.explicitRange) {\r
9943                                 if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {\r
9944                                         // Safari, Opera and Chrome only ever select text which causes the range to change.\r
9945                                         // This lets us use the originally set range if the selection hasn't been changed by the user.\r
9946                                         rng = self.explicitRange;\r
9947                                 } else {\r
9948                                         self.selectedRange = null;\r
9949                                         self.explicitRange = null;\r
9950                                 }\r
9951                         }\r
9952 \r
9953                         return rng;\r
9954                 },\r
9955 \r
9956                 setRng : function(r, forward) {\r
9957                         var s, t = this;\r
9958 \r
9959                         if (!t.tridentSel) {\r
9960                                 s = t.getSel();\r
9961 \r
9962                                 if (s) {\r
9963                                         t.explicitRange = r;\r
9964 \r
9965                                         try {\r
9966                                                 s.removeAllRanges();\r
9967                                         } catch (ex) {\r
9968                                                 // IE9 might throw errors here don't know why\r
9969                                         }\r
9970 \r
9971                                         s.addRange(r);\r
9972 \r
9973                                         // Forward is set to false and we have an extend function\r
9974                                         if (forward === false && s.extend) {\r
9975                                                 s.collapse(r.endContainer, r.endOffset);\r
9976                                                 s.extend(r.startContainer, r.startOffset);\r
9977                                         }\r
9978 \r
9979                                         // adding range isn't always successful so we need to check range count otherwise an exception can occur\r
9980                                         t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;\r
9981                                 }\r
9982                         } else {\r
9983                                 // Is W3C Range\r
9984                                 if (r.cloneRange) {\r
9985                                         try {\r
9986                                                 t.tridentSel.addRange(r);\r
9987                                                 return;\r
9988                                         } catch (ex) {\r
9989                                                 //IE9 throws an error here if called before selection is placed in the editor\r
9990                                         }\r
9991                                 }\r
9992 \r
9993                                 // Is IE specific range\r
9994                                 try {\r
9995                                         r.select();\r
9996                                 } catch (ex) {\r
9997                                         // Needed for some odd IE bug #1843306\r
9998                                 }\r
9999                         }\r
10000                 },\r
10001 \r
10002                 setNode : function(n) {\r
10003                         var t = this;\r
10004 \r
10005                         t.setContent(t.dom.getOuterHTML(n));\r
10006 \r
10007                         return n;\r
10008                 },\r
10009 \r
10010                 getNode : function() {\r
10011                         var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;\r
10012 \r
10013                         function skipEmptyTextNodes(n, forwards) {\r
10014                                 var orig = n;\r
10015                                 while (n && n.nodeType === 3 && n.length === 0) {\r
10016                                         n = forwards ? n.nextSibling : n.previousSibling;\r
10017                                 }\r
10018                                 return n || orig;\r
10019                         };\r
10020 \r
10021                         // Range maybe lost after the editor is made visible again\r
10022                         if (!rng)\r
10023                                 return t.dom.getRoot();\r
10024 \r
10025                         if (rng.setStart) {\r
10026                                 elm = rng.commonAncestorContainer;\r
10027 \r
10028                                 // Handle selection a image or other control like element such as anchors\r
10029                                 if (!rng.collapsed) {\r
10030                                         if (rng.startContainer == rng.endContainer) {\r
10031                                                 if (rng.endOffset - rng.startOffset < 2) {\r
10032                                                         if (rng.startContainer.hasChildNodes())\r
10033                                                                 elm = rng.startContainer.childNodes[rng.startOffset];\r
10034                                                 }\r
10035                                         }\r
10036 \r
10037                                         // If the anchor node is a element instead of a text node then return this element\r
10038                                         //if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)\r
10039                                         //      return sel.anchorNode.childNodes[sel.anchorOffset];\r
10040 \r
10041                                         // Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.\r
10042                                         // This happens when you double click an underlined word in FireFox.\r
10043                                         if (start.nodeType === 3 && end.nodeType === 3) {\r
10044                                                 if (start.length === rng.startOffset) {\r
10045                                                         start = skipEmptyTextNodes(start.nextSibling, true);\r
10046                                                 } else {\r
10047                                                         start = start.parentNode;\r
10048                                                 }\r
10049                                                 if (rng.endOffset === 0) {\r
10050                                                         end = skipEmptyTextNodes(end.previousSibling, false);\r
10051                                                 } else {\r
10052                                                         end = end.parentNode;\r
10053                                                 }\r
10054 \r
10055                                                 if (start && start === end)\r
10056                                                         return start;\r
10057                                         }\r
10058                                 }\r
10059 \r
10060                                 if (elm && elm.nodeType == 3)\r
10061                                         return elm.parentNode;\r
10062 \r
10063                                 return elm;\r
10064                         }\r
10065 \r
10066                         return rng.item ? rng.item(0) : rng.parentElement();\r
10067                 },\r
10068 \r
10069                 getSelectedBlocks : function(st, en) {\r
10070                         var t = this, dom = t.dom, sb, eb, n, bl = [];\r
10071 \r
10072                         sb = dom.getParent(st || t.getStart(), dom.isBlock);\r
10073                         eb = dom.getParent(en || t.getEnd(), dom.isBlock);\r
10074 \r
10075                         if (sb)\r
10076                                 bl.push(sb);\r
10077 \r
10078                         if (sb && eb && sb != eb) {\r
10079                                 n = sb;\r
10080 \r
10081                                 var walker = new TreeWalker(sb, dom.getRoot());\r
10082                                 while ((n = walker.next()) && n != eb) {\r
10083                                         if (dom.isBlock(n))\r
10084                                                 bl.push(n);\r
10085                                 }\r
10086                         }\r
10087 \r
10088                         if (eb && sb != eb)\r
10089                                 bl.push(eb);\r
10090 \r
10091                         return bl;\r
10092                 },\r
10093 \r
10094                 isForward: function(){\r
10095                         var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;\r
10096 \r
10097                         // No support for selection direction then always return true\r
10098                         if (!sel || sel.anchorNode == null || sel.focusNode == null) {\r
10099                                 return true;\r
10100                         }\r
10101 \r
10102                         anchorRange = dom.createRng();\r
10103                         anchorRange.setStart(sel.anchorNode, sel.anchorOffset);\r
10104                         anchorRange.collapse(true);\r
10105 \r
10106                         focusRange = dom.createRng();\r
10107                         focusRange.setStart(sel.focusNode, sel.focusOffset);\r
10108                         focusRange.collapse(true);\r
10109 \r
10110                         return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;\r
10111                 },\r
10112 \r
10113                 normalize : function() {\r
10114                         var self = this, rng, normalized, collapsed, node, sibling;\r
10115 \r
10116                         function normalizeEndPoint(start) {\r
10117                                 var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;\r
10118 \r
10119                                 function hasBrBeforeAfter(node, left) {\r
10120                                         var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);\r
10121 \r
10122                                         while (node = walker[left ? 'prev' : 'next']()) {\r
10123                                                 if (node.nodeName === "BR") {\r
10124                                                         return true;\r
10125                                                 }\r
10126                                         }\r
10127                                 };\r
10128 \r
10129                                 // Walks the dom left/right to find a suitable text node to move the endpoint into\r
10130                                 // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG\r
10131                                 function findTextNodeRelative(left, startNode) {\r
10132                                         var walker, lastInlineElement;\r
10133 \r
10134                                         startNode = startNode || container;\r
10135                                         walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);\r
10136 \r
10137                                         // Walk left until we hit a text node we can move to or a block/br/img\r
10138                                         while (node = walker[left ? 'prev' : 'next']()) {\r
10139                                                 // Found text node that has a length\r
10140                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
10141                                                         container = node;\r
10142                                                         offset = left ? node.nodeValue.length : 0;\r
10143                                                         normalized = true;\r
10144                                                         return;\r
10145                                                 }\r
10146 \r
10147                                                 // Break if we find a block or a BR/IMG/INPUT etc\r
10148                                                 if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
10149                                                         return;\r
10150                                                 }\r
10151 \r
10152                                                 lastInlineElement = node;\r
10153                                         }\r
10154 \r
10155                                         // Only fetch the last inline element when in caret mode for now\r
10156                                         if (collapsed && lastInlineElement) {\r
10157                                                 container = lastInlineElement;\r
10158                                                 normalized = true;\r
10159                                                 offset = 0;\r
10160                                         }\r
10161                                 };\r
10162 \r
10163                                 container = rng[(start ? 'start' : 'end') + 'Container'];\r
10164                                 offset = rng[(start ? 'start' : 'end') + 'Offset'];\r
10165                                 nonEmptyElementsMap = dom.schema.getNonEmptyElements();\r
10166 \r
10167                                 // If the container is a document move it to the body element\r
10168                                 if (container.nodeType === 9) {\r
10169                                         container = dom.getRoot();\r
10170                                         offset = 0;\r
10171                                 }\r
10172 \r
10173                                 // If the container is body try move it into the closest text node or position\r
10174                                 if (container === body) {\r
10175                                         // If start is before/after a image, table etc\r
10176                                         if (start) {\r
10177                                                 node = container.childNodes[offset > 0 ? offset - 1 : 0];\r
10178                                                 if (node) {\r
10179                                                         nodeName = node.nodeName.toLowerCase();\r
10180                                                         if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {\r
10181                                                                 return;\r
10182                                                         }\r
10183                                                 }\r
10184                                         }\r
10185 \r
10186                                         // Resolve the index\r
10187                                         if (container.hasChildNodes()) {\r
10188                                                 container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];\r
10189                                                 offset = 0;\r
10190 \r
10191                                                 // Don't walk into elements that doesn't have any child nodes like a IMG\r
10192                                                 if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {\r
10193                                                         // Walk the DOM to find a text node to place the caret at or a BR\r
10194                                                         node = container;\r
10195                                                         walker = new TreeWalker(container, body);\r
10196 \r
10197                                                         do {\r
10198                                                                 // Found a text node use that position\r
10199                                                                 if (node.nodeType === 3 && node.nodeValue.length > 0) {\r
10200                                                                         offset = start ? 0 : node.nodeValue.length;\r
10201                                                                         container = node;\r
10202                                                                         normalized = true;\r
10203                                                                         break;\r
10204                                                                 }\r
10205 \r
10206                                                                 // Found a BR/IMG element that we can place the caret before\r
10207                                                                 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
10208                                                                         offset = dom.nodeIndex(node);\r
10209                                                                         container = node.parentNode;\r
10210 \r
10211                                                                         // Put caret after image when moving the end point\r
10212                                                                         if (node.nodeName ==  "IMG" && !start) {\r
10213                                                                                 offset++;\r
10214                                                                         }\r
10215 \r
10216                                                                         normalized = true;\r
10217                                                                         break;\r
10218                                                                 }\r
10219                                                         } while (node = (start ? walker.next() : walker.prev()));\r
10220                                                 }\r
10221                                         }\r
10222                                 }\r
10223 \r
10224                                 // Lean the caret to the left if possible\r
10225                                 if (collapsed) {\r
10226                                         // So this: <b>x</b><i>|x</i>\r
10227                                         // Becomes: <b>x|</b><i>x</i>\r
10228                                         // Seems that only gecko has issues with this\r
10229                                         if (container.nodeType === 3 && offset === 0) {\r
10230                                                 findTextNodeRelative(true);\r
10231                                         }\r
10232 \r
10233                                         // Lean left into empty inline elements when the caret is before a BR\r
10234                                         // So this: <i><b></b><i>|<br></i>\r
10235                                         // Becomes: <i><b>|</b><i><br></i>\r
10236                                         // Seems that only gecko has issues with this\r
10237                                         if (container.nodeType === 1) {\r
10238                                                 node = container.childNodes[offset];\r
10239                                                 if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {\r
10240                                                         findTextNodeRelative(true, container.childNodes[offset]);\r
10241                                                 }\r
10242                                         }\r
10243                                 }\r
10244 \r
10245                                 // Lean the start of the selection right if possible\r
10246                                 // So this: x[<b>x]</b>\r
10247                                 // Becomes: x<b>[x]</b>\r
10248                                 if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {\r
10249                                         findTextNodeRelative(false);\r
10250                                 }\r
10251 \r
10252                                 // Set endpoint if it was normalized\r
10253                                 if (normalized)\r
10254                                         rng['set' + (start ? 'Start' : 'End')](container, offset);\r
10255                         };\r
10256 \r
10257                         // Normalize only on non IE browsers for now\r
10258                         if (tinymce.isIE)\r
10259                                 return;\r
10260                         \r
10261                         rng = self.getRng();\r
10262                         collapsed = rng.collapsed;\r
10263 \r
10264                         // Normalize the end points\r
10265                         normalizeEndPoint(true);\r
10266 \r
10267                         if (!collapsed)\r
10268                                 normalizeEndPoint();\r
10269 \r
10270                         // Set the selection if it was normalized\r
10271                         if (normalized) {\r
10272                                 // If it was collapsed then make sure it still is\r
10273                                 if (collapsed) {\r
10274                                         rng.collapse(true);\r
10275                                 }\r
10276 \r
10277                                 //console.log(self.dom.dumpRng(rng));\r
10278                                 self.setRng(rng, self.isForward());\r
10279                         }\r
10280                 },\r
10281 \r
10282                 selectorChanged: function(selector, callback) {\r
10283                         var self = this, currentSelectors;\r
10284 \r
10285                         if (!self.selectorChangedData) {\r
10286                                 self.selectorChangedData = {};\r
10287                                 currentSelectors = {};\r
10288 \r
10289                                 self.editor.onNodeChange.addToTop(function(ed, cm, node) {\r
10290                                         var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};\r
10291 \r
10292                                         // Check for new matching selectors\r
10293                                         each(self.selectorChangedData, function(callbacks, selector) {\r
10294                                                 each(parents, function(node) {\r
10295                                                         if (dom.is(node, selector)) {\r
10296                                                                 if (!currentSelectors[selector]) {\r
10297                                                                         // Execute callbacks\r
10298                                                                         each(callbacks, function(callback) {\r
10299                                                                                 callback(true, {node: node, selector: selector, parents: parents});\r
10300                                                                         });\r
10301 \r
10302                                                                         currentSelectors[selector] = callbacks;\r
10303                                                                 }\r
10304 \r
10305                                                                 matchedSelectors[selector] = callbacks;\r
10306                                                                 return false;\r
10307                                                         }\r
10308                                                 });\r
10309                                         });\r
10310 \r
10311                                         // Check if current selectors still match\r
10312                                         each(currentSelectors, function(callbacks, selector) {\r
10313                                                 if (!matchedSelectors[selector]) {\r
10314                                                         delete currentSelectors[selector];\r
10315 \r
10316                                                         each(callbacks, function(callback) {\r
10317                                                                 callback(false, {node: node, selector: selector, parents: parents});\r
10318                                                         });\r
10319                                                 }\r
10320                                         });\r
10321                                 });\r
10322                         }\r
10323 \r
10324                         // Add selector listeners\r
10325                         if (!self.selectorChangedData[selector]) {\r
10326                                 self.selectorChangedData[selector] = [];\r
10327                         }\r
10328 \r
10329                         self.selectorChangedData[selector].push(callback);\r
10330 \r
10331                         return self;\r
10332                 },\r
10333 \r
10334                 scrollIntoView: function(elm) {\r
10335                         var y, viewPort, self = this, dom = self.dom;\r
10336 \r
10337                         viewPort = dom.getViewPort(self.editor.getWin());\r
10338                         y = dom.getPos(elm).y;\r
10339                         if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {\r
10340                                 self.editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25);\r
10341                         }\r
10342                 },\r
10343 \r
10344                 destroy : function(manual) {\r
10345                         var self = this;\r
10346 \r
10347                         self.win = null;\r
10348 \r
10349                         // Manual destroy then remove unload handler\r
10350                         if (!manual)\r
10351                                 tinymce.removeUnload(self.destroy);\r
10352                 },\r
10353 \r
10354                 // 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
10355                 _fixIESelection : function() {\r
10356                         var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;\r
10357 \r
10358                         // Return range from point or null if it failed\r
10359                         function rngFromPoint(x, y) {\r
10360                                 var rng = body.createTextRange();\r
10361 \r
10362                                 try {\r
10363                                         rng.moveToPoint(x, y);\r
10364                                 } catch (ex) {\r
10365                                         // IE sometimes throws and exception, so lets just ignore it\r
10366                                         rng = null;\r
10367                                 }\r
10368 \r
10369                                 return rng;\r
10370                         };\r
10371 \r
10372                         // Fires while the selection is changing\r
10373                         function selectionChange(e) {\r
10374                                 var pointRng;\r
10375 \r
10376                                 // Check if the button is down or not\r
10377                                 if (e.button) {\r
10378                                         // Create range from mouse position\r
10379                                         pointRng = rngFromPoint(e.x, e.y);\r
10380 \r
10381                                         if (pointRng) {\r
10382                                                 // Check if pointRange is before/after selection then change the endPoint\r
10383                                                 if (pointRng.compareEndPoints('StartToStart', startRng) > 0)\r
10384                                                         pointRng.setEndPoint('StartToStart', startRng);\r
10385                                                 else\r
10386                                                         pointRng.setEndPoint('EndToEnd', startRng);\r
10387 \r
10388                                                 pointRng.select();\r
10389                                         }\r
10390                                 } else\r
10391                                         endSelection();\r
10392                         }\r
10393 \r
10394                         // Removes listeners\r
10395                         function endSelection() {\r
10396                                 var rng = doc.selection.createRange();\r
10397 \r
10398                                 // If the range is collapsed then use the last start range\r
10399                                 if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)\r
10400                                         startRng.select();\r
10401 \r
10402                                 dom.unbind(doc, 'mouseup', endSelection);\r
10403                                 dom.unbind(doc, 'mousemove', selectionChange);\r
10404                                 startRng = started = 0;\r
10405                         };\r
10406 \r
10407                         // Make HTML element unselectable since we are going to handle selection by hand\r
10408                         doc.documentElement.unselectable = true;\r
10409                         \r
10410                         // Detect when user selects outside BODY\r
10411                         dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {\r
10412                                 if (e.target.nodeName === 'HTML') {\r
10413                                         if (started)\r
10414                                                 endSelection();\r
10415 \r
10416                                         // Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML\r
10417                                         htmlElm = doc.documentElement;\r
10418                                         if (htmlElm.scrollHeight > htmlElm.clientHeight)\r
10419                                                 return;\r
10420 \r
10421                                         started = 1;\r
10422                                         // Setup start position\r
10423                                         startRng = rngFromPoint(e.x, e.y);\r
10424                                         if (startRng) {\r
10425                                                 // Listen for selection change events\r
10426                                                 dom.bind(doc, 'mouseup', endSelection);\r
10427                                                 dom.bind(doc, 'mousemove', selectionChange);\r
10428 \r
10429                                                 dom.win.focus();\r
10430                                                 startRng.select();\r
10431                                         }\r
10432                                 }\r
10433                         });\r
10434                 }\r
10435         });\r
10436 })(tinymce);\r
10437 \r
10438 (function(tinymce) {\r
10439         tinymce.dom.Serializer = function(settings, dom, schema) {\r
10440                 var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;\r
10441 \r
10442                 // Support the old apply_source_formatting option\r
10443                 if (!settings.apply_source_formatting)\r
10444                         settings.indent = false;\r
10445 \r
10446                 // Default DOM and Schema if they are undefined\r
10447                 dom = dom || tinymce.DOM;\r
10448                 schema = schema || new tinymce.html.Schema(settings);\r
10449                 settings.entity_encoding = settings.entity_encoding || 'named';\r
10450                 settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;\r
10451 \r
10452                 onPreProcess = new tinymce.util.Dispatcher(self);\r
10453 \r
10454                 onPostProcess = new tinymce.util.Dispatcher(self);\r
10455 \r
10456                 htmlParser = new tinymce.html.DomParser(settings, schema);\r
10457 \r
10458                 // Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed\r
10459                 htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {\r
10460                         var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;\r
10461 \r
10462                         while (i--) {\r
10463                                 node = nodes[i];\r
10464 \r
10465                                 value = node.attributes.map[internalName];\r
10466                                 if (value !== undef) {\r
10467                                         // Set external name to internal value and remove internal\r
10468                                         node.attr(name, value.length > 0 ? value : null);\r
10469                                         node.attr(internalName, null);\r
10470                                 } else {\r
10471                                         // No internal attribute found then convert the value we have in the DOM\r
10472                                         value = node.attributes.map[name];\r
10473 \r
10474                                         if (name === "style")\r
10475                                                 value = dom.serializeStyle(dom.parseStyle(value), node.name);\r
10476                                         else if (urlConverter)\r
10477                                                 value = urlConverter.call(urlConverterScope, value, name, node.name);\r
10478 \r
10479                                         node.attr(name, value.length > 0 ? value : null);\r
10480                                 }\r
10481                         }\r
10482                 });\r
10483 \r
10484                 // Remove internal classes mceItem<..> or mceSelected\r
10485                 htmlParser.addAttributeFilter('class', function(nodes, name) {\r
10486                         var i = nodes.length, node, value;\r
10487 \r
10488                         while (i--) {\r
10489                                 node = nodes[i];\r
10490                                 value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');\r
10491                                 node.attr('class', value.length > 0 ? value : null);\r
10492                         }\r
10493                 });\r
10494 \r
10495                 // Remove bookmark elements\r
10496                 htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {\r
10497                         var i = nodes.length, node;\r
10498 \r
10499                         while (i--) {\r
10500                                 node = nodes[i];\r
10501 \r
10502                                 if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)\r
10503                                         node.remove();\r
10504                         }\r
10505                 });\r
10506 \r
10507                 // Remove expando attributes\r
10508                 htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {\r
10509                         var i = nodes.length;\r
10510 \r
10511                         while (i--) {\r
10512                                 nodes[i].attr(name, null);\r
10513                         }\r
10514                 });\r
10515 \r
10516                 htmlParser.addNodeFilter('noscript', function(nodes) {\r
10517                         var i = nodes.length, node;\r
10518 \r
10519                         while (i--) {\r
10520                                 node = nodes[i].firstChild;\r
10521 \r
10522                                 if (node) {\r
10523                                         node.value = tinymce.html.Entities.decode(node.value);\r
10524                                 }\r
10525                         }\r
10526                 });\r
10527 \r
10528                 // Force script into CDATA sections and remove the mce- prefix also add comments around styles\r
10529                 htmlParser.addNodeFilter('script,style', function(nodes, name) {\r
10530                         var i = nodes.length, node, value;\r
10531 \r
10532                         function trim(value) {\r
10533                                 return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')\r
10534                                                 .replace(/^[\r\n]*|[\r\n]*$/g, '')\r
10535                                                 .replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')\r
10536                                                 .replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');\r
10537                         };\r
10538 \r
10539                         while (i--) {\r
10540                                 node = nodes[i];\r
10541                                 value = node.firstChild ? node.firstChild.value : '';\r
10542 \r
10543                                 if (name === "script") {\r
10544                                         // Remove mce- prefix from script elements\r
10545                                         node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));\r
10546 \r
10547                                         if (value.length > 0)\r
10548                                                 node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';\r
10549                                 } else {\r
10550                                         if (value.length > 0)\r
10551                                                 node.firstChild.value = '<!--\n' + trim(value) + '\n-->';\r
10552                                 }\r
10553                         }\r
10554                 });\r
10555 \r
10556                 // Convert comments to cdata and handle protected comments\r
10557                 htmlParser.addNodeFilter('#comment', function(nodes, name) {\r
10558                         var i = nodes.length, node;\r
10559 \r
10560                         while (i--) {\r
10561                                 node = nodes[i];\r
10562 \r
10563                                 if (node.value.indexOf('[CDATA[') === 0) {\r
10564                                         node.name = '#cdata';\r
10565                                         node.type = 4;\r
10566                                         node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');\r
10567                                 } else if (node.value.indexOf('mce:protected ') === 0) {\r
10568                                         node.name = "#text";\r
10569                                         node.type = 3;\r
10570                                         node.raw = true;\r
10571                                         node.value = unescape(node.value).substr(14);\r
10572                                 }\r
10573                         }\r
10574                 });\r
10575 \r
10576                 htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {\r
10577                         var i = nodes.length, node;\r
10578 \r
10579                         while (i--) {\r
10580                                 node = nodes[i];\r
10581                                 if (node.type === 7)\r
10582                                         node.remove();\r
10583                                 else if (node.type === 1) {\r
10584                                         if (name === "input" && !("type" in node.attributes.map))\r
10585                                                 node.attr('type', 'text');\r
10586                                 }\r
10587                         }\r
10588                 });\r
10589 \r
10590                 // Fix list elements, TODO: Replace this later\r
10591                 if (settings.fix_list_elements) {\r
10592                         htmlParser.addNodeFilter('ul,ol', function(nodes, name) {\r
10593                                 var i = nodes.length, node, parentNode;\r
10594 \r
10595                                 while (i--) {\r
10596                                         node = nodes[i];\r
10597                                         parentNode = node.parent;\r
10598 \r
10599                                         if (parentNode.name === 'ul' || parentNode.name === 'ol') {\r
10600                                                 if (node.prev && node.prev.name === 'li') {\r
10601                                                         node.prev.append(node);\r
10602                                                 }\r
10603                                         }\r
10604                                 }\r
10605                         });\r
10606                 }\r
10607 \r
10608                 // Remove internal data attributes\r
10609                 htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {\r
10610                         var i = nodes.length;\r
10611 \r
10612                         while (i--) {\r
10613                                 nodes[i].attr(name, null);\r
10614                         }\r
10615                 });\r
10616 \r
10617                 // Return public methods\r
10618                 return {\r
10619                         schema : schema,\r
10620 \r
10621                         addNodeFilter : htmlParser.addNodeFilter,\r
10622 \r
10623                         addAttributeFilter : htmlParser.addAttributeFilter,\r
10624 \r
10625                         onPreProcess : onPreProcess,\r
10626 \r
10627                         onPostProcess : onPostProcess,\r
10628 \r
10629                         serialize : function(node, args) {\r
10630                                 var impl, doc, oldDoc, htmlSerializer, content;\r
10631 \r
10632                                 // Explorer won't clone contents of script and style and the\r
10633                                 // selected index of select elements are cleared on a clone operation.\r
10634                                 if (isIE && dom.select('script,style,select,map').length > 0) {\r
10635                                         content = node.innerHTML;\r
10636                                         node = node.cloneNode(false);\r
10637                                         dom.setHTML(node, content);\r
10638                                 } else\r
10639                                         node = node.cloneNode(true);\r
10640 \r
10641                                 // Nodes needs to be attached to something in WebKit/Opera\r
10642                                 // Older builds of Opera crashes if you attach the node to an document created dynamically\r
10643                                 // and since we can't feature detect a crash we need to sniff the acutal build number\r
10644                                 // This fix will make DOM ranges and make Sizzle happy!\r
10645                                 impl = node.ownerDocument.implementation;\r
10646                                 if (impl.createHTMLDocument) {\r
10647                                         // Create an empty HTML document\r
10648                                         doc = impl.createHTMLDocument("");\r
10649 \r
10650                                         // Add the element or it's children if it's a body element to the new document\r
10651                                         each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {\r
10652                                                 doc.body.appendChild(doc.importNode(node, true));\r
10653                                         });\r
10654 \r
10655                                         // Grab first child or body element for serialization\r
10656                                         if (node.nodeName != 'BODY')\r
10657                                                 node = doc.body.firstChild;\r
10658                                         else\r
10659                                                 node = doc.body;\r
10660 \r
10661                                         // set the new document in DOMUtils so createElement etc works\r
10662                                         oldDoc = dom.doc;\r
10663                                         dom.doc = doc;\r
10664                                 }\r
10665 \r
10666                                 args = args || {};\r
10667                                 args.format = args.format || 'html';\r
10668 \r
10669                                 // Pre process\r
10670                                 if (!args.no_events) {\r
10671                                         args.node = node;\r
10672                                         onPreProcess.dispatch(self, args);\r
10673                                 }\r
10674 \r
10675                                 // Setup serializer\r
10676                                 htmlSerializer = new tinymce.html.Serializer(settings, schema);\r
10677 \r
10678                                 // Parse and serialize HTML\r
10679                                 args.content = htmlSerializer.serialize(\r
10680                                         htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)\r
10681                                 );\r
10682 \r
10683                                 // Replace all BOM characters for now until we can find a better solution\r
10684                                 if (!args.cleanup)\r
10685                                         args.content = args.content.replace(/\uFEFF/g, '');\r
10686 \r
10687                                 // Post process\r
10688                                 if (!args.no_events)\r
10689                                         onPostProcess.dispatch(self, args);\r
10690 \r
10691                                 // Restore the old document if it was changed\r
10692                                 if (oldDoc)\r
10693                                         dom.doc = oldDoc;\r
10694 \r
10695                                 args.node = null;\r
10696 \r
10697                                 return args.content;\r
10698                         },\r
10699 \r
10700                         addRules : function(rules) {\r
10701                                 schema.addValidElements(rules);\r
10702                         },\r
10703 \r
10704                         setRules : function(rules) {\r
10705                                 schema.setValidElements(rules);\r
10706                         }\r
10707                 };\r
10708         };\r
10709 })(tinymce);\r
10710 (function(tinymce) {\r
10711         tinymce.dom.ScriptLoader = function(settings) {\r
10712                 var QUEUED = 0,\r
10713                         LOADING = 1,\r
10714                         LOADED = 2,\r
10715                         states = {},\r
10716                         queue = [],\r
10717                         scriptLoadedCallbacks = {},\r
10718                         queueLoadedCallbacks = [],\r
10719                         loading = 0,\r
10720                         undef;\r
10721 \r
10722                 function loadScript(url, callback) {\r
10723                         var t = this, dom = tinymce.DOM, elm, uri, loc, id;\r
10724 \r
10725                         // Execute callback when script is loaded\r
10726                         function done() {\r
10727                                 dom.remove(id);\r
10728 \r
10729                                 if (elm)\r
10730                                         elm.onreadystatechange = elm.onload = elm = null;\r
10731 \r
10732                                 callback();\r
10733                         };\r
10734                         \r
10735                         function error() {\r
10736                                 // Report the error so it's easier for people to spot loading errors\r
10737                                 if (typeof(console) !== "undefined" && console.log)\r
10738                                         console.log("Failed to load: " + url);\r
10739 \r
10740                                 // We can't mark it as done if there is a load error since\r
10741                                 // A) We don't want to produce 404 errors on the server and\r
10742                                 // B) the onerror event won't fire on all browsers.\r
10743                                 // done();\r
10744                         };\r
10745 \r
10746                         id = dom.uniqueId();\r
10747 \r
10748                         if (tinymce.isIE6) {\r
10749                                 uri = new tinymce.util.URI(url);\r
10750                                 loc = location;\r
10751 \r
10752                                 // If script is from same domain and we\r
10753                                 // use IE 6 then use XHR since it's more reliable\r
10754                                 if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {\r
10755                                         tinymce.util.XHR.send({\r
10756                                                 url : tinymce._addVer(uri.getURI()),\r
10757                                                 success : function(content) {\r
10758                                                         // Create new temp script element\r
10759                                                         var script = dom.create('script', {\r
10760                                                                 type : 'text/javascript'\r
10761                                                         });\r
10762 \r
10763                                                         // Evaluate script in global scope\r
10764                                                         script.text = content;\r
10765                                                         document.getElementsByTagName('head')[0].appendChild(script);\r
10766                                                         dom.remove(script);\r
10767 \r
10768                                                         done();\r
10769                                                 },\r
10770                                                 \r
10771                                                 error : error\r
10772                                         });\r
10773 \r
10774                                         return;\r
10775                                 }\r
10776                         }\r
10777 \r
10778                         // Create new script element\r
10779                         elm = document.createElement('script');\r
10780                         elm.id = id;\r
10781                         elm.type = 'text/javascript';\r
10782                         elm.src = tinymce._addVer(url);\r
10783 \r
10784                         // Add onload listener for non IE browsers since IE9\r
10785                         // fires onload event before the script is parsed and executed\r
10786                         if (!tinymce.isIE)\r
10787                                 elm.onload = done;\r
10788 \r
10789                         // Add onerror event will get fired on some browsers but not all of them\r
10790                         elm.onerror = error;\r
10791 \r
10792                         // Opera 9.60 doesn't seem to fire the onreadystate event at correctly\r
10793                         if (!tinymce.isOpera) {\r
10794                                 elm.onreadystatechange = function() {\r
10795                                         var state = elm.readyState;\r
10796 \r
10797                                         // Loaded state is passed on IE 6 however there\r
10798                                         // are known issues with this method but we can't use\r
10799                                         // XHR in a cross domain loading\r
10800                                         if (state == 'complete' || state == 'loaded')\r
10801                                                 done();\r
10802                                 };\r
10803                         }\r
10804 \r
10805                         // Most browsers support this feature so we report errors\r
10806                         // for those at least to help users track their missing plugins etc\r
10807                         // todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option\r
10808                         /*elm.onerror = function() {\r
10809                                 alert('Failed to load: ' + url);\r
10810                         };*/\r
10811 \r
10812                         // Add script to document\r
10813                         (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);\r
10814                 };\r
10815 \r
10816                 this.isDone = function(url) {\r
10817                         return states[url] == LOADED;\r
10818                 };\r
10819 \r
10820                 this.markDone = function(url) {\r
10821                         states[url] = LOADED;\r
10822                 };\r
10823 \r
10824                 this.add = this.load = function(url, callback, scope) {\r
10825                         var item, state = states[url];\r
10826 \r
10827                         // Add url to load queue\r
10828                         if (state == undef) {\r
10829                                 queue.push(url);\r
10830                                 states[url] = QUEUED;\r
10831                         }\r
10832 \r
10833                         if (callback) {\r
10834                                 // Store away callback for later execution\r
10835                                 if (!scriptLoadedCallbacks[url])\r
10836                                         scriptLoadedCallbacks[url] = [];\r
10837 \r
10838                                 scriptLoadedCallbacks[url].push({\r
10839                                         func : callback,\r
10840                                         scope : scope || this\r
10841                                 });\r
10842                         }\r
10843                 };\r
10844 \r
10845                 this.loadQueue = function(callback, scope) {\r
10846                         this.loadScripts(queue, callback, scope);\r
10847                 };\r
10848 \r
10849                 this.loadScripts = function(scripts, callback, scope) {\r
10850                         var loadScripts;\r
10851 \r
10852                         function execScriptLoadedCallbacks(url) {\r
10853                                 // Execute URL callback functions\r
10854                                 tinymce.each(scriptLoadedCallbacks[url], function(callback) {\r
10855                                         callback.func.call(callback.scope);\r
10856                                 });\r
10857 \r
10858                                 scriptLoadedCallbacks[url] = undef;\r
10859                         };\r
10860 \r
10861                         queueLoadedCallbacks.push({\r
10862                                 func : callback,\r
10863                                 scope : scope || this\r
10864                         });\r
10865 \r
10866                         loadScripts = function() {\r
10867                                 var loadingScripts = tinymce.grep(scripts);\r
10868 \r
10869                                 // Current scripts has been handled\r
10870                                 scripts.length = 0;\r
10871 \r
10872                                 // Load scripts that needs to be loaded\r
10873                                 tinymce.each(loadingScripts, function(url) {\r
10874                                         // Script is already loaded then execute script callbacks directly\r
10875                                         if (states[url] == LOADED) {\r
10876                                                 execScriptLoadedCallbacks(url);\r
10877                                                 return;\r
10878                                         }\r
10879 \r
10880                                         // Is script not loading then start loading it\r
10881                                         if (states[url] != LOADING) {\r
10882                                                 states[url] = LOADING;\r
10883                                                 loading++;\r
10884 \r
10885                                                 loadScript(url, function() {\r
10886                                                         states[url] = LOADED;\r
10887                                                         loading--;\r
10888 \r
10889                                                         execScriptLoadedCallbacks(url);\r
10890 \r
10891                                                         // Load more scripts if they where added by the recently loaded script\r
10892                                                         loadScripts();\r
10893                                                 });\r
10894                                         }\r
10895                                 });\r
10896 \r
10897                                 // No scripts are currently loading then execute all pending queue loaded callbacks\r
10898                                 if (!loading) {\r
10899                                         tinymce.each(queueLoadedCallbacks, function(callback) {\r
10900                                                 callback.func.call(callback.scope);\r
10901                                         });\r
10902 \r
10903                                         queueLoadedCallbacks.length = 0;\r
10904                                 }\r
10905                         };\r
10906 \r
10907                         loadScripts();\r
10908                 };\r
10909         };\r
10910 \r
10911         // Global script loader\r
10912         tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();\r
10913 })(tinymce);\r
10914 \r
10915 (function(tinymce) {\r
10916         tinymce.dom.RangeUtils = function(dom) {\r
10917                 var INVISIBLE_CHAR = '\uFEFF';\r
10918 \r
10919                 this.walk = function(rng, callback) {\r
10920                         var startContainer = rng.startContainer,\r
10921                                 startOffset = rng.startOffset,\r
10922                                 endContainer = rng.endContainer,\r
10923                                 endOffset = rng.endOffset,\r
10924                                 ancestor, startPoint,\r
10925                                 endPoint, node, parent, siblings, nodes;\r
10926 \r
10927                         // Handle table cell selection the table plugin enables\r
10928                         // you to fake select table cells and perform formatting actions on them\r
10929                         nodes = dom.select('td.mceSelected,th.mceSelected');\r
10930                         if (nodes.length > 0) {\r
10931                                 tinymce.each(nodes, function(node) {\r
10932                                         callback([node]);\r
10933                                 });\r
10934 \r
10935                                 return;\r
10936                         }\r
10937 \r
10938                         function exclude(nodes) {\r
10939                                 var node;\r
10940 \r
10941                                 // First node is excluded\r
10942                                 node = nodes[0];\r
10943                                 if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {\r
10944                                         nodes.splice(0, 1);\r
10945                                 }\r
10946 \r
10947                                 // Last node is excluded\r
10948                                 node = nodes[nodes.length - 1];\r
10949                                 if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {\r
10950                                         nodes.splice(nodes.length - 1, 1);\r
10951                                 }\r
10952 \r
10953                                 return nodes;\r
10954                         };\r
10955 \r
10956                         function collectSiblings(node, name, end_node) {\r
10957                                 var siblings = [];\r
10958 \r
10959                                 for (; node && node != end_node; node = node[name])\r
10960                                         siblings.push(node);\r
10961 \r
10962                                 return siblings;\r
10963                         };\r
10964 \r
10965                         function findEndPoint(node, root) {\r
10966                                 do {\r
10967                                         if (node.parentNode == root)\r
10968                                                 return node;\r
10969 \r
10970                                         node = node.parentNode;\r
10971                                 } while(node);\r
10972                         };\r
10973 \r
10974                         function walkBoundary(start_node, end_node, next) {\r
10975                                 var siblingName = next ? 'nextSibling' : 'previousSibling';\r
10976 \r
10977                                 for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {\r
10978                                         parent = node.parentNode;\r
10979                                         siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);\r
10980 \r
10981                                         if (siblings.length) {\r
10982                                                 if (!next)\r
10983                                                         siblings.reverse();\r
10984 \r
10985                                                 callback(exclude(siblings));\r
10986                                         }\r
10987                                 }\r
10988                         };\r
10989 \r
10990                         // If index based start position then resolve it\r
10991                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes())\r
10992                                 startContainer = startContainer.childNodes[startOffset];\r
10993 \r
10994                         // If index based end position then resolve it\r
10995                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes())\r
10996                                 endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];\r
10997 \r
10998                         // Same container\r
10999                         if (startContainer == endContainer)\r
11000                                 return callback(exclude([startContainer]));\r
11001 \r
11002                         // Find common ancestor and end points\r
11003                         ancestor = dom.findCommonAncestor(startContainer, endContainer);\r
11004                                 \r
11005                         // Process left side\r
11006                         for (node = startContainer; node; node = node.parentNode) {\r
11007                                 if (node === endContainer)\r
11008                                         return walkBoundary(startContainer, ancestor, true);\r
11009 \r
11010                                 if (node === ancestor)\r
11011                                         break;\r
11012                         }\r
11013 \r
11014                         // Process right side\r
11015                         for (node = endContainer; node; node = node.parentNode) {\r
11016                                 if (node === startContainer)\r
11017                                         return walkBoundary(endContainer, ancestor);\r
11018 \r
11019                                 if (node === ancestor)\r
11020                                         break;\r
11021                         }\r
11022 \r
11023                         // Find start/end point\r
11024                         startPoint = findEndPoint(startContainer, ancestor) || startContainer;\r
11025                         endPoint = findEndPoint(endContainer, ancestor) || endContainer;\r
11026 \r
11027                         // Walk left leaf\r
11028                         walkBoundary(startContainer, startPoint, true);\r
11029 \r
11030                         // Walk the middle from start to end point\r
11031                         siblings = collectSiblings(\r
11032                                 startPoint == startContainer ? startPoint : startPoint.nextSibling,\r
11033                                 'nextSibling',\r
11034                                 endPoint == endContainer ? endPoint.nextSibling : endPoint\r
11035                         );\r
11036 \r
11037                         if (siblings.length)\r
11038                                 callback(exclude(siblings));\r
11039 \r
11040                         // Walk right leaf\r
11041                         walkBoundary(endContainer, endPoint);\r
11042                 };\r
11043 \r
11044                 this.split = function(rng) {\r
11045                         var startContainer = rng.startContainer,\r
11046                                 startOffset = rng.startOffset,\r
11047                                 endContainer = rng.endContainer,\r
11048                                 endOffset = rng.endOffset;\r
11049 \r
11050                         function splitText(node, offset) {\r
11051                                 return node.splitText(offset);\r
11052                         };\r
11053 \r
11054                         // Handle single text node\r
11055                         if (startContainer == endContainer && startContainer.nodeType == 3) {\r
11056                                 if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
11057                                         endContainer = splitText(startContainer, startOffset);\r
11058                                         startContainer = endContainer.previousSibling;\r
11059 \r
11060                                         if (endOffset > startOffset) {\r
11061                                                 endOffset = endOffset - startOffset;\r
11062                                                 startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;\r
11063                                                 endOffset = endContainer.nodeValue.length;\r
11064                                                 startOffset = 0;\r
11065                                         } else {\r
11066                                                 endOffset = 0;\r
11067                                         }\r
11068                                 }\r
11069                         } else {\r
11070                                 // Split startContainer text node if needed\r
11071                                 if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {\r
11072                                         startContainer = splitText(startContainer, startOffset);\r
11073                                         startOffset = 0;\r
11074                                 }\r
11075 \r
11076                                 // Split endContainer text node if needed\r
11077                                 if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {\r
11078                                         endContainer = splitText(endContainer, endOffset).previousSibling;\r
11079                                         endOffset = endContainer.nodeValue.length;\r
11080                                 }\r
11081                         }\r
11082 \r
11083                         return {\r
11084                                 startContainer : startContainer,\r
11085                                 startOffset : startOffset,\r
11086                                 endContainer : endContainer,\r
11087                                 endOffset : endOffset\r
11088                         };\r
11089                 };\r
11090 \r
11091         };\r
11092 \r
11093         tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {\r
11094                 if (rng1 && rng2) {\r
11095                         // Compare native IE ranges\r
11096                         if (rng1.item || rng1.duplicate) {\r
11097                                 // Both are control ranges and the selected element matches\r
11098                                 if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))\r
11099                                         return true;\r
11100 \r
11101                                 // Both are text ranges and the range matches\r
11102                                 if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))\r
11103                                         return true;\r
11104                         } else {\r
11105                                 // Compare w3c ranges\r
11106                                 return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;\r
11107                         }\r
11108                 }\r
11109 \r
11110                 return false;\r
11111         };\r
11112 })(tinymce);\r
11113 \r
11114 (function(tinymce) {\r
11115         var Event = tinymce.dom.Event, each = tinymce.each;\r
11116 \r
11117         tinymce.create('tinymce.ui.KeyboardNavigation', {\r
11118                 KeyboardNavigation: function(settings, dom) {\r
11119                         var t = this, root = settings.root, items = settings.items,\r
11120                                         enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,\r
11121                                         excludeFromTabOrder = settings.excludeFromTabOrder,\r
11122                                         itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;\r
11123 \r
11124                         dom = dom || tinymce.DOM;\r
11125 \r
11126                         itemFocussed = function(evt) {\r
11127                                 focussedId = evt.target.id;\r
11128                         };\r
11129                         \r
11130                         itemBlurred = function(evt) {\r
11131                                 dom.setAttrib(evt.target.id, 'tabindex', '-1');\r
11132                         };\r
11133                         \r
11134                         rootFocussed = function(evt) {\r
11135                                 var item = dom.get(focussedId);\r
11136                                 dom.setAttrib(item, 'tabindex', '0');\r
11137                                 item.focus();\r
11138                         };\r
11139                         \r
11140                         t.focus = function() {\r
11141                                 dom.get(focussedId).focus();\r
11142                         };\r
11143 \r
11144                         t.destroy = function() {\r
11145                                 each(items, function(item) {\r
11146                                         var elm = dom.get(item.id);\r
11147 \r
11148                                         dom.unbind(elm, 'focus', itemFocussed);\r
11149                                         dom.unbind(elm, 'blur', itemBlurred);\r
11150                                 });\r
11151 \r
11152                                 var rootElm = dom.get(root);\r
11153                                 dom.unbind(rootElm, 'focus', rootFocussed);\r
11154                                 dom.unbind(rootElm, 'keydown', rootKeydown);\r
11155 \r
11156                                 items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;\r
11157                                 t.destroy = function() {};\r
11158                         };\r
11159                         \r
11160                         t.moveFocus = function(dir, evt) {\r
11161                                 var idx = -1, controls = t.controls, newFocus;\r
11162 \r
11163                                 if (!focussedId)\r
11164                                         return;\r
11165 \r
11166                                 each(items, function(item, index) {\r
11167                                         if (item.id === focussedId) {\r
11168                                                 idx = index;\r
11169                                                 return false;\r
11170                                         }\r
11171                                 });\r
11172 \r
11173                                 idx += dir;\r
11174                                 if (idx < 0) {\r
11175                                         idx = items.length - 1;\r
11176                                 } else if (idx >= items.length) {\r
11177                                         idx = 0;\r
11178                                 }\r
11179                                 \r
11180                                 newFocus = items[idx];\r
11181                                 dom.setAttrib(focussedId, 'tabindex', '-1');\r
11182                                 dom.setAttrib(newFocus.id, 'tabindex', '0');\r
11183                                 dom.get(newFocus.id).focus();\r
11184 \r
11185                                 if (settings.actOnFocus) {\r
11186                                         settings.onAction(newFocus.id);\r
11187                                 }\r
11188 \r
11189                                 if (evt)\r
11190                                         Event.cancel(evt);\r
11191                         };\r
11192                         \r
11193                         rootKeydown = function(evt) {\r
11194                                 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
11195                                 \r
11196                                 switch (evt.keyCode) {\r
11197                                         case DOM_VK_LEFT:\r
11198                                                 if (enableLeftRight) t.moveFocus(-1);\r
11199                                                 break;\r
11200         \r
11201                                         case DOM_VK_RIGHT:\r
11202                                                 if (enableLeftRight) t.moveFocus(1);\r
11203                                                 break;\r
11204         \r
11205                                         case DOM_VK_UP:\r
11206                                                 if (enableUpDown) t.moveFocus(-1);\r
11207                                                 break;\r
11208 \r
11209                                         case DOM_VK_DOWN:\r
11210                                                 if (enableUpDown) t.moveFocus(1);\r
11211                                                 break;\r
11212 \r
11213                                         case DOM_VK_ESCAPE:\r
11214                                                 if (settings.onCancel) {\r
11215                                                         settings.onCancel();\r
11216                                                         Event.cancel(evt);\r
11217                                                 }\r
11218                                                 break;\r
11219 \r
11220                                         case DOM_VK_ENTER:\r
11221                                         case DOM_VK_RETURN:\r
11222                                         case DOM_VK_SPACE:\r
11223                                                 if (settings.onAction) {\r
11224                                                         settings.onAction(focussedId);\r
11225                                                         Event.cancel(evt);\r
11226                                                 }\r
11227                                                 break;\r
11228                                 }\r
11229                         };\r
11230 \r
11231                         // Set up state and listeners for each item.\r
11232                         each(items, function(item, idx) {\r
11233                                 var tabindex, elm;\r
11234 \r
11235                                 if (!item.id) {\r
11236                                         item.id = dom.uniqueId('_mce_item_');\r
11237                                 }\r
11238 \r
11239                                 elm = dom.get(item.id);\r
11240 \r
11241                                 if (excludeFromTabOrder) {\r
11242                                         dom.bind(elm, 'blur', itemBlurred);\r
11243                                         tabindex = '-1';\r
11244                                 } else {\r
11245                                         tabindex = (idx === 0 ? '0' : '-1');\r
11246                                 }\r
11247 \r
11248                                 elm.setAttribute('tabindex', tabindex);\r
11249                                 dom.bind(elm, 'focus', itemFocussed);\r
11250                         });\r
11251                         \r
11252                         // Setup initial state for root element.\r
11253                         if (items[0]){\r
11254                                 focussedId = items[0].id;\r
11255                         }\r
11256 \r
11257                         dom.setAttrib(root, 'tabindex', '-1');\r
11258 \r
11259                         // Setup listeners for root element.\r
11260                         var rootElm = dom.get(root);\r
11261                         dom.bind(rootElm, 'focus', rootFocussed);\r
11262                         dom.bind(rootElm, 'keydown', rootKeydown);\r
11263                 }\r
11264         });\r
11265 })(tinymce);\r
11266 \r
11267 (function(tinymce) {\r
11268         // Shorten class names\r
11269         var DOM = tinymce.DOM, is = tinymce.is;\r
11270 \r
11271         tinymce.create('tinymce.ui.Control', {\r
11272                 Control : function(id, s, editor) {\r
11273                         this.id = id;\r
11274                         this.settings = s = s || {};\r
11275                         this.rendered = false;\r
11276                         this.onRender = new tinymce.util.Dispatcher(this);\r
11277                         this.classPrefix = '';\r
11278                         this.scope = s.scope || this;\r
11279                         this.disabled = 0;\r
11280                         this.active = 0;\r
11281                         this.editor = editor;\r
11282                 },\r
11283                 \r
11284                 setAriaProperty : function(property, value) {\r
11285                         var element = DOM.get(this.id + '_aria') || DOM.get(this.id);\r
11286                         if (element) {\r
11287                                 DOM.setAttrib(element, 'aria-' + property, !!value);\r
11288                         }\r
11289                 },\r
11290                 \r
11291                 focus : function() {\r
11292                         DOM.get(this.id).focus();\r
11293                 },\r
11294 \r
11295                 setDisabled : function(s) {\r
11296                         if (s != this.disabled) {\r
11297                                 this.setAriaProperty('disabled', s);\r
11298 \r
11299                                 this.setState('Disabled', s);\r
11300                                 this.setState('Enabled', !s);\r
11301                                 this.disabled = s;\r
11302                         }\r
11303                 },\r
11304 \r
11305                 isDisabled : function() {\r
11306                         return this.disabled;\r
11307                 },\r
11308 \r
11309                 setActive : function(s) {\r
11310                         if (s != this.active) {\r
11311                                 this.setState('Active', s);\r
11312                                 this.active = s;\r
11313                                 this.setAriaProperty('pressed', s);\r
11314                         }\r
11315                 },\r
11316 \r
11317                 isActive : function() {\r
11318                         return this.active;\r
11319                 },\r
11320 \r
11321                 setState : function(c, s) {\r
11322                         var n = DOM.get(this.id);\r
11323 \r
11324                         c = this.classPrefix + c;\r
11325 \r
11326                         if (s)\r
11327                                 DOM.addClass(n, c);\r
11328                         else\r
11329                                 DOM.removeClass(n, c);\r
11330                 },\r
11331 \r
11332                 isRendered : function() {\r
11333                         return this.rendered;\r
11334                 },\r
11335 \r
11336                 renderHTML : function() {\r
11337                 },\r
11338 \r
11339                 renderTo : function(n) {\r
11340                         DOM.setHTML(n, this.renderHTML());\r
11341                 },\r
11342 \r
11343                 postRender : function() {\r
11344                         var t = this, b;\r
11345 \r
11346                         // Set pending states\r
11347                         if (is(t.disabled)) {\r
11348                                 b = t.disabled;\r
11349                                 t.disabled = -1;\r
11350                                 t.setDisabled(b);\r
11351                         }\r
11352 \r
11353                         if (is(t.active)) {\r
11354                                 b = t.active;\r
11355                                 t.active = -1;\r
11356                                 t.setActive(b);\r
11357                         }\r
11358                 },\r
11359 \r
11360                 remove : function() {\r
11361                         DOM.remove(this.id);\r
11362                         this.destroy();\r
11363                 },\r
11364 \r
11365                 destroy : function() {\r
11366                         tinymce.dom.Event.clear(this.id);\r
11367                 }\r
11368         });\r
11369 })(tinymce);\r
11370 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {\r
11371         Container : function(id, s, editor) {\r
11372                 this.parent(id, s, editor);\r
11373 \r
11374                 this.controls = [];\r
11375 \r
11376                 this.lookup = {};\r
11377         },\r
11378 \r
11379         add : function(c) {\r
11380                 this.lookup[c.id] = c;\r
11381                 this.controls.push(c);\r
11382 \r
11383                 return c;\r
11384         },\r
11385 \r
11386         get : function(n) {\r
11387                 return this.lookup[n];\r
11388         }\r
11389 });\r
11390 \r
11391 \r
11392 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {\r
11393         Separator : function(id, s) {\r
11394                 this.parent(id, s);\r
11395                 this.classPrefix = 'mceSeparator';\r
11396                 this.setDisabled(true);\r
11397         },\r
11398 \r
11399         renderHTML : function() {\r
11400                 return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});\r
11401         }\r
11402 });\r
11403 \r
11404 (function(tinymce) {\r
11405         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
11406 \r
11407         tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {\r
11408                 MenuItem : function(id, s) {\r
11409                         this.parent(id, s);\r
11410                         this.classPrefix = 'mceMenuItem';\r
11411                 },\r
11412 \r
11413                 setSelected : function(s) {\r
11414                         this.setState('Selected', s);\r
11415                         this.setAriaProperty('checked', !!s);\r
11416                         this.selected = s;\r
11417                 },\r
11418 \r
11419                 isSelected : function() {\r
11420                         return this.selected;\r
11421                 },\r
11422 \r
11423                 postRender : function() {\r
11424                         var t = this;\r
11425                         \r
11426                         t.parent();\r
11427 \r
11428                         // Set pending state\r
11429                         if (is(t.selected))\r
11430                                 t.setSelected(t.selected);\r
11431                 }\r
11432         });\r
11433 })(tinymce);\r
11434 \r
11435 (function(tinymce) {\r
11436         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;\r
11437 \r
11438         tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {\r
11439                 Menu : function(id, s) {\r
11440                         var t = this;\r
11441 \r
11442                         t.parent(id, s);\r
11443                         t.items = {};\r
11444                         t.collapsed = false;\r
11445                         t.menuCount = 0;\r
11446                         t.onAddItem = new tinymce.util.Dispatcher(this);\r
11447                 },\r
11448 \r
11449                 expand : function(d) {\r
11450                         var t = this;\r
11451 \r
11452                         if (d) {\r
11453                                 walk(t, function(o) {\r
11454                                         if (o.expand)\r
11455                                                 o.expand();\r
11456                                 }, 'items', t);\r
11457                         }\r
11458 \r
11459                         t.collapsed = false;\r
11460                 },\r
11461 \r
11462                 collapse : function(d) {\r
11463                         var t = this;\r
11464 \r
11465                         if (d) {\r
11466                                 walk(t, function(o) {\r
11467                                         if (o.collapse)\r
11468                                                 o.collapse();\r
11469                                 }, 'items', t);\r
11470                         }\r
11471 \r
11472                         t.collapsed = true;\r
11473                 },\r
11474 \r
11475                 isCollapsed : function() {\r
11476                         return this.collapsed;\r
11477                 },\r
11478 \r
11479                 add : function(o) {\r
11480                         if (!o.settings)\r
11481                                 o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);\r
11482 \r
11483                         this.onAddItem.dispatch(this, o);\r
11484 \r
11485                         return this.items[o.id] = o;\r
11486                 },\r
11487 \r
11488                 addSeparator : function() {\r
11489                         return this.add({separator : true});\r
11490                 },\r
11491 \r
11492                 addMenu : function(o) {\r
11493                         if (!o.collapse)\r
11494                                 o = this.createMenu(o);\r
11495 \r
11496                         this.menuCount++;\r
11497 \r
11498                         return this.add(o);\r
11499                 },\r
11500 \r
11501                 hasMenus : function() {\r
11502                         return this.menuCount !== 0;\r
11503                 },\r
11504 \r
11505                 remove : function(o) {\r
11506                         delete this.items[o.id];\r
11507                 },\r
11508 \r
11509                 removeAll : function() {\r
11510                         var t = this;\r
11511 \r
11512                         walk(t, function(o) {\r
11513                                 if (o.removeAll)\r
11514                                         o.removeAll();\r
11515                                 else\r
11516                                         o.remove();\r
11517 \r
11518                                 o.destroy();\r
11519                         }, 'items', t);\r
11520 \r
11521                         t.items = {};\r
11522                 },\r
11523 \r
11524                 createMenu : function(o) {\r
11525                         var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);\r
11526 \r
11527                         m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);\r
11528 \r
11529                         return m;\r
11530                 }\r
11531         });\r
11532 })(tinymce);\r
11533 (function(tinymce) {\r
11534         var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;\r
11535 \r
11536         tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {\r
11537                 DropMenu : function(id, s) {\r
11538                         s = s || {};\r
11539                         s.container = s.container || DOM.doc.body;\r
11540                         s.offset_x = s.offset_x || 0;\r
11541                         s.offset_y = s.offset_y || 0;\r
11542                         s.vp_offset_x = s.vp_offset_x || 0;\r
11543                         s.vp_offset_y = s.vp_offset_y || 0;\r
11544 \r
11545                         if (is(s.icons) && !s.icons)\r
11546                                 s['class'] += ' mceNoIcons';\r
11547 \r
11548                         this.parent(id, s);\r
11549                         this.onShowMenu = new tinymce.util.Dispatcher(this);\r
11550                         this.onHideMenu = new tinymce.util.Dispatcher(this);\r
11551                         this.classPrefix = 'mceMenu';\r
11552                 },\r
11553 \r
11554                 createMenu : function(s) {\r
11555                         var t = this, cs = t.settings, m;\r
11556 \r
11557                         s.container = s.container || cs.container;\r
11558                         s.parent = t;\r
11559                         s.constrain = s.constrain || cs.constrain;\r
11560                         s['class'] = s['class'] || cs['class'];\r
11561                         s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;\r
11562                         s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;\r
11563                         s.keyboard_focus = cs.keyboard_focus;\r
11564                         m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);\r
11565 \r
11566                         m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);\r
11567 \r
11568                         return m;\r
11569                 },\r
11570                 \r
11571                 focus : function() {\r
11572                         var t = this;\r
11573                         if (t.keyboardNav) {\r
11574                                 t.keyboardNav.focus();\r
11575                         }\r
11576                 },\r
11577 \r
11578                 update : function() {\r
11579                         var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;\r
11580 \r
11581                         tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;\r
11582                         th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;\r
11583 \r
11584                         if (!DOM.boxModel)\r
11585                                 t.element.setStyles({width : tw + 2, height : th + 2});\r
11586                         else\r
11587                                 t.element.setStyles({width : tw, height : th});\r
11588 \r
11589                         if (s.max_width)\r
11590                                 DOM.setStyle(co, 'width', tw);\r
11591 \r
11592                         if (s.max_height) {\r
11593                                 DOM.setStyle(co, 'height', th);\r
11594 \r
11595                                 if (tb.clientHeight < s.max_height)\r
11596                                         DOM.setStyle(co, 'overflow', 'hidden');\r
11597                         }\r
11598                 },\r
11599 \r
11600                 showMenu : function(x, y, px) {\r
11601                         var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;\r
11602 \r
11603                         t.collapse(1);\r
11604 \r
11605                         if (t.isMenuVisible)\r
11606                                 return;\r
11607 \r
11608                         if (!t.rendered) {\r
11609                                 co = DOM.add(t.settings.container, t.renderNode());\r
11610 \r
11611                                 each(t.items, function(o) {\r
11612                                         o.postRender();\r
11613                                 });\r
11614 \r
11615                                 t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
11616                         } else\r
11617                                 co = DOM.get('menu_' + t.id);\r
11618 \r
11619                         // Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug\r
11620                         if (!tinymce.isOpera)\r
11621                                 DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});\r
11622 \r
11623                         DOM.show(co);\r
11624                         t.update();\r
11625 \r
11626                         x += s.offset_x || 0;\r
11627                         y += s.offset_y || 0;\r
11628                         vp.w -= 4;\r
11629                         vp.h -= 4;\r
11630 \r
11631                         // Move inside viewport if not submenu\r
11632                         if (s.constrain) {\r
11633                                 w = co.clientWidth - ot;\r
11634                                 h = co.clientHeight - ot;\r
11635                                 mx = vp.x + vp.w;\r
11636                                 my = vp.y + vp.h;\r
11637 \r
11638                                 if ((x + s.vp_offset_x + w) > mx)\r
11639                                         x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);\r
11640 \r
11641                                 if ((y + s.vp_offset_y + h) > my)\r
11642                                         y = Math.max(0, (my - s.vp_offset_y) - h);\r
11643                         }\r
11644 \r
11645                         DOM.setStyles(co, {left : x , top : y});\r
11646                         t.element.update();\r
11647 \r
11648                         t.isMenuVisible = 1;\r
11649                         t.mouseClickFunc = Event.add(co, 'click', function(e) {\r
11650                                 var m;\r
11651 \r
11652                                 e = e.target;\r
11653 \r
11654                                 if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {\r
11655                                         m = t.items[e.id];\r
11656 \r
11657                                         if (m.isDisabled())\r
11658                                                 return;\r
11659 \r
11660                                         dm = t;\r
11661 \r
11662                                         while (dm) {\r
11663                                                 if (dm.hideMenu)\r
11664                                                         dm.hideMenu();\r
11665 \r
11666                                                 dm = dm.settings.parent;\r
11667                                         }\r
11668 \r
11669                                         if (m.settings.onclick)\r
11670                                                 m.settings.onclick(e);\r
11671 \r
11672                                         return false; // Cancel to fix onbeforeunload problem\r
11673                                 }\r
11674                         });\r
11675 \r
11676                         if (t.hasMenus()) {\r
11677                                 t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {\r
11678                                         var m, r, mi;\r
11679 \r
11680                                         e = e.target;\r
11681                                         if (e && (e = DOM.getParent(e, 'tr'))) {\r
11682                                                 m = t.items[e.id];\r
11683 \r
11684                                                 if (t.lastMenu)\r
11685                                                         t.lastMenu.collapse(1);\r
11686 \r
11687                                                 if (m.isDisabled())\r
11688                                                         return;\r
11689 \r
11690                                                 if (e && DOM.hasClass(e, cp + 'ItemSub')) {\r
11691                                                         //p = DOM.getPos(s.container);\r
11692                                                         r = DOM.getRect(e);\r
11693                                                         m.showMenu((r.x + r.w - ot), r.y - ot, r.x);\r
11694                                                         t.lastMenu = m;\r
11695                                                         DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');\r
11696                                                 }\r
11697                                         }\r
11698                                 });\r
11699                         }\r
11700                         \r
11701                         Event.add(co, 'keydown', t._keyHandler, t);\r
11702 \r
11703                         t.onShowMenu.dispatch(t);\r
11704 \r
11705                         if (s.keyboard_focus) { \r
11706                                 t._setupKeyboardNav(); \r
11707                         }\r
11708                 },\r
11709 \r
11710                 hideMenu : function(c) {\r
11711                         var t = this, co = DOM.get('menu_' + t.id), e;\r
11712 \r
11713                         if (!t.isMenuVisible)\r
11714                                 return;\r
11715 \r
11716                         if (t.keyboardNav) t.keyboardNav.destroy();\r
11717                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
11718                         Event.remove(co, 'click', t.mouseClickFunc);\r
11719                         Event.remove(co, 'keydown', t._keyHandler);\r
11720                         DOM.hide(co);\r
11721                         t.isMenuVisible = 0;\r
11722 \r
11723                         if (!c)\r
11724                                 t.collapse(1);\r
11725 \r
11726                         if (t.element)\r
11727                                 t.element.hide();\r
11728 \r
11729                         if (e = DOM.get(t.id))\r
11730                                 DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');\r
11731 \r
11732                         t.onHideMenu.dispatch(t);\r
11733                 },\r
11734 \r
11735                 add : function(o) {\r
11736                         var t = this, co;\r
11737 \r
11738                         o = t.parent(o);\r
11739 \r
11740                         if (t.isRendered && (co = DOM.get('menu_' + t.id)))\r
11741                                 t._add(DOM.select('tbody', co)[0], o);\r
11742 \r
11743                         return o;\r
11744                 },\r
11745 \r
11746                 collapse : function(d) {\r
11747                         this.parent(d);\r
11748                         this.hideMenu(1);\r
11749                 },\r
11750 \r
11751                 remove : function(o) {\r
11752                         DOM.remove(o.id);\r
11753                         this.destroy();\r
11754 \r
11755                         return this.parent(o);\r
11756                 },\r
11757 \r
11758                 destroy : function() {\r
11759                         var t = this, co = DOM.get('menu_' + t.id);\r
11760 \r
11761                         if (t.keyboardNav) t.keyboardNav.destroy();\r
11762                         Event.remove(co, 'mouseover', t.mouseOverFunc);\r
11763                         Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);\r
11764                         Event.remove(co, 'click', t.mouseClickFunc);\r
11765                         Event.remove(co, 'keydown', t._keyHandler);\r
11766 \r
11767                         if (t.element)\r
11768                                 t.element.remove();\r
11769 \r
11770                         DOM.remove(co);\r
11771                 },\r
11772 \r
11773                 renderNode : function() {\r
11774                         var t = this, s = t.settings, n, tb, co, w;\r
11775 \r
11776                         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
11777                         if (t.settings.parent) {\r
11778                                 DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);\r
11779                         }\r
11780                         co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});\r
11781                         t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});\r
11782 \r
11783                         if (s.menu_line)\r
11784                                 DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});\r
11785 \r
11786 //                      n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});\r
11787                         n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});\r
11788                         tb = DOM.add(n, 'tbody');\r
11789 \r
11790                         each(t.items, function(o) {\r
11791                                 t._add(tb, o);\r
11792                         });\r
11793 \r
11794                         t.rendered = true;\r
11795 \r
11796                         return w;\r
11797                 },\r
11798 \r
11799                 // Internal functions\r
11800                 _setupKeyboardNav : function(){\r
11801                         var contextMenu, menuItems, t=this; \r
11802                         contextMenu = DOM.get('menu_' + t.id);\r
11803                         menuItems = DOM.select('a[role=option]', 'menu_' + t.id);\r
11804                         menuItems.splice(0,0,contextMenu);\r
11805                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({\r
11806                                 root: 'menu_' + t.id,\r
11807                                 items: menuItems,\r
11808                                 onCancel: function() {\r
11809                                         t.hideMenu();\r
11810                                 },\r
11811                                 enableUpDown: true\r
11812                         });\r
11813                         contextMenu.focus();\r
11814                 },\r
11815 \r
11816                 _keyHandler : function(evt) {\r
11817                         var t = this, e;\r
11818                         switch (evt.keyCode) {\r
11819                                 case 37: // Left\r
11820                                         if (t.settings.parent) {\r
11821                                                 t.hideMenu();\r
11822                                                 t.settings.parent.focus();\r
11823                                                 Event.cancel(evt);\r
11824                                         }\r
11825                                         break;\r
11826                                 case 39: // Right\r
11827                                         if (t.mouseOverFunc)\r
11828                                                 t.mouseOverFunc(evt);\r
11829                                         break;\r
11830                         }\r
11831                 },\r
11832 \r
11833                 _add : function(tb, o) {\r
11834                         var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;\r
11835 \r
11836                         if (s.separator) {\r
11837                                 ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});\r
11838                                 DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});\r
11839 \r
11840                                 if (n = ro.previousSibling)\r
11841                                         DOM.addClass(n, 'mceLast');\r
11842 \r
11843                                 return;\r
11844                         }\r
11845 \r
11846                         n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});\r
11847                         n = it = DOM.add(n, s.titleItem ? 'th' : 'td');\r
11848                         n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});\r
11849 \r
11850                         if (s.parent) {\r
11851                                 DOM.setAttrib(a, 'aria-haspopup', 'true');\r
11852                                 DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);\r
11853                         }\r
11854 \r
11855                         DOM.addClass(it, s['class']);\r
11856 //                      n = DOM.add(n, 'span', {'class' : 'item'});\r
11857 \r
11858                         ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});\r
11859 \r
11860                         if (s.icon_src)\r
11861                                 DOM.add(ic, 'img', {src : s.icon_src});\r
11862 \r
11863                         n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);\r
11864 \r
11865                         if (o.settings.style) {\r
11866                                 if (typeof o.settings.style == "function")\r
11867                                         o.settings.style = o.settings.style();\r
11868 \r
11869                                 DOM.setAttrib(n, 'style', o.settings.style);\r
11870                         }\r
11871 \r
11872                         if (tb.childNodes.length == 1)\r
11873                                 DOM.addClass(ro, 'mceFirst');\r
11874 \r
11875                         if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))\r
11876                                 DOM.addClass(ro, 'mceFirst');\r
11877 \r
11878                         if (o.collapse)\r
11879                                 DOM.addClass(ro, cp + 'ItemSub');\r
11880 \r
11881                         if (n = ro.previousSibling)\r
11882                                 DOM.removeClass(n, 'mceLast');\r
11883 \r
11884                         DOM.addClass(ro, 'mceLast');\r
11885                 }\r
11886         });\r
11887 })(tinymce);\r
11888 (function(tinymce) {\r
11889         var DOM = tinymce.DOM;\r
11890 \r
11891         tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {\r
11892                 Button : function(id, s, ed) {\r
11893                         this.parent(id, s, ed);\r
11894                         this.classPrefix = 'mceButton';\r
11895                 },\r
11896 \r
11897                 renderHTML : function() {\r
11898                         var cp = this.classPrefix, s = this.settings, h, l;\r
11899 \r
11900                         l = DOM.encode(s.label || '');\r
11901                         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
11902                         if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )\r
11903                                 h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
11904                         else\r
11905                                 h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');\r
11906 \r
11907                         h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; \r
11908                         h += '</a>';\r
11909                         return h;\r
11910                 },\r
11911 \r
11912                 postRender : function() {\r
11913                         var t = this, s = t.settings, imgBookmark;\r
11914 \r
11915                         // In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so\r
11916                         // need to keep the selection in case the selection is lost\r
11917                         if (tinymce.isIE && t.editor) {\r
11918                                 tinymce.dom.Event.add(t.id, 'mousedown', function(e) {\r
11919                                         var nodeName = t.editor.selection.getNode().nodeName;\r
11920                                         imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;\r
11921                                 });\r
11922                         }\r
11923                         tinymce.dom.Event.add(t.id, 'click', function(e) {\r
11924                                 if (!t.isDisabled()) {\r
11925                                         // restore the selection in case the selection is lost in IE\r
11926                                         if (tinymce.isIE && t.editor && imgBookmark !== null) {\r
11927                                                 t.editor.selection.moveToBookmark(imgBookmark);\r
11928                                         }\r
11929                                         return s.onclick.call(s.scope, e);\r
11930                                 }\r
11931                         });\r
11932                         tinymce.dom.Event.add(t.id, 'keyup', function(e) {\r
11933                                 if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)\r
11934                                         return s.onclick.call(s.scope, e);\r
11935                         });\r
11936                 }\r
11937         });\r
11938 })(tinymce);\r
11939 \r
11940 (function(tinymce) {\r
11941         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;\r
11942 \r
11943         tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {\r
11944                 ListBox : function(id, s, ed) {\r
11945                         var t = this;\r
11946 \r
11947                         t.parent(id, s, ed);\r
11948 \r
11949                         t.items = [];\r
11950 \r
11951                         t.onChange = new Dispatcher(t);\r
11952 \r
11953                         t.onPostRender = new Dispatcher(t);\r
11954 \r
11955                         t.onAdd = new Dispatcher(t);\r
11956 \r
11957                         t.onRenderMenu = new tinymce.util.Dispatcher(this);\r
11958 \r
11959                         t.classPrefix = 'mceListBox';\r
11960                         t.marked = {};\r
11961                 },\r
11962 \r
11963                 select : function(va) {\r
11964                         var t = this, fv, f;\r
11965 \r
11966                         t.marked = {};\r
11967 \r
11968                         if (va == undef)\r
11969                                 return t.selectByIndex(-1);\r
11970 \r
11971                         // Is string or number make function selector\r
11972                         if (va && typeof(va)=="function")\r
11973                                 f = va;\r
11974                         else {\r
11975                                 f = function(v) {\r
11976                                         return v == va;\r
11977                                 };\r
11978                         }\r
11979 \r
11980                         // Do we need to do something?\r
11981                         if (va != t.selectedValue) {\r
11982                                 // Find item\r
11983                                 each(t.items, function(o, i) {\r
11984                                         if (f(o.value)) {\r
11985                                                 fv = 1;\r
11986                                                 t.selectByIndex(i);\r
11987                                                 return false;\r
11988                                         }\r
11989                                 });\r
11990 \r
11991                                 if (!fv)\r
11992                                         t.selectByIndex(-1);\r
11993                         }\r
11994                 },\r
11995 \r
11996                 selectByIndex : function(idx) {\r
11997                         var t = this, e, o, label;\r
11998 \r
11999                         t.marked = {};\r
12000 \r
12001                         if (idx != t.selectedIndex) {\r
12002                                 e = DOM.get(t.id + '_text');\r
12003                                 label = DOM.get(t.id + '_voiceDesc');\r
12004                                 o = t.items[idx];\r
12005 \r
12006                                 if (o) {\r
12007                                         t.selectedValue = o.value;\r
12008                                         t.selectedIndex = idx;\r
12009                                         DOM.setHTML(e, DOM.encode(o.title));\r
12010                                         DOM.setHTML(label, t.settings.title + " - " + o.title);\r
12011                                         DOM.removeClass(e, 'mceTitle');\r
12012                                         DOM.setAttrib(t.id, 'aria-valuenow', o.title);\r
12013                                 } else {\r
12014                                         DOM.setHTML(e, DOM.encode(t.settings.title));\r
12015                                         DOM.setHTML(label, DOM.encode(t.settings.title));\r
12016                                         DOM.addClass(e, 'mceTitle');\r
12017                                         t.selectedValue = t.selectedIndex = null;\r
12018                                         DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);\r
12019                                 }\r
12020                                 e = 0;\r
12021                         }\r
12022                 },\r
12023 \r
12024                 mark : function(value) {\r
12025                         this.marked[value] = true;\r
12026                 },\r
12027 \r
12028                 add : function(n, v, o) {\r
12029                         var t = this;\r
12030 \r
12031                         o = o || {};\r
12032                         o = tinymce.extend(o, {\r
12033                                 title : n,\r
12034                                 value : v\r
12035                         });\r
12036 \r
12037                         t.items.push(o);\r
12038                         t.onAdd.dispatch(t, o);\r
12039                 },\r
12040 \r
12041                 getLength : function() {\r
12042                         return this.items.length;\r
12043                 },\r
12044 \r
12045                 renderHTML : function() {\r
12046                         var h = '', t = this, s = t.settings, cp = t.classPrefix;\r
12047 \r
12048                         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
12049                         h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); \r
12050                         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
12051                         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
12052                         h += '</tr></tbody></table></span>';\r
12053 \r
12054                         return h;\r
12055                 },\r
12056 \r
12057                 showMenu : function() {\r
12058                         var t = this, p2, e = DOM.get(this.id), m;\r
12059 \r
12060                         if (t.isDisabled() || t.items.length === 0)\r
12061                                 return;\r
12062 \r
12063                         if (t.menu && t.menu.isMenuVisible)\r
12064                                 return t.hideMenu();\r
12065 \r
12066                         if (!t.isMenuRendered) {\r
12067                                 t.renderMenu();\r
12068                                 t.isMenuRendered = true;\r
12069                         }\r
12070 \r
12071                         p2 = DOM.getPos(e);\r
12072 \r
12073                         m = t.menu;\r
12074                         m.settings.offset_x = p2.x;\r
12075                         m.settings.offset_y = p2.y;\r
12076                         m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus\r
12077 \r
12078                         // Select in menu\r
12079                         each(t.items, function(o) {\r
12080                                 if (m.items[o.id]) {\r
12081                                         m.items[o.id].setSelected(0);\r
12082                                 }\r
12083                         });\r
12084 \r
12085                         each(t.items, function(o) {\r
12086                                 if (m.items[o.id] && t.marked[o.value]) {\r
12087                                         m.items[o.id].setSelected(1);\r
12088                                 }\r
12089 \r
12090                                 if (o.value === t.selectedValue) {\r
12091                                         m.items[o.id].setSelected(1);\r
12092                                 }\r
12093                         });\r
12094 \r
12095                         m.showMenu(0, e.clientHeight);\r
12096 \r
12097                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
12098                         DOM.addClass(t.id, t.classPrefix + 'Selected');\r
12099 \r
12100                         //DOM.get(t.id + '_text').focus();\r
12101                 },\r
12102 \r
12103                 hideMenu : function(e) {\r
12104                         var t = this;\r
12105 \r
12106                         if (t.menu && t.menu.isMenuVisible) {\r
12107                                 DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
12108 \r
12109                                 // Prevent double toogles by canceling the mouse click event to the button\r
12110                                 if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))\r
12111                                         return;\r
12112 \r
12113                                 if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
12114                                         DOM.removeClass(t.id, t.classPrefix + 'Selected');\r
12115                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
12116                                         t.menu.hideMenu();\r
12117                                 }\r
12118                         }\r
12119                 },\r
12120 \r
12121                 renderMenu : function() {\r
12122                         var t = this, m;\r
12123 \r
12124                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
12125                                 menu_line : 1,\r
12126                                 'class' : t.classPrefix + 'Menu mceNoIcons',\r
12127                                 max_width : 250,\r
12128                                 max_height : 150\r
12129                         });\r
12130 \r
12131                         m.onHideMenu.add(function() {\r
12132                                 t.hideMenu();\r
12133                                 t.focus();\r
12134                         });\r
12135 \r
12136                         m.add({\r
12137                                 title : t.settings.title,\r
12138                                 'class' : 'mceMenuItemTitle',\r
12139                                 onclick : function() {\r
12140                                         if (t.settings.onselect('') !== false)\r
12141                                                 t.select(''); // Must be runned after\r
12142                                 }\r
12143                         });\r
12144 \r
12145                         each(t.items, function(o) {\r
12146                                 // No value then treat it as a title\r
12147                                 if (o.value === undef) {\r
12148                                         m.add({\r
12149                                                 title : o.title,\r
12150                                                 role : "option",\r
12151                                                 'class' : 'mceMenuItemTitle',\r
12152                                                 onclick : function() {\r
12153                                                         if (t.settings.onselect('') !== false)\r
12154                                                                 t.select(''); // Must be runned after\r
12155                                                 }\r
12156                                         });\r
12157                                 } else {\r
12158                                         o.id = DOM.uniqueId();\r
12159                                         o.role= "option";\r
12160                                         o.onclick = function() {\r
12161                                                 if (t.settings.onselect(o.value) !== false)\r
12162                                                         t.select(o.value); // Must be runned after\r
12163                                         };\r
12164 \r
12165                                         m.add(o);\r
12166                                 }\r
12167                         });\r
12168 \r
12169                         t.onRenderMenu.dispatch(t, m);\r
12170                         t.menu = m;\r
12171                 },\r
12172 \r
12173                 postRender : function() {\r
12174                         var t = this, cp = t.classPrefix;\r
12175 \r
12176                         Event.add(t.id, 'click', t.showMenu, t);\r
12177                         Event.add(t.id, 'keydown', function(evt) {\r
12178                                 if (evt.keyCode == 32) { // Space\r
12179                                         t.showMenu(evt);\r
12180                                         Event.cancel(evt);\r
12181                                 }\r
12182                         });\r
12183                         Event.add(t.id, 'focus', function() {\r
12184                                 if (!t._focused) {\r
12185                                         t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {\r
12186                                                 if (e.keyCode == 40) {\r
12187                                                         t.showMenu();\r
12188                                                         Event.cancel(e);\r
12189                                                 }\r
12190                                         });\r
12191                                         t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {\r
12192                                                 var v;\r
12193                                                 if (e.keyCode == 13) {\r
12194                                                         // Fake select on enter\r
12195                                                         v = t.selectedValue;\r
12196                                                         t.selectedValue = null; // Needs to be null to fake change\r
12197                                                         Event.cancel(e);\r
12198                                                         t.settings.onselect(v);\r
12199                                                 }\r
12200                                         });\r
12201                                 }\r
12202 \r
12203                                 t._focused = 1;\r
12204                         });\r
12205                         Event.add(t.id, 'blur', function() {\r
12206                                 Event.remove(t.id, 'keydown', t.keyDownHandler);\r
12207                                 Event.remove(t.id, 'keypress', t.keyPressHandler);\r
12208                                 t._focused = 0;\r
12209                         });\r
12210 \r
12211                         // Old IE doesn't have hover on all elements\r
12212                         if (tinymce.isIE6 || !DOM.boxModel) {\r
12213                                 Event.add(t.id, 'mouseover', function() {\r
12214                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
12215                                                 DOM.addClass(t.id, cp + 'Hover');\r
12216                                 });\r
12217 \r
12218                                 Event.add(t.id, 'mouseout', function() {\r
12219                                         if (!DOM.hasClass(t.id, cp + 'Disabled'))\r
12220                                                 DOM.removeClass(t.id, cp + 'Hover');\r
12221                                 });\r
12222                         }\r
12223 \r
12224                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
12225                 },\r
12226 \r
12227                 destroy : function() {\r
12228                         this.parent();\r
12229 \r
12230                         Event.clear(this.id + '_text');\r
12231                         Event.clear(this.id + '_open');\r
12232                 }\r
12233         });\r
12234 })(tinymce);\r
12235 \r
12236 (function(tinymce) {\r
12237         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;\r
12238 \r
12239         tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {\r
12240                 NativeListBox : function(id, s) {\r
12241                         this.parent(id, s);\r
12242                         this.classPrefix = 'mceNativeListBox';\r
12243                 },\r
12244 \r
12245                 setDisabled : function(s) {\r
12246                         DOM.get(this.id).disabled = s;\r
12247                         this.setAriaProperty('disabled', s);\r
12248                 },\r
12249 \r
12250                 isDisabled : function() {\r
12251                         return DOM.get(this.id).disabled;\r
12252                 },\r
12253 \r
12254                 select : function(va) {\r
12255                         var t = this, fv, f;\r
12256 \r
12257                         if (va == undef)\r
12258                                 return t.selectByIndex(-1);\r
12259 \r
12260                         // Is string or number make function selector\r
12261                         if (va && typeof(va)=="function")\r
12262                                 f = va;\r
12263                         else {\r
12264                                 f = function(v) {\r
12265                                         return v == va;\r
12266                                 };\r
12267                         }\r
12268 \r
12269                         // Do we need to do something?\r
12270                         if (va != t.selectedValue) {\r
12271                                 // Find item\r
12272                                 each(t.items, function(o, i) {\r
12273                                         if (f(o.value)) {\r
12274                                                 fv = 1;\r
12275                                                 t.selectByIndex(i);\r
12276                                                 return false;\r
12277                                         }\r
12278                                 });\r
12279 \r
12280                                 if (!fv)\r
12281                                         t.selectByIndex(-1);\r
12282                         }\r
12283                 },\r
12284 \r
12285                 selectByIndex : function(idx) {\r
12286                         DOM.get(this.id).selectedIndex = idx + 1;\r
12287                         this.selectedValue = this.items[idx] ? this.items[idx].value : null;\r
12288                 },\r
12289 \r
12290                 add : function(n, v, a) {\r
12291                         var o, t = this;\r
12292 \r
12293                         a = a || {};\r
12294                         a.value = v;\r
12295 \r
12296                         if (t.isRendered())\r
12297                                 DOM.add(DOM.get(this.id), 'option', a, n);\r
12298 \r
12299                         o = {\r
12300                                 title : n,\r
12301                                 value : v,\r
12302                                 attribs : a\r
12303                         };\r
12304 \r
12305                         t.items.push(o);\r
12306                         t.onAdd.dispatch(t, o);\r
12307                 },\r
12308 \r
12309                 getLength : function() {\r
12310                         return this.items.length;\r
12311                 },\r
12312 \r
12313                 renderHTML : function() {\r
12314                         var h, t = this;\r
12315 \r
12316                         h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');\r
12317 \r
12318                         each(t.items, function(it) {\r
12319                                 h += DOM.createHTML('option', {value : it.value}, it.title);\r
12320                         });\r
12321 \r
12322                         h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);\r
12323                         h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);\r
12324                         return h;\r
12325                 },\r
12326 \r
12327                 postRender : function() {\r
12328                         var t = this, ch, changeListenerAdded = true;\r
12329 \r
12330                         t.rendered = true;\r
12331 \r
12332                         function onChange(e) {\r
12333                                 var v = t.items[e.target.selectedIndex - 1];\r
12334 \r
12335                                 if (v && (v = v.value)) {\r
12336                                         t.onChange.dispatch(t, v);\r
12337 \r
12338                                         if (t.settings.onselect)\r
12339                                                 t.settings.onselect(v);\r
12340                                 }\r
12341                         };\r
12342 \r
12343                         Event.add(t.id, 'change', onChange);\r
12344 \r
12345                         // Accessibility keyhandler\r
12346                         Event.add(t.id, 'keydown', function(e) {\r
12347                                 var bf;\r
12348 \r
12349                                 Event.remove(t.id, 'change', ch);\r
12350                                 changeListenerAdded = false;\r
12351 \r
12352                                 bf = Event.add(t.id, 'blur', function() {\r
12353                                         if (changeListenerAdded) return;\r
12354                                         changeListenerAdded = true;\r
12355                                         Event.add(t.id, 'change', onChange);\r
12356                                         Event.remove(t.id, 'blur', bf);\r
12357                                 });\r
12358 \r
12359                                 //prevent default left and right keys on chrome - so that the keyboard navigation is used.\r
12360                                 if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {\r
12361                                         return Event.prevent(e);\r
12362                                 }\r
12363                                 \r
12364                                 if (e.keyCode == 13 || e.keyCode == 32) {\r
12365                                         onChange(e);\r
12366                                         return Event.cancel(e);\r
12367                                 }\r
12368                         });\r
12369 \r
12370                         t.onPostRender.dispatch(t, DOM.get(t.id));\r
12371                 }\r
12372         });\r
12373 })(tinymce);\r
12374 \r
12375 (function(tinymce) {\r
12376         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
12377 \r
12378         tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {\r
12379                 MenuButton : function(id, s, ed) {\r
12380                         this.parent(id, s, ed);\r
12381 \r
12382                         this.onRenderMenu = new tinymce.util.Dispatcher(this);\r
12383 \r
12384                         s.menu_container = s.menu_container || DOM.doc.body;\r
12385                 },\r
12386 \r
12387                 showMenu : function() {\r
12388                         var t = this, p1, p2, e = DOM.get(t.id), m;\r
12389 \r
12390                         if (t.isDisabled())\r
12391                                 return;\r
12392 \r
12393                         if (!t.isMenuRendered) {\r
12394                                 t.renderMenu();\r
12395                                 t.isMenuRendered = true;\r
12396                         }\r
12397 \r
12398                         if (t.isMenuVisible)\r
12399                                 return t.hideMenu();\r
12400 \r
12401                         p1 = DOM.getPos(t.settings.menu_container);\r
12402                         p2 = DOM.getPos(e);\r
12403 \r
12404                         m = t.menu;\r
12405                         m.settings.offset_x = p2.x;\r
12406                         m.settings.offset_y = p2.y;\r
12407                         m.settings.vp_offset_x = p2.x;\r
12408                         m.settings.vp_offset_y = p2.y;\r
12409                         m.settings.keyboard_focus = t._focused;\r
12410                         m.showMenu(0, e.firstChild.clientHeight);\r
12411 \r
12412                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
12413                         t.setState('Selected', 1);\r
12414 \r
12415                         t.isMenuVisible = 1;\r
12416                 },\r
12417 \r
12418                 renderMenu : function() {\r
12419                         var t = this, m;\r
12420 \r
12421                         m = t.settings.control_manager.createDropMenu(t.id + '_menu', {\r
12422                                 menu_line : 1,\r
12423                                 'class' : this.classPrefix + 'Menu',\r
12424                                 icons : t.settings.icons\r
12425                         });\r
12426 \r
12427                         m.onHideMenu.add(function() {\r
12428                                 t.hideMenu();\r
12429                                 t.focus();\r
12430                         });\r
12431 \r
12432                         t.onRenderMenu.dispatch(t, m);\r
12433                         t.menu = m;\r
12434                 },\r
12435 \r
12436                 hideMenu : function(e) {\r
12437                         var t = this;\r
12438 \r
12439                         // Prevent double toogles by canceling the mouse click event to the button\r
12440                         if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))\r
12441                                 return;\r
12442 \r
12443                         if (!e || !DOM.getParent(e.target, '.mceMenu')) {\r
12444                                 t.setState('Selected', 0);\r
12445                                 Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
12446                                 if (t.menu)\r
12447                                         t.menu.hideMenu();\r
12448                         }\r
12449 \r
12450                         t.isMenuVisible = 0;\r
12451                 },\r
12452 \r
12453                 postRender : function() {\r
12454                         var t = this, s = t.settings;\r
12455 \r
12456                         Event.add(t.id, 'click', function() {\r
12457                                 if (!t.isDisabled()) {\r
12458                                         if (s.onclick)\r
12459                                                 s.onclick(t.value);\r
12460 \r
12461                                         t.showMenu();\r
12462                                 }\r
12463                         });\r
12464                 }\r
12465         });\r
12466 })(tinymce);\r
12467 \r
12468 (function(tinymce) {\r
12469         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;\r
12470 \r
12471         tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {\r
12472                 SplitButton : function(id, s, ed) {\r
12473                         this.parent(id, s, ed);\r
12474                         this.classPrefix = 'mceSplitButton';\r
12475                 },\r
12476 \r
12477                 renderHTML : function() {\r
12478                         var h, t = this, s = t.settings, h1;\r
12479 \r
12480                         h = '<tbody><tr>';\r
12481 \r
12482                         if (s.image)\r
12483                                 h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});\r
12484                         else\r
12485                                 h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');\r
12486 \r
12487                         h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);\r
12488                         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
12489         \r
12490                         h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');\r
12491                         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
12492 \r
12493                         h += '</tr></tbody>';\r
12494                         h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);\r
12495                         return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);\r
12496                 },\r
12497 \r
12498                 postRender : function() {\r
12499                         var t = this, s = t.settings, activate;\r
12500 \r
12501                         if (s.onclick) {\r
12502                                 activate = function(evt) {\r
12503                                         if (!t.isDisabled()) {\r
12504                                                 s.onclick(t.value);\r
12505                                                 Event.cancel(evt);\r
12506                                         }\r
12507                                 };\r
12508                                 Event.add(t.id + '_action', 'click', activate);\r
12509                                 Event.add(t.id, ['click', 'keydown'], function(evt) {\r
12510                                         var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;\r
12511                                         if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {\r
12512                                                 activate();\r
12513                                                 Event.cancel(evt);\r
12514                                         } else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {\r
12515                                                 t.showMenu();\r
12516                                                 Event.cancel(evt);\r
12517                                         }\r
12518                                 });\r
12519                         }\r
12520 \r
12521                         Event.add(t.id + '_open', 'click', function (evt) {\r
12522                                 t.showMenu();\r
12523                                 Event.cancel(evt);\r
12524                         });\r
12525                         Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});\r
12526                         Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});\r
12527 \r
12528                         // Old IE doesn't have hover on all elements\r
12529                         if (tinymce.isIE6 || !DOM.boxModel) {\r
12530                                 Event.add(t.id, 'mouseover', function() {\r
12531                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
12532                                                 DOM.addClass(t.id, 'mceSplitButtonHover');\r
12533                                 });\r
12534 \r
12535                                 Event.add(t.id, 'mouseout', function() {\r
12536                                         if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))\r
12537                                                 DOM.removeClass(t.id, 'mceSplitButtonHover');\r
12538                                 });\r
12539                         }\r
12540                 },\r
12541 \r
12542                 destroy : function() {\r
12543                         this.parent();\r
12544 \r
12545                         Event.clear(this.id + '_action');\r
12546                         Event.clear(this.id + '_open');\r
12547                         Event.clear(this.id);\r
12548                 }\r
12549         });\r
12550 })(tinymce);\r
12551 \r
12552 (function(tinymce) {\r
12553         var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;\r
12554 \r
12555         tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {\r
12556                 ColorSplitButton : function(id, s, ed) {\r
12557                         var t = this;\r
12558 \r
12559                         t.parent(id, s, ed);\r
12560 \r
12561                         t.settings = s = tinymce.extend({\r
12562                                 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
12563                                 grid_width : 8,\r
12564                                 default_color : '#888888'\r
12565                         }, t.settings);\r
12566 \r
12567                         t.onShowMenu = new tinymce.util.Dispatcher(t);\r
12568 \r
12569                         t.onHideMenu = new tinymce.util.Dispatcher(t);\r
12570 \r
12571                         t.value = s.default_color;\r
12572                 },\r
12573 \r
12574                 showMenu : function() {\r
12575                         var t = this, r, p, e, p2;\r
12576 \r
12577                         if (t.isDisabled())\r
12578                                 return;\r
12579 \r
12580                         if (!t.isMenuRendered) {\r
12581                                 t.renderMenu();\r
12582                                 t.isMenuRendered = true;\r
12583                         }\r
12584 \r
12585                         if (t.isMenuVisible)\r
12586                                 return t.hideMenu();\r
12587 \r
12588                         e = DOM.get(t.id);\r
12589                         DOM.show(t.id + '_menu');\r
12590                         DOM.addClass(e, 'mceSplitButtonSelected');\r
12591                         p2 = DOM.getPos(e);\r
12592                         DOM.setStyles(t.id + '_menu', {\r
12593                                 left : p2.x,\r
12594                                 top : p2.y + e.firstChild.clientHeight,\r
12595                                 zIndex : 200000\r
12596                         });\r
12597                         e = 0;\r
12598 \r
12599                         Event.add(DOM.doc, 'mousedown', t.hideMenu, t);\r
12600                         t.onShowMenu.dispatch(t);\r
12601 \r
12602                         if (t._focused) {\r
12603                                 t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {\r
12604                                         if (e.keyCode == 27)\r
12605                                                 t.hideMenu();\r
12606                                 });\r
12607 \r
12608                                 DOM.select('a', t.id + '_menu')[0].focus(); // Select first link\r
12609                         }\r
12610 \r
12611                         t.keyboardNav = new tinymce.ui.KeyboardNavigation({\r
12612                                 root: t.id + '_menu',\r
12613                                 items: DOM.select('a', t.id + '_menu'),\r
12614                                 onCancel: function() {\r
12615                                         t.hideMenu();\r
12616                                         t.focus();\r
12617                                 }\r
12618                         });\r
12619 \r
12620                         t.keyboardNav.focus();\r
12621                         t.isMenuVisible = 1;\r
12622                 },\r
12623 \r
12624                 hideMenu : function(e) {\r
12625                         var t = this;\r
12626 \r
12627                         if (t.isMenuVisible) {\r
12628                                 // Prevent double toogles by canceling the mouse click event to the button\r
12629                                 if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))\r
12630                                         return;\r
12631 \r
12632                                 if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {\r
12633                                         DOM.removeClass(t.id, 'mceSplitButtonSelected');\r
12634                                         Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);\r
12635                                         Event.remove(t.id + '_menu', 'keydown', t._keyHandler);\r
12636                                         DOM.hide(t.id + '_menu');\r
12637                                 }\r
12638 \r
12639                                 t.isMenuVisible = 0;\r
12640                                 t.onHideMenu.dispatch();\r
12641                                 t.keyboardNav.destroy();\r
12642                         }\r
12643                 },\r
12644 \r
12645                 renderMenu : function() {\r
12646                         var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;\r
12647 \r
12648                         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
12649                         m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});\r
12650                         DOM.add(m, 'span', {'class' : 'mceMenuLine'});\r
12651 \r
12652                         n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});\r
12653                         tb = DOM.add(n, 'tbody');\r
12654 \r
12655                         // Generate color grid\r
12656                         i = 0;\r
12657                         each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {\r
12658                                 c = c.replace(/^#/, '');\r
12659 \r
12660                                 if (!i--) {\r
12661                                         tr = DOM.add(tb, 'tr');\r
12662                                         i = s.grid_width - 1;\r
12663                                 }\r
12664 \r
12665                                 n = DOM.add(tr, 'td');\r
12666                                 var settings = {\r
12667                                         href : 'javascript:;',\r
12668                                         style : {\r
12669                                                 backgroundColor : '#' + c\r
12670                                         },\r
12671                                         'title': t.editor.getLang('colors.' + c, c),\r
12672                                         'data-mce-color' : '#' + c\r
12673                                 };\r
12674 \r
12675                                 // adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.\r
12676                                 if (!tinymce.isIE ) {\r
12677                                         settings.role = 'option';\r
12678                                 }\r
12679 \r
12680                                 n = DOM.add(n, 'a', settings);\r
12681 \r
12682                                 if (t.editor.forcedHighContrastMode) {\r
12683                                         n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });\r
12684                                         if (n.getContext && (context = n.getContext("2d"))) {\r
12685                                                 context.fillStyle = '#' + c;\r
12686                                                 context.fillRect(0, 0, 16, 16);\r
12687                                         } else {\r
12688                                                 // No point leaving a canvas element around if it's not supported for drawing on anyway.\r
12689                                                 DOM.remove(n);\r
12690                                         }\r
12691                                 }\r
12692                         });\r
12693 \r
12694                         if (s.more_colors_func) {\r
12695                                 n = DOM.add(tb, 'tr');\r
12696                                 n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});\r
12697                                 n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);\r
12698 \r
12699                                 Event.add(n, 'click', function(e) {\r
12700                                         s.more_colors_func.call(s.more_colors_scope || this);\r
12701                                         return Event.cancel(e); // Cancel to fix onbeforeunload problem\r
12702                                 });\r
12703                         }\r
12704 \r
12705                         DOM.addClass(m, 'mceColorSplitMenu');\r
12706 \r
12707                         // Prevent IE from scrolling and hindering click to occur #4019\r
12708                         Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});\r
12709 \r
12710                         Event.add(t.id + '_menu', 'click', function(e) {\r
12711                                 var c;\r
12712 \r
12713                                 e = DOM.getParent(e.target, 'a', tb);\r
12714 \r
12715                                 if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))\r
12716                                         t.setColor(c);\r
12717 \r
12718                                 return false; // Prevent IE auto save warning\r
12719                         });\r
12720 \r
12721                         return w;\r
12722                 },\r
12723 \r
12724                 setColor : function(c) {\r
12725                         this.displayColor(c);\r
12726                         this.hideMenu();\r
12727                         this.settings.onselect(c);\r
12728                 },\r
12729                 \r
12730                 displayColor : function(c) {\r
12731                         var t = this;\r
12732 \r
12733                         DOM.setStyle(t.id + '_preview', 'backgroundColor', c);\r
12734 \r
12735                         t.value = c;\r
12736                 },\r
12737 \r
12738                 postRender : function() {\r
12739                         var t = this, id = t.id;\r
12740 \r
12741                         t.parent();\r
12742                         DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});\r
12743                         DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);\r
12744                 },\r
12745 \r
12746                 destroy : function() {\r
12747                         var self = this;\r
12748 \r
12749                         self.parent();\r
12750 \r
12751                         Event.clear(self.id + '_menu');\r
12752                         Event.clear(self.id + '_more');\r
12753                         DOM.remove(self.id + '_menu');\r
12754 \r
12755                         if (self.keyboardNav) {\r
12756                                 self.keyboardNav.destroy();\r
12757                         }\r
12758                 }\r
12759         });\r
12760 })(tinymce);\r
12761 \r
12762 (function(tinymce) {\r
12763 // Shorten class names\r
12764 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;\r
12765 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {\r
12766         renderHTML : function() {\r
12767                 var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;\r
12768 \r
12769                 h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');\r
12770                 //TODO: ACC test this out - adding a role = application for getting the landmarks working well.\r
12771                 h.push("<span role='application'>");\r
12772                 h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');\r
12773                 each(controls, function(toolbar) {\r
12774                         h.push(toolbar.renderHTML());\r
12775                 });\r
12776                 h.push("</span>");\r
12777                 h.push('</div>');\r
12778 \r
12779                 return h.join('');\r
12780         },\r
12781         \r
12782         focus : function() {\r
12783                 var t = this;\r
12784                 dom.get(t.id).focus();\r
12785         },\r
12786         \r
12787         postRender : function() {\r
12788                 var t = this, items = [];\r
12789 \r
12790                 each(t.controls, function(toolbar) {\r
12791                         each (toolbar.controls, function(control) {\r
12792                                 if (control.id) {\r
12793                                         items.push(control);\r
12794                                 }\r
12795                         });\r
12796                 });\r
12797 \r
12798                 t.keyNav = new tinymce.ui.KeyboardNavigation({\r
12799                         root: t.id,\r
12800                         items: items,\r
12801                         onCancel: function() {\r
12802                                 //Move focus if webkit so that navigation back will read the item.\r
12803                                 if (tinymce.isWebKit) {\r
12804                                         dom.get(t.editor.id+"_ifr").focus();\r
12805                                 }\r
12806                                 t.editor.focus();\r
12807                         },\r
12808                         excludeFromTabOrder: !t.settings.tab_focus_toolbar\r
12809                 });\r
12810         },\r
12811         \r
12812         destroy : function() {\r
12813                 var self = this;\r
12814 \r
12815                 self.parent();\r
12816                 self.keyNav.destroy();\r
12817                 Event.clear(self.id);\r
12818         }\r
12819 });\r
12820 })(tinymce);\r
12821 \r
12822 (function(tinymce) {\r
12823 // Shorten class names\r
12824 var dom = tinymce.DOM, each = tinymce.each;\r
12825 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {\r
12826         renderHTML : function() {\r
12827                 var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;\r
12828 \r
12829                 cl = t.controls;\r
12830                 for (i=0; i<cl.length; i++) {\r
12831                         // Get current control, prev control, next control and if the control is a list box or not\r
12832                         co = cl[i];\r
12833                         pr = cl[i - 1];\r
12834                         nx = cl[i + 1];\r
12835 \r
12836                         // Add toolbar start\r
12837                         if (i === 0) {\r
12838                                 c = 'mceToolbarStart';\r
12839 \r
12840                                 if (co.Button)\r
12841                                         c += ' mceToolbarStartButton';\r
12842                                 else if (co.SplitButton)\r
12843                                         c += ' mceToolbarStartSplitButton';\r
12844                                 else if (co.ListBox)\r
12845                                         c += ' mceToolbarStartListBox';\r
12846 \r
12847                                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
12848                         }\r
12849 \r
12850                         // Add toolbar end before list box and after the previous button\r
12851                         // This is to fix the o2k7 editor skins\r
12852                         if (pr && co.ListBox) {\r
12853                                 if (pr.Button || pr.SplitButton)\r
12854                                         h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));\r
12855                         }\r
12856 \r
12857                         // Render control HTML\r
12858 \r
12859                         // IE 8 quick fix, needed to propertly generate a hit area for anchors\r
12860                         if (dom.stdMode)\r
12861                                 h += '<td style="position: relative">' + co.renderHTML() + '</td>';\r
12862                         else\r
12863                                 h += '<td>' + co.renderHTML() + '</td>';\r
12864 \r
12865                         // Add toolbar start after list box and before the next button\r
12866                         // This is to fix the o2k7 editor skins\r
12867                         if (nx && co.ListBox) {\r
12868                                 if (nx.Button || nx.SplitButton)\r
12869                                         h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));\r
12870                         }\r
12871                 }\r
12872 \r
12873                 c = 'mceToolbarEnd';\r
12874 \r
12875                 if (co.Button)\r
12876                         c += ' mceToolbarEndButton';\r
12877                 else if (co.SplitButton)\r
12878                         c += ' mceToolbarEndSplitButton';\r
12879                 else if (co.ListBox)\r
12880                         c += ' mceToolbarEndListBox';\r
12881 \r
12882                 h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));\r
12883 \r
12884                 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
12885         }\r
12886 });\r
12887 })(tinymce);\r
12888 \r
12889 (function(tinymce) {\r
12890         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;\r
12891 \r
12892         tinymce.create('tinymce.AddOnManager', {\r
12893                 AddOnManager : function() {\r
12894                         var self = this;\r
12895 \r
12896                         self.items = [];\r
12897                         self.urls = {};\r
12898                         self.lookup = {};\r
12899                         self.onAdd = new Dispatcher(self);\r
12900                 },\r
12901 \r
12902                 get : function(n) {\r
12903                         if (this.lookup[n]) {\r
12904                                 return this.lookup[n].instance;\r
12905                         } else {\r
12906                                 return undefined;\r
12907                         }\r
12908                 },\r
12909 \r
12910                 dependencies : function(n) {\r
12911                         var result;\r
12912                         if (this.lookup[n]) {\r
12913                                 result = this.lookup[n].dependencies;\r
12914                         }\r
12915                         return result || [];\r
12916                 },\r
12917 \r
12918                 requireLangPack : function(n) {\r
12919                         var s = tinymce.settings;\r
12920 \r
12921                         if (s && s.language && s.language_load !== false)\r
12922                                 tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');\r
12923                 },\r
12924 \r
12925                 add : function(id, o, dependencies) {\r
12926                         this.items.push(o);\r
12927                         this.lookup[id] = {instance:o, dependencies:dependencies};\r
12928                         this.onAdd.dispatch(this, id, o);\r
12929 \r
12930                         return o;\r
12931                 },\r
12932                 createUrl: function(baseUrl, dep) {\r
12933                         if (typeof dep === "object") {\r
12934                                 return dep\r
12935                         } else {\r
12936                                 return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};\r
12937                         }\r
12938                 },\r
12939 \r
12940                 addComponents: function(pluginName, scripts) {\r
12941                         var pluginUrl = this.urls[pluginName];\r
12942                         tinymce.each(scripts, function(script){\r
12943                                 tinymce.ScriptLoader.add(pluginUrl+"/"+script); \r
12944                         });\r
12945                 },\r
12946 \r
12947                 load : function(n, u, cb, s) {\r
12948                         var t = this, url = u;\r
12949 \r
12950                         function loadDependencies() {\r
12951                                 var dependencies = t.dependencies(n);\r
12952                                 tinymce.each(dependencies, function(dep) {\r
12953                                         var newUrl = t.createUrl(u, dep);\r
12954                                         t.load(newUrl.resource, newUrl, undefined, undefined);\r
12955                                 });\r
12956                                 if (cb) {\r
12957                                         if (s) {\r
12958                                                 cb.call(s);\r
12959                                         } else {\r
12960                                                 cb.call(tinymce.ScriptLoader);\r
12961                                         }\r
12962                                 }\r
12963                         }\r
12964 \r
12965                         if (t.urls[n])\r
12966                                 return;\r
12967                         if (typeof u === "object")\r
12968                                 url = u.prefix + u.resource + u.suffix;\r
12969 \r
12970                         if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)\r
12971                                 url = tinymce.baseURL + '/' + url;\r
12972 \r
12973                         t.urls[n] = url.substring(0, url.lastIndexOf('/'));\r
12974 \r
12975                         if (t.lookup[n]) {\r
12976                                 loadDependencies();\r
12977                         } else {\r
12978                                 tinymce.ScriptLoader.add(url, loadDependencies, s);\r
12979                         }\r
12980                 }\r
12981         });\r
12982 \r
12983         // Create plugin and theme managers\r
12984         tinymce.PluginManager = new tinymce.AddOnManager();\r
12985         tinymce.ThemeManager = new tinymce.AddOnManager();\r
12986 }(tinymce));\r
12987 \r
12988 (function(tinymce) {\r
12989         // Shorten names\r
12990         var each = tinymce.each, extend = tinymce.extend,\r
12991                 DOM = tinymce.DOM, Event = tinymce.dom.Event,\r
12992                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
12993                 explode = tinymce.explode,\r
12994                 Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;\r
12995 \r
12996         // Setup some URLs where the editor API is located and where the document is\r
12997         tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');\r
12998         if (!/[\/\\]$/.test(tinymce.documentBaseURL))\r
12999                 tinymce.documentBaseURL += '/';\r
13000 \r
13001         tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);\r
13002 \r
13003         tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);\r
13004 \r
13005         // Add before unload listener\r
13006         // This was required since IE was leaking memory if you added and removed beforeunload listeners\r
13007         // with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event\r
13008         tinymce.onBeforeUnload = new Dispatcher(tinymce);\r
13009 \r
13010         // Must be on window or IE will leak if the editor is placed in frame or iframe\r
13011         Event.add(window, 'beforeunload', function(e) {\r
13012                 tinymce.onBeforeUnload.dispatch(tinymce, e);\r
13013         });\r
13014 \r
13015         tinymce.onAddEditor = new Dispatcher(tinymce);\r
13016 \r
13017         tinymce.onRemoveEditor = new Dispatcher(tinymce);\r
13018 \r
13019         tinymce.EditorManager = extend(tinymce, {\r
13020                 editors : [],\r
13021 \r
13022                 i18n : {},\r
13023 \r
13024                 activeEditor : null,\r
13025 \r
13026                 init : function(s) {\r
13027                         var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;\r
13028 \r
13029                         function createId(elm) {\r
13030                                 var id = elm.id;\r
13031         \r
13032                                 // Use element id, or unique name or generate a unique id\r
13033                                 if (!id) {\r
13034                                         id = elm.name;\r
13035         \r
13036                                         if (id && !DOM.get(id)) {\r
13037                                                 id = elm.name;\r
13038                                         } else {\r
13039                                                 // Generate unique name\r
13040                                                 id = DOM.uniqueId();\r
13041                                         }\r
13042 \r
13043                                         elm.setAttribute('id', id);\r
13044                                 }\r
13045 \r
13046                                 return id;\r
13047                         };\r
13048 \r
13049                         function execCallback(se, n, s) {\r
13050                                 var f = se[n];\r
13051 \r
13052                                 if (!f)\r
13053                                         return;\r
13054 \r
13055                                 if (tinymce.is(f, 'string')) {\r
13056                                         s = f.replace(/\.\w+$/, '');\r
13057                                         s = s ? tinymce.resolve(s) : 0;\r
13058                                         f = tinymce.resolve(f);\r
13059                                 }\r
13060 \r
13061                                 return f.apply(s || this, Array.prototype.slice.call(arguments, 2));\r
13062                         };\r
13063 \r
13064                         function hasClass(n, c) {\r
13065                                 return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);\r
13066                         };\r
13067 \r
13068                         t.settings = s;\r
13069 \r
13070                         // Legacy call\r
13071                         Event.bind(window, 'ready', function() {\r
13072                                 var l, co;\r
13073 \r
13074                                 execCallback(s, 'onpageload');\r
13075 \r
13076                                 switch (s.mode) {\r
13077                                         case "exact":\r
13078                                                 l = s.elements || '';\r
13079 \r
13080                                                 if(l.length > 0) {\r
13081                                                         each(explode(l), function(v) {\r
13082                                                                 if (DOM.get(v)) {\r
13083                                                                         ed = new tinymce.Editor(v, s);\r
13084                                                                         el.push(ed);\r
13085                                                                         ed.render(1);\r
13086                                                                 } else {\r
13087                                                                         each(document.forms, function(f) {\r
13088                                                                                 each(f.elements, function(e) {\r
13089                                                                                         if (e.name === v) {\r
13090                                                                                                 v = 'mce_editor_' + instanceCounter++;\r
13091                                                                                                 DOM.setAttrib(e, 'id', v);\r
13092 \r
13093                                                                                                 ed = new tinymce.Editor(v, s);\r
13094                                                                                                 el.push(ed);\r
13095                                                                                                 ed.render(1);\r
13096                                                                                         }\r
13097                                                                                 });\r
13098                                                                         });\r
13099                                                                 }\r
13100                                                         });\r
13101                                                 }\r
13102                                                 break;\r
13103 \r
13104                                         case "textareas":\r
13105                                         case "specific_textareas":\r
13106                                                 each(DOM.select('textarea'), function(elm) {\r
13107                                                         if (s.editor_deselector && hasClass(elm, s.editor_deselector))\r
13108                                                                 return;\r
13109 \r
13110                                                         if (!s.editor_selector || hasClass(elm, s.editor_selector)) {\r
13111                                                                 ed = new tinymce.Editor(createId(elm), s);\r
13112                                                                 el.push(ed);\r
13113                                                                 ed.render(1);\r
13114                                                         }\r
13115                                                 });\r
13116                                                 break;\r
13117                                         \r
13118                                         default:\r
13119                                                 if (s.types) {\r
13120                                                         // Process type specific selector\r
13121                                                         each(s.types, function(type) {\r
13122                                                                 each(DOM.select(type.selector), function(elm) {\r
13123                                                                         var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));\r
13124                                                                         el.push(editor);\r
13125                                                                         editor.render(1);\r
13126                                                                 });\r
13127                                                         });\r
13128                                                 } else if (s.selector) {\r
13129                                                         // Process global selector\r
13130                                                         each(DOM.select(s.selector), function(elm) {\r
13131                                                                 var editor = new tinymce.Editor(createId(elm), s);\r
13132                                                                 el.push(editor);\r
13133                                                                 editor.render(1);\r
13134                                                         });\r
13135                                                 }\r
13136                                 }\r
13137 \r
13138                                 // Call onInit when all editors are initialized\r
13139                                 if (s.oninit) {\r
13140                                         l = co = 0;\r
13141 \r
13142                                         each(el, function(ed) {\r
13143                                                 co++;\r
13144 \r
13145                                                 if (!ed.initialized) {\r
13146                                                         // Wait for it\r
13147                                                         ed.onInit.add(function() {\r
13148                                                                 l++;\r
13149 \r
13150                                                                 // All done\r
13151                                                                 if (l == co)\r
13152                                                                         execCallback(s, 'oninit');\r
13153                                                         });\r
13154                                                 } else\r
13155                                                         l++;\r
13156 \r
13157                                                 // All done\r
13158                                                 if (l == co)\r
13159                                                         execCallback(s, 'oninit');                                      \r
13160                                         });\r
13161                                 }\r
13162                         });\r
13163                 },\r
13164 \r
13165                 get : function(id) {\r
13166                         if (id === undef)\r
13167                                 return this.editors;\r
13168 \r
13169                         if (!this.editors.hasOwnProperty(id))\r
13170                                 return undef;\r
13171 \r
13172                         return this.editors[id];\r
13173                 },\r
13174 \r
13175                 getInstanceById : function(id) {\r
13176                         return this.get(id);\r
13177                 },\r
13178 \r
13179                 add : function(editor) {\r
13180                         var self = this, editors = self.editors;\r
13181 \r
13182                         // Add named and index editor instance\r
13183                         editors[editor.id] = editor;\r
13184                         editors.push(editor);\r
13185 \r
13186                         self._setActive(editor);\r
13187                         self.onAddEditor.dispatch(self, editor);\r
13188 \r
13189 \r
13190                         return editor;\r
13191                 },\r
13192 \r
13193                 remove : function(editor) {\r
13194                         var t = this, i, editors = t.editors;\r
13195 \r
13196                         // Not in the collection\r
13197                         if (!editors[editor.id])\r
13198                                 return null;\r
13199 \r
13200                         delete editors[editor.id];\r
13201 \r
13202                         for (i = 0; i < editors.length; i++) {\r
13203                                 if (editors[i] == editor) {\r
13204                                         editors.splice(i, 1);\r
13205                                         break;\r
13206                                 }\r
13207                         }\r
13208 \r
13209                         // Select another editor since the active one was removed\r
13210                         if (t.activeEditor == editor)\r
13211                                 t._setActive(editors[0]);\r
13212 \r
13213                         editor.destroy();\r
13214                         t.onRemoveEditor.dispatch(t, editor);\r
13215 \r
13216                         return editor;\r
13217                 },\r
13218 \r
13219                 execCommand : function(c, u, v) {\r
13220                         var t = this, ed = t.get(v), w;\r
13221 \r
13222                         function clr() {\r
13223                                 ed.destroy();\r
13224                                 w.detachEvent('onunload', clr);\r
13225                                 w = w.tinyMCE = w.tinymce = null; // IE leak\r
13226                         };\r
13227 \r
13228                         // Manager commands\r
13229                         switch (c) {\r
13230                                 case "mceFocus":\r
13231                                         ed.focus();\r
13232                                         return true;\r
13233 \r
13234                                 case "mceAddEditor":\r
13235                                 case "mceAddControl":\r
13236                                         if (!t.get(v))\r
13237                                                 new tinymce.Editor(v, t.settings).render();\r
13238 \r
13239                                         return true;\r
13240 \r
13241                                 case "mceAddFrameControl":\r
13242                                         w = v.window;\r
13243 \r
13244                                         // Add tinyMCE global instance and tinymce namespace to specified window\r
13245                                         w.tinyMCE = tinyMCE;\r
13246                                         w.tinymce = tinymce;\r
13247 \r
13248                                         tinymce.DOM.doc = w.document;\r
13249                                         tinymce.DOM.win = w;\r
13250 \r
13251                                         ed = new tinymce.Editor(v.element_id, v);\r
13252                                         ed.render();\r
13253 \r
13254                                         // Fix IE memory leaks\r
13255                                         if (tinymce.isIE) {\r
13256                                                 w.attachEvent('onunload', clr);\r
13257                                         }\r
13258 \r
13259                                         v.page_window = null;\r
13260 \r
13261                                         return true;\r
13262 \r
13263                                 case "mceRemoveEditor":\r
13264                                 case "mceRemoveControl":\r
13265                                         if (ed)\r
13266                                                 ed.remove();\r
13267 \r
13268                                         return true;\r
13269 \r
13270                                 case 'mceToggleEditor':\r
13271                                         if (!ed) {\r
13272                                                 t.execCommand('mceAddControl', 0, v);\r
13273                                                 return true;\r
13274                                         }\r
13275 \r
13276                                         if (ed.isHidden())\r
13277                                                 ed.show();\r
13278                                         else\r
13279                                                 ed.hide();\r
13280 \r
13281                                         return true;\r
13282                         }\r
13283 \r
13284                         // Run command on active editor\r
13285                         if (t.activeEditor)\r
13286                                 return t.activeEditor.execCommand(c, u, v);\r
13287 \r
13288                         return false;\r
13289                 },\r
13290 \r
13291                 execInstanceCommand : function(id, c, u, v) {\r
13292                         var ed = this.get(id);\r
13293 \r
13294                         if (ed)\r
13295                                 return ed.execCommand(c, u, v);\r
13296 \r
13297                         return false;\r
13298                 },\r
13299 \r
13300                 triggerSave : function() {\r
13301                         each(this.editors, function(e) {\r
13302                                 e.save();\r
13303                         });\r
13304                 },\r
13305 \r
13306                 addI18n : function(p, o) {\r
13307                         var lo, i18n = this.i18n;\r
13308 \r
13309                         if (!tinymce.is(p, 'string')) {\r
13310                                 each(p, function(o, lc) {\r
13311                                         each(o, function(o, g) {\r
13312                                                 each(o, function(o, k) {\r
13313                                                         if (g === 'common')\r
13314                                                                 i18n[lc + '.' + k] = o;\r
13315                                                         else\r
13316                                                                 i18n[lc + '.' + g + '.' + k] = o;\r
13317                                                 });\r
13318                                         });\r
13319                                 });\r
13320                         } else {\r
13321                                 each(o, function(o, k) {\r
13322                                         i18n[p + '.' + k] = o;\r
13323                                 });\r
13324                         }\r
13325                 },\r
13326 \r
13327                 // Private methods\r
13328 \r
13329                 _setActive : function(editor) {\r
13330                         this.selectedInstance = this.activeEditor = editor;\r
13331                 }\r
13332         });\r
13333 })(tinymce);\r
13334 \r
13335 (function(tinymce) {\r
13336         // Shorten these names\r
13337         var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,\r
13338                 each = tinymce.each, isGecko = tinymce.isGecko,\r
13339                 isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,\r
13340                 ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,\r
13341                 explode = tinymce.explode;\r
13342 \r
13343         tinymce.create('tinymce.Editor', {\r
13344                 Editor : function(id, settings) {\r
13345                         var self = this, TRUE = true;\r
13346 \r
13347                         self.settings = settings = extend({\r
13348                                 id : id,\r
13349                                 language : 'en',\r
13350                                 theme : 'advanced',\r
13351                                 skin : 'default',\r
13352                                 delta_width : 0,\r
13353                                 delta_height : 0,\r
13354                                 popup_css : '',\r
13355                                 plugins : '',\r
13356                                 document_base_url : tinymce.documentBaseURL,\r
13357                                 add_form_submit_trigger : TRUE,\r
13358                                 submit_patch : TRUE,\r
13359                                 add_unload_trigger : TRUE,\r
13360                                 convert_urls : TRUE,\r
13361                                 relative_urls : TRUE,\r
13362                                 remove_script_host : TRUE,\r
13363                                 table_inline_editing : false,\r
13364                                 object_resizing : TRUE,\r
13365                                 accessibility_focus : TRUE,\r
13366                                 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
13367                                 visual : TRUE,\r
13368                                 font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',\r
13369                                 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
13370                                 apply_source_formatting : TRUE,\r
13371                                 directionality : 'ltr',\r
13372                                 forced_root_block : 'p',\r
13373                                 hidden_input : TRUE,\r
13374                                 padd_empty_editor : TRUE,\r
13375                                 render_ui : TRUE,\r
13376                                 indentation : '30px',\r
13377                                 fix_table_elements : TRUE,\r
13378                                 inline_styles : TRUE,\r
13379                                 convert_fonts_to_spans : TRUE,\r
13380                                 indent : 'simple',\r
13381                                 indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',\r
13382                                 indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',\r
13383                                 validate : TRUE,\r
13384                                 entity_encoding : 'named',\r
13385                                 url_converter : self.convertURL,\r
13386                                 url_converter_scope : self,\r
13387                                 ie7_compat : TRUE\r
13388                         }, settings);\r
13389 \r
13390                         self.id = self.editorId = id;\r
13391 \r
13392                         self.isNotDirty = false;\r
13393 \r
13394                         self.plugins = {};\r
13395 \r
13396                         self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {\r
13397                                 base_uri : tinyMCE.baseURI\r
13398                         });\r
13399 \r
13400                         self.baseURI = tinymce.baseURI;\r
13401 \r
13402                         self.contentCSS = [];\r
13403 \r
13404                         self.contentStyles = [];\r
13405 \r
13406                         // Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic\r
13407                         self.setupEvents();\r
13408 \r
13409                         // Internal command handler objects\r
13410                         self.execCommands = {};\r
13411                         self.queryStateCommands = {};\r
13412                         self.queryValueCommands = {};\r
13413 \r
13414                         // Call setup\r
13415                         self.execCallback('setup', self);\r
13416                 },\r
13417 \r
13418                 render : function(nst) {\r
13419                         var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;\r
13420 \r
13421                         // Page is not loaded yet, wait for it\r
13422                         if (!Event.domLoaded) {\r
13423                                 Event.add(window, 'ready', function() {\r
13424                                         t.render();\r
13425                                 });\r
13426                                 return;\r
13427                         }\r
13428 \r
13429                         tinyMCE.settings = s;\r
13430 \r
13431                         // Element not found, then skip initialization\r
13432                         if (!t.getElement())\r
13433                                 return;\r
13434 \r
13435                         // Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff \r
13436                         // here since the browser says it has contentEditable support but there is no visible caret.\r
13437                         if (tinymce.isIDevice && !tinymce.isIOS5)\r
13438                                 return;\r
13439 \r
13440                         // Add hidden input for non input elements inside form elements\r
13441                         if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))\r
13442                                 DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);\r
13443 \r
13444                         // Hide target element early to prevent content flashing\r
13445                         if (!s.content_editable) {\r
13446                                 t.orgVisibility = t.getElement().style.visibility;\r
13447                                 t.getElement().style.visibility = 'hidden';\r
13448                         }\r
13449 \r
13450                         if (tinymce.WindowManager)\r
13451                                 t.windowManager = new tinymce.WindowManager(t);\r
13452 \r
13453                         if (s.encoding == 'xml') {\r
13454                                 t.onGetContent.add(function(ed, o) {\r
13455                                         if (o.save)\r
13456                                                 o.content = DOM.encode(o.content);\r
13457                                 });\r
13458                         }\r
13459 \r
13460                         if (s.add_form_submit_trigger) {\r
13461                                 t.onSubmit.addToTop(function() {\r
13462                                         if (t.initialized) {\r
13463                                                 t.save();\r
13464                                                 t.isNotDirty = 1;\r
13465                                         }\r
13466                                 });\r
13467                         }\r
13468 \r
13469                         if (s.add_unload_trigger) {\r
13470                                 t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {\r
13471                                         if (t.initialized && !t.destroyed && !t.isHidden())\r
13472                                                 t.save({format : 'raw', no_events : true});\r
13473                                 });\r
13474                         }\r
13475 \r
13476                         tinymce.addUnload(t.destroy, t);\r
13477 \r
13478                         if (s.submit_patch) {\r
13479                                 t.onBeforeRenderUI.add(function() {\r
13480                                         var n = t.getElement().form;\r
13481 \r
13482                                         if (!n)\r
13483                                                 return;\r
13484 \r
13485                                         // Already patched\r
13486                                         if (n._mceOldSubmit)\r
13487                                                 return;\r
13488 \r
13489                                         // Check page uses id="submit" or name="submit" for it's submit button\r
13490                                         if (!n.submit.nodeType && !n.submit.length) {\r
13491                                                 t.formElement = n;\r
13492                                                 n._mceOldSubmit = n.submit;\r
13493                                                 n.submit = function() {\r
13494                                                         // Save all instances\r
13495                                                         tinymce.triggerSave();\r
13496                                                         t.isNotDirty = 1;\r
13497 \r
13498                                                         return t.formElement._mceOldSubmit(t.formElement);\r
13499                                                 };\r
13500                                         }\r
13501 \r
13502                                         n = null;\r
13503                                 });\r
13504                         }\r
13505 \r
13506                         // Load scripts\r
13507                         function loadScripts() {\r
13508                                 if (s.language && s.language_load !== false)\r
13509                                         sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');\r
13510 \r
13511                                 if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])\r
13512                                         ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');\r
13513 \r
13514                                 each(explode(s.plugins), function(p) {\r
13515                                         if (p &&!PluginManager.urls[p]) {\r
13516                                                 if (p.charAt(0) == '-') {\r
13517                                                         p = p.substr(1, p.length);\r
13518                                                         var dependencies = PluginManager.dependencies(p);\r
13519                                                         each(dependencies, function(dep) {\r
13520                                                                 var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};\r
13521                                                                 dep = PluginManager.createUrl(defaultSettings, dep);\r
13522                                                                 PluginManager.load(dep.resource, dep);\r
13523                                                         });\r
13524                                                 } else {\r
13525                                                         // Skip safari plugin, since it is removed as of 3.3b1\r
13526                                                         if (p == 'safari') {\r
13527                                                                 return;\r
13528                                                         }\r
13529                                                         PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});\r
13530                                                 }\r
13531                                         }\r
13532                                 });\r
13533 \r
13534                                 // Init when que is loaded\r
13535                                 sl.loadQueue(function() {\r
13536                                         if (!t.removed)\r
13537                                                 t.init();\r
13538                                 });\r
13539                         };\r
13540 \r
13541                         loadScripts();\r
13542                 },\r
13543 \r
13544                 init : function() {\r
13545                         var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];\r
13546 \r
13547                         tinymce.add(t);\r
13548 \r
13549                         s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));\r
13550 \r
13551                         if (s.theme) {\r
13552                                 if (typeof s.theme != "function") {\r
13553                                         s.theme = s.theme.replace(/-/, '');\r
13554                                         o = ThemeManager.get(s.theme);\r
13555                                         t.theme = new o();\r
13556 \r
13557                                         if (t.theme.init)\r
13558                                                 t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));\r
13559                                 } else {\r
13560                                         t.theme = s.theme;\r
13561                                 }\r
13562                         }\r
13563 \r
13564                         function initPlugin(p) {\r
13565                                 var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;\r
13566                                 if (c && tinymce.inArray(initializedPlugins,p) === -1) {\r
13567                                         each(PluginManager.dependencies(p), function(dep){\r
13568                                                 initPlugin(dep);\r
13569                                         });\r
13570                                         po = new c(t, u);\r
13571 \r
13572                                         t.plugins[p] = po;\r
13573 \r
13574                                         if (po.init) {\r
13575                                                 po.init(t, u);\r
13576                                                 initializedPlugins.push(p);\r
13577                                         }\r
13578                                 }\r
13579                         }\r
13580                         \r
13581                         // Create all plugins\r
13582                         each(explode(s.plugins.replace(/\-/g, '')), initPlugin);\r
13583 \r
13584                         // Setup popup CSS path(s)\r
13585                         if (s.popup_css !== false) {\r
13586                                 if (s.popup_css)\r
13587                                         s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);\r
13588                                 else\r
13589                                         s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");\r
13590                         }\r
13591 \r
13592                         if (s.popup_css_add)\r
13593                                 s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);\r
13594 \r
13595                         t.controlManager = new tinymce.ControlManager(t);\r
13596 \r
13597                         // Enables users to override the control factory\r
13598                         t.onBeforeRenderUI.dispatch(t, t.controlManager);\r
13599 \r
13600                         // Measure box\r
13601                         if (s.render_ui && t.theme) {\r
13602                                 t.orgDisplay = e.style.display;\r
13603 \r
13604                                 if (typeof s.theme != "function") {\r
13605                                         w = s.width || e.style.width || e.offsetWidth;\r
13606                                         h = s.height || e.style.height || e.offsetHeight;\r
13607                                         mh = s.min_height || 100;\r
13608                                         re = /^[0-9\.]+(|px)$/i;\r
13609 \r
13610                                         if (re.test('' + w))\r
13611                                                 w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);\r
13612 \r
13613                                         if (re.test('' + h))\r
13614                                                 h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);\r
13615 \r
13616                                         // Render UI\r
13617                                         o = t.theme.renderUI({\r
13618                                                 targetNode : e,\r
13619                                                 width : w,\r
13620                                                 height : h,\r
13621                                                 deltaWidth : s.delta_width,\r
13622                                                 deltaHeight : s.delta_height\r
13623                                         });\r
13624 \r
13625                                         // Resize editor\r
13626                                         DOM.setStyles(o.sizeContainer || o.editorContainer, {\r
13627                                                 width : w,\r
13628                                                 height : h\r
13629                                         });\r
13630 \r
13631                                         h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');\r
13632                                         if (h < mh)\r
13633                                                 h = mh;\r
13634                                 } else {\r
13635                                         o = s.theme(t, e);\r
13636 \r
13637                                         // Convert element type to id:s\r
13638                                         if (o.editorContainer.nodeType) {\r
13639                                                 o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";\r
13640                                         }\r
13641 \r
13642                                         // Convert element type to id:s\r
13643                                         if (o.iframeContainer.nodeType) {\r
13644                                                 o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";\r
13645                                         }\r
13646 \r
13647                                         // Use specified iframe height or the targets offsetHeight\r
13648                                         h = o.iframeHeight || e.offsetHeight;\r
13649 \r
13650                                         // Store away the selection when it's changed to it can be restored later with a editor.focus() call\r
13651                                         if (isIE) {\r
13652                                                 t.onInit.add(function(ed) {\r
13653                                                         ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {\r
13654                                                                 ed.lastIERng = ed.selection.getRng();\r
13655                                                         });\r
13656                                                 });\r
13657                                         }\r
13658                                 }\r
13659 \r
13660                                 t.editorContainer = o.editorContainer;\r
13661                         }\r
13662 \r
13663                         // Load specified content CSS last\r
13664                         if (s.content_css) {\r
13665                                 each(explode(s.content_css), function(u) {\r
13666                                         t.contentCSS.push(t.documentBaseURI.toAbsolute(u));\r
13667                                 });\r
13668                         }\r
13669 \r
13670                         // Load specified content CSS last\r
13671                         if (s.content_style) {\r
13672                                 t.contentStyles.push(s.content_style);\r
13673                         }\r
13674 \r
13675                         // Content editable mode ends here\r
13676                         if (s.content_editable) {\r
13677                                 e = n = o = null; // Fix IE leak\r
13678                                 return t.initContentBody();\r
13679                         }\r
13680 \r
13681                         // User specified a document.domain value\r
13682                         if (document.domain && location.hostname != document.domain)\r
13683                                 tinymce.relaxedDomain = document.domain;\r
13684 \r
13685                         t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';\r
13686 \r
13687                         // We only need to override paths if we have to\r
13688                         // IE has a bug where it remove site absolute urls to relative ones if this is specified\r
13689                         if (s.document_base_url != tinymce.documentBaseURL)\r
13690                                 t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';\r
13691 \r
13692                         // IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.\r
13693                         if (tinymce.isIE8) {\r
13694                                 if (s.ie7_compat)\r
13695                                         t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';\r
13696                                 else\r
13697                                         t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';\r
13698                         }\r
13699 \r
13700                         t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';\r
13701 \r
13702                         // Load the CSS by injecting them into the HTML this will reduce "flicker"\r
13703                         for (i = 0; i < t.contentCSS.length; i++) {\r
13704                                 t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';\r
13705                         }\r
13706 \r
13707                         t.contentCSS = [];\r
13708 \r
13709                         bi = s.body_id || 'tinymce';\r
13710                         if (bi.indexOf('=') != -1) {\r
13711                                 bi = t.getParam('body_id', '', 'hash');\r
13712                                 bi = bi[t.id] || bi;\r
13713                         }\r
13714 \r
13715                         bc = s.body_class || '';\r
13716                         if (bc.indexOf('=') != -1) {\r
13717                                 bc = t.getParam('body_class', '', 'hash');\r
13718                                 bc = bc[t.id] || '';\r
13719                         }\r
13720 \r
13721                         t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';\r
13722 \r
13723                         // Domain relaxing enabled, then set document domain\r
13724                         if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {\r
13725                                 // We need to write the contents here in IE since multiple writes messes up refresh button and back button\r
13726                                 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
13727                         }\r
13728 \r
13729                         // Create iframe\r
13730                         // TODO: ACC add the appropriate description on this.\r
13731                         n = DOM.add(o.iframeContainer, 'iframe', { \r
13732                                 id : t.id + "_ifr",\r
13733                                 src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7\r
13734                                 frameBorder : '0',\r
13735                                 allowTransparency : "true",\r
13736                                 title : s.aria_label,\r
13737                                 style : {\r
13738                                         width : '100%',\r
13739                                         height : h,\r
13740                                         display : 'block' // Important for Gecko to render the iframe correctly\r
13741                                 }\r
13742                         });\r
13743 \r
13744                         t.contentAreaContainer = o.iframeContainer;\r
13745 \r
13746                         if (o.editorContainer) {\r
13747                                 DOM.get(o.editorContainer).style.display = t.orgDisplay;\r
13748                         }\r
13749 \r
13750                         // Restore visibility on target element\r
13751                         e.style.visibility = t.orgVisibility;\r
13752 \r
13753                         DOM.get(t.id).style.display = 'none';\r
13754                         DOM.setAttrib(t.id, 'aria-hidden', true);\r
13755 \r
13756                         if (!tinymce.relaxedDomain || !u)\r
13757                                 t.initContentBody();\r
13758 \r
13759                         e = n = o = null; // Cleanup\r
13760                 },\r
13761 \r
13762                 initContentBody : function() {\r
13763                         var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;\r
13764 \r
13765                         // Setup iframe body\r
13766                         if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {\r
13767                                 doc.open();\r
13768                                 doc.write(self.iframeHTML);\r
13769                                 doc.close();\r
13770 \r
13771                                 if (tinymce.relaxedDomain)\r
13772                                         doc.domain = tinymce.relaxedDomain;\r
13773                         }\r
13774 \r
13775                         if (settings.content_editable) {\r
13776                                 DOM.addClass(targetElm, 'mceContentBody');\r
13777                                 self.contentDocument = doc = settings.content_document || document;\r
13778                                 self.contentWindow = settings.content_window || window;\r
13779                                 self.bodyElement = targetElm;\r
13780 \r
13781                                 // Prevent leak in IE\r
13782                                 settings.content_document = settings.content_window = null;\r
13783                         }\r
13784 \r
13785                         // It will not steal focus while setting contentEditable\r
13786                         body = self.getBody();\r
13787                         body.disabled = true;\r
13788 \r
13789                         if (!settings.readonly)\r
13790                                 body.contentEditable = self.getParam('content_editable_state', true);\r
13791 \r
13792                         body.disabled = false;\r
13793 \r
13794                         self.schema = new tinymce.html.Schema(settings);\r
13795 \r
13796                         self.dom = new tinymce.dom.DOMUtils(doc, {\r
13797                                 keep_values : true,\r
13798                                 url_converter : self.convertURL,\r
13799                                 url_converter_scope : self,\r
13800                                 hex_colors : settings.force_hex_style_colors,\r
13801                                 class_filter : settings.class_filter,\r
13802                                 update_styles : true,\r
13803                                 root_element : settings.content_editable ? self.id : null,\r
13804                                 schema : self.schema\r
13805                         });\r
13806 \r
13807                         self.parser = new tinymce.html.DomParser(settings, self.schema);\r
13808 \r
13809                         // Convert src and href into data-mce-src, data-mce-href and data-mce-style\r
13810                         self.parser.addAttributeFilter('src,href,style', function(nodes, name) {\r
13811                                 var i = nodes.length, node, dom = self.dom, value, internalName;\r
13812 \r
13813                                 while (i--) {\r
13814                                         node = nodes[i];\r
13815                                         value = node.attr(name);\r
13816                                         internalName = 'data-mce-' + name;\r
13817 \r
13818                                         // Add internal attribute if we need to we don't on a refresh of the document\r
13819                                         if (!node.attributes.map[internalName]) {       \r
13820                                                 if (name === "style")\r
13821                                                         node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));\r
13822                                                 else\r
13823                                                         node.attr(internalName, self.convertURL(value, name, node.name));\r
13824                                         }\r
13825                                 }\r
13826                         });\r
13827 \r
13828                         // Keep scripts from executing\r
13829                         self.parser.addNodeFilter('script', function(nodes, name) {\r
13830                                 var i = nodes.length, node;\r
13831 \r
13832                                 while (i--) {\r
13833                                         node = nodes[i];\r
13834                                         node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));\r
13835                                 }\r
13836                         });\r
13837 \r
13838                         self.parser.addNodeFilter('#cdata', function(nodes, name) {\r
13839                                 var i = nodes.length, node;\r
13840 \r
13841                                 while (i--) {\r
13842                                         node = nodes[i];\r
13843                                         node.type = 8;\r
13844                                         node.name = '#comment';\r
13845                                         node.value = '[CDATA[' + node.value + ']]';\r
13846                                 }\r
13847                         });\r
13848 \r
13849                         self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {\r
13850                                 var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();\r
13851 \r
13852                                 while (i--) {\r
13853                                         node = nodes[i];\r
13854 \r
13855                                         if (node.isEmpty(nonEmptyElements))\r
13856                                                 node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;\r
13857                                 }\r
13858                         });\r
13859 \r
13860                         self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);\r
13861 \r
13862                         self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);\r
13863 \r
13864                         self.formatter = new tinymce.Formatter(self);\r
13865 \r
13866                         self.undoManager = new tinymce.UndoManager(self);\r
13867 \r
13868                         self.forceBlocks = new tinymce.ForceBlocks(self);\r
13869                         self.enterKey = new tinymce.EnterKey(self);\r
13870                         self.editorCommands = new tinymce.EditorCommands(self);\r
13871 \r
13872                         self.onExecCommand.add(function(editor, command) {\r
13873                                 // Don't refresh the select lists until caret move\r
13874                                 if (!/^(FontName|FontSize)$/.test(command))\r
13875                                         self.nodeChanged();\r
13876                         });\r
13877 \r
13878                         // Pass through\r
13879                         self.serializer.onPreProcess.add(function(se, o) {\r
13880                                 return self.onPreProcess.dispatch(self, o, se);\r
13881                         });\r
13882 \r
13883                         self.serializer.onPostProcess.add(function(se, o) {\r
13884                                 return self.onPostProcess.dispatch(self, o, se);\r
13885                         });\r
13886 \r
13887                         self.onPreInit.dispatch(self);\r
13888 \r
13889                         if (!settings.browser_spellcheck && !settings.gecko_spellcheck)\r
13890                                 doc.body.spellcheck = false;\r
13891 \r
13892                         if (!settings.readonly) {\r
13893                                 self.bindNativeEvents();\r
13894                         }\r
13895 \r
13896                         self.controlManager.onPostRender.dispatch(self, self.controlManager);\r
13897                         self.onPostRender.dispatch(self);\r
13898 \r
13899                         self.quirks = tinymce.util.Quirks(self);\r
13900 \r
13901                         if (settings.directionality)\r
13902                                 body.dir = settings.directionality;\r
13903 \r
13904                         if (settings.nowrap)\r
13905                                 body.style.whiteSpace = "nowrap";\r
13906 \r
13907                         if (settings.protect) {\r
13908                                 self.onBeforeSetContent.add(function(ed, o) {\r
13909                                         each(settings.protect, function(pattern) {\r
13910                                                 o.content = o.content.replace(pattern, function(str) {\r
13911                                                         return '<!--mce:protected ' + escape(str) + '-->';\r
13912                                                 });\r
13913                                         });\r
13914                                 });\r
13915                         }\r
13916 \r
13917                         // Add visual aids when new contents is added\r
13918                         self.onSetContent.add(function() {\r
13919                                 self.addVisual(self.getBody());\r
13920                         });\r
13921 \r
13922                         // Remove empty contents\r
13923                         if (settings.padd_empty_editor) {\r
13924                                 self.onPostProcess.add(function(ed, o) {\r
13925                                         o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');\r
13926                                 });\r
13927                         }\r
13928 \r
13929                         self.load({initial : true, format : 'html'});\r
13930                         self.startContent = self.getContent({format : 'raw'});\r
13931 \r
13932                         self.initialized = true;\r
13933 \r
13934                         self.onInit.dispatch(self);\r
13935                         self.execCallback('setupcontent_callback', self.id, body, doc);\r
13936                         self.execCallback('init_instance_callback', self);\r
13937                         self.focus(true);\r
13938                         self.nodeChanged({initial : true});\r
13939 \r
13940                         // Add editor specific CSS styles\r
13941                         if (self.contentStyles.length > 0) {\r
13942                                 contentCssText = '';\r
13943 \r
13944                                 each(self.contentStyles, function(style) {\r
13945                                         contentCssText += style + "\r\n";\r
13946                                 });\r
13947 \r
13948                                 self.dom.addStyle(contentCssText);\r
13949                         }\r
13950 \r
13951                         // Load specified content CSS last\r
13952                         each(self.contentCSS, function(url) {\r
13953                                 self.dom.loadCSS(url);\r
13954                         });\r
13955 \r
13956                         // Handle auto focus\r
13957                         if (settings.auto_focus) {\r
13958                                 setTimeout(function () {\r
13959                                         var ed = tinymce.get(settings.auto_focus);\r
13960 \r
13961                                         ed.selection.select(ed.getBody(), 1);\r
13962                                         ed.selection.collapse(1);\r
13963                                         ed.getBody().focus();\r
13964                                         ed.getWin().focus();\r
13965                                 }, 100);\r
13966                         }\r
13967 \r
13968                         // Clean up references for IE\r
13969                         targetElm = doc = body = null;\r
13970                 },\r
13971 \r
13972                 focus : function(skip_focus) {\r
13973                         var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;\r
13974 \r
13975                         if (!skip_focus) {\r
13976                                 if (self.lastIERng) {\r
13977                                         selection.setRng(self.lastIERng);\r
13978                                 }\r
13979 \r
13980                                 // Get selected control element\r
13981                                 ieRng = selection.getRng();\r
13982                                 if (ieRng.item) {\r
13983                                         controlElm = ieRng.item(0);\r
13984                                 }\r
13985 \r
13986                                 self._refreshContentEditable();\r
13987 \r
13988                                 // Focus the window iframe\r
13989                                 if (!contentEditable) {\r
13990                                         self.getWin().focus();\r
13991                                 }\r
13992 \r
13993                                 // Focus the body as well since it's contentEditable\r
13994                                 if (tinymce.isGecko || contentEditable) {\r
13995                                         body = self.getBody();\r
13996 \r
13997                                         // Check for setActive since it doesn't scroll to the element\r
13998                                         if (body.setActive) {\r
13999                                                 body.setActive();\r
14000                                         } else {\r
14001                                                 body.focus();\r
14002                                         }\r
14003 \r
14004                                         if (contentEditable) {\r
14005                                                 selection.normalize();\r
14006                                         }\r
14007                                 }\r
14008 \r
14009                                 // Restore selected control element\r
14010                                 // This is needed when for example an image is selected within a\r
14011                                 // layer a call to focus will then remove the control selection\r
14012                                 if (controlElm && controlElm.ownerDocument == doc) {\r
14013                                         ieRng = doc.body.createControlRange();\r
14014                                         ieRng.addElement(controlElm);\r
14015                                         ieRng.select();\r
14016                                 }\r
14017                         }\r
14018 \r
14019                         if (tinymce.activeEditor != self) {\r
14020                                 if ((oed = tinymce.activeEditor) != null)\r
14021                                         oed.onDeactivate.dispatch(oed, self);\r
14022 \r
14023                                 self.onActivate.dispatch(self, oed);\r
14024                         }\r
14025 \r
14026                         tinymce._setActive(self);\r
14027                 },\r
14028 \r
14029                 execCallback : function(n) {\r
14030                         var t = this, f = t.settings[n], s;\r
14031 \r
14032                         if (!f)\r
14033                                 return;\r
14034 \r
14035                         // Look through lookup\r
14036                         if (t.callbackLookup && (s = t.callbackLookup[n])) {\r
14037                                 f = s.func;\r
14038                                 s = s.scope;\r
14039                         }\r
14040 \r
14041                         if (is(f, 'string')) {\r
14042                                 s = f.replace(/\.\w+$/, '');\r
14043                                 s = s ? tinymce.resolve(s) : 0;\r
14044                                 f = tinymce.resolve(f);\r
14045                                 t.callbackLookup = t.callbackLookup || {};\r
14046                                 t.callbackLookup[n] = {func : f, scope : s};\r
14047                         }\r
14048 \r
14049                         return f.apply(s || t, Array.prototype.slice.call(arguments, 1));\r
14050                 },\r
14051 \r
14052                 translate : function(s) {\r
14053                         var c = this.settings.language || 'en', i18n = tinymce.i18n;\r
14054 \r
14055                         if (!s)\r
14056                                 return '';\r
14057 \r
14058                         return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {\r
14059                                 return i18n[c + '.' + b] || '{#' + b + '}';\r
14060                         });\r
14061                 },\r
14062 \r
14063                 getLang : function(n, dv) {\r
14064                         return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');\r
14065                 },\r
14066 \r
14067                 getParam : function(n, dv, ty) {\r
14068                         var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;\r
14069 \r
14070                         if (ty === 'hash') {\r
14071                                 o = {};\r
14072 \r
14073                                 if (is(v, 'string')) {\r
14074                                         each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {\r
14075                                                 v = v.split('=');\r
14076 \r
14077                                                 if (v.length > 1)\r
14078                                                         o[tr(v[0])] = tr(v[1]);\r
14079                                                 else\r
14080                                                         o[tr(v[0])] = tr(v);\r
14081                                         });\r
14082                                 } else\r
14083                                         o = v;\r
14084 \r
14085                                 return o;\r
14086                         }\r
14087 \r
14088                         return v;\r
14089                 },\r
14090 \r
14091                 nodeChanged : function(o) {\r
14092                         var self = this, selection = self.selection, node;\r
14093 \r
14094                         // Fix for bug #1896577 it seems that this can not be fired while the editor is loading\r
14095                         if (self.initialized) {\r
14096                                 o = o || {};\r
14097 \r
14098                                 // Get start node\r
14099                                 node = selection.getStart() || self.getBody();\r
14100                                 node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state\r
14101 \r
14102                                 // Get parents and add them to object\r
14103                                 o.parents = [];\r
14104                                 self.dom.getParent(node, function(node) {\r
14105                                         if (node.nodeName == 'BODY')\r
14106                                                 return true;\r
14107 \r
14108                                         o.parents.push(node);\r
14109                                 });\r
14110 \r
14111                                 self.onNodeChange.dispatch(\r
14112                                         self,\r
14113                                         o ? o.controlManager || self.controlManager : self.controlManager,\r
14114                                         node,\r
14115                                         selection.isCollapsed(),\r
14116                                         o\r
14117                                 );\r
14118                         }\r
14119                 },\r
14120 \r
14121                 addButton : function(name, settings) {\r
14122                         var self = this;\r
14123 \r
14124                         self.buttons = self.buttons || {};\r
14125                         self.buttons[name] = settings;\r
14126                 },\r
14127 \r
14128                 addCommand : function(name, callback, scope) {\r
14129                         this.execCommands[name] = {func : callback, scope : scope || this};\r
14130                 },\r
14131 \r
14132                 addQueryStateHandler : function(name, callback, scope) {\r
14133                         this.queryStateCommands[name] = {func : callback, scope : scope || this};\r
14134                 },\r
14135 \r
14136                 addQueryValueHandler : function(name, callback, scope) {\r
14137                         this.queryValueCommands[name] = {func : callback, scope : scope || this};\r
14138                 },\r
14139 \r
14140                 addShortcut : function(pa, desc, cmd_func, sc) {\r
14141                         var t = this, c;\r
14142 \r
14143                         if (t.settings.custom_shortcuts === false)\r
14144                                 return false;\r
14145 \r
14146                         t.shortcuts = t.shortcuts || {};\r
14147 \r
14148                         if (is(cmd_func, 'string')) {\r
14149                                 c = cmd_func;\r
14150 \r
14151                                 cmd_func = function() {\r
14152                                         t.execCommand(c, false, null);\r
14153                                 };\r
14154                         }\r
14155 \r
14156                         if (is(cmd_func, 'object')) {\r
14157                                 c = cmd_func;\r
14158 \r
14159                                 cmd_func = function() {\r
14160                                         t.execCommand(c[0], c[1], c[2]);\r
14161                                 };\r
14162                         }\r
14163 \r
14164                         each(explode(pa), function(pa) {\r
14165                                 var o = {\r
14166                                         func : cmd_func,\r
14167                                         scope : sc || this,\r
14168                                         desc : t.translate(desc),\r
14169                                         alt : false,\r
14170                                         ctrl : false,\r
14171                                         shift : false\r
14172                                 };\r
14173 \r
14174                                 each(explode(pa, '+'), function(v) {\r
14175                                         switch (v) {\r
14176                                                 case 'alt':\r
14177                                                 case 'ctrl':\r
14178                                                 case 'shift':\r
14179                                                         o[v] = true;\r
14180                                                         break;\r
14181 \r
14182                                                 default:\r
14183                                                         o.charCode = v.charCodeAt(0);\r
14184                                                         o.keyCode = v.toUpperCase().charCodeAt(0);\r
14185                                         }\r
14186                                 });\r
14187 \r
14188                                 t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;\r
14189                         });\r
14190 \r
14191                         return true;\r
14192                 },\r
14193 \r
14194                 execCommand : function(cmd, ui, val, a) {\r
14195                         var t = this, s = 0, o, st;\r
14196 \r
14197                         if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))\r
14198                                 t.focus();\r
14199 \r
14200                         a = extend({}, a);\r
14201                         t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);\r
14202                         if (a.terminate)\r
14203                                 return false;\r
14204 \r
14205                         // Command callback\r
14206                         if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {\r
14207                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14208                                 return true;\r
14209                         }\r
14210 \r
14211                         // Registred commands\r
14212                         if (o = t.execCommands[cmd]) {\r
14213                                 st = o.func.call(o.scope, ui, val);\r
14214 \r
14215                                 // Fall through on true\r
14216                                 if (st !== true) {\r
14217                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14218                                         return st;\r
14219                                 }\r
14220                         }\r
14221 \r
14222                         // Plugin commands\r
14223                         each(t.plugins, function(p) {\r
14224                                 if (p.execCommand && p.execCommand(cmd, ui, val)) {\r
14225                                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14226                                         s = 1;\r
14227                                         return false;\r
14228                                 }\r
14229                         });\r
14230 \r
14231                         if (s)\r
14232                                 return true;\r
14233 \r
14234                         // Theme commands\r
14235                         if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {\r
14236                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14237                                 return true;\r
14238                         }\r
14239 \r
14240                         // Editor commands\r
14241                         if (t.editorCommands.execCommand(cmd, ui, val)) {\r
14242                                 t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14243                                 return true;\r
14244                         }\r
14245 \r
14246                         // Browser commands\r
14247                         t.getDoc().execCommand(cmd, ui, val);\r
14248                         t.onExecCommand.dispatch(t, cmd, ui, val, a);\r
14249                 },\r
14250 \r
14251                 queryCommandState : function(cmd) {\r
14252                         var t = this, o, s;\r
14253 \r
14254                         // Is hidden then return undefined\r
14255                         if (t._isHidden())\r
14256                                 return;\r
14257 \r
14258                         // Registred commands\r
14259                         if (o = t.queryStateCommands[cmd]) {\r
14260                                 s = o.func.call(o.scope);\r
14261 \r
14262                                 // Fall though on true\r
14263                                 if (s !== true)\r
14264                                         return s;\r
14265                         }\r
14266 \r
14267                         // Registred commands\r
14268                         o = t.editorCommands.queryCommandState(cmd);\r
14269                         if (o !== -1)\r
14270                                 return o;\r
14271 \r
14272                         // Browser commands\r
14273                         try {\r
14274                                 return this.getDoc().queryCommandState(cmd);\r
14275                         } catch (ex) {\r
14276                                 // Fails sometimes see bug: 1896577\r
14277                         }\r
14278                 },\r
14279 \r
14280                 queryCommandValue : function(c) {\r
14281                         var t = this, o, s;\r
14282 \r
14283                         // Is hidden then return undefined\r
14284                         if (t._isHidden())\r
14285                                 return;\r
14286 \r
14287                         // Registred commands\r
14288                         if (o = t.queryValueCommands[c]) {\r
14289                                 s = o.func.call(o.scope);\r
14290 \r
14291                                 // Fall though on true\r
14292                                 if (s !== true)\r
14293                                         return s;\r
14294                         }\r
14295 \r
14296                         // Registred commands\r
14297                         o = t.editorCommands.queryCommandValue(c);\r
14298                         if (is(o))\r
14299                                 return o;\r
14300 \r
14301                         // Browser commands\r
14302                         try {\r
14303                                 return this.getDoc().queryCommandValue(c);\r
14304                         } catch (ex) {\r
14305                                 // Fails sometimes see bug: 1896577\r
14306                         }\r
14307                 },\r
14308 \r
14309                 show : function() {\r
14310                         var self = this;\r
14311 \r
14312                         DOM.show(self.getContainer());\r
14313                         DOM.hide(self.id);\r
14314                         self.load();\r
14315                 },\r
14316 \r
14317                 hide : function() {\r
14318                         var self = this, doc = self.getDoc();\r
14319 \r
14320                         // Fixed bug where IE has a blinking cursor left from the editor\r
14321                         if (isIE && doc)\r
14322                                 doc.execCommand('SelectAll');\r
14323 \r
14324                         // We must save before we hide so Safari doesn't crash\r
14325                         self.save();\r
14326 \r
14327                         // defer the call to hide to prevent an IE9 crash #4921\r
14328                         DOM.hide(self.getContainer());\r
14329                         DOM.setStyle(self.id, 'display', self.orgDisplay);\r
14330                 },\r
14331 \r
14332                 isHidden : function() {\r
14333                         return !DOM.isHidden(this.id);\r
14334                 },\r
14335 \r
14336                 setProgressState : function(b, ti, o) {\r
14337                         this.onSetProgressState.dispatch(this, b, ti, o);\r
14338 \r
14339                         return b;\r
14340                 },\r
14341 \r
14342                 load : function(o) {\r
14343                         var t = this, e = t.getElement(), h;\r
14344 \r
14345                         if (e) {\r
14346                                 o = o || {};\r
14347                                 o.load = true;\r
14348 \r
14349                                 // Double encode existing entities in the value\r
14350                                 h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);\r
14351                                 o.element = e;\r
14352 \r
14353                                 if (!o.no_events)\r
14354                                         t.onLoadContent.dispatch(t, o);\r
14355 \r
14356                                 o.element = e = null;\r
14357 \r
14358                                 return h;\r
14359                         }\r
14360                 },\r
14361 \r
14362                 save : function(o) {\r
14363                         var t = this, e = t.getElement(), h, f;\r
14364 \r
14365                         if (!e || !t.initialized)\r
14366                                 return;\r
14367 \r
14368                         o = o || {};\r
14369                         o.save = true;\r
14370 \r
14371                         o.element = e;\r
14372                         h = o.content = t.getContent(o);\r
14373 \r
14374                         if (!o.no_events)\r
14375                                 t.onSaveContent.dispatch(t, o);\r
14376 \r
14377                         h = o.content;\r
14378 \r
14379                         if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {\r
14380                                 e.innerHTML = h;\r
14381 \r
14382                                 // Update hidden form element\r
14383                                 if (f = DOM.getParent(t.id, 'form')) {\r
14384                                         each(f.elements, function(e) {\r
14385                                                 if (e.name == t.id) {\r
14386                                                         e.value = h;\r
14387                                                         return false;\r
14388                                                 }\r
14389                                         });\r
14390                                 }\r
14391                         } else\r
14392                                 e.value = h;\r
14393 \r
14394                         o.element = e = null;\r
14395 \r
14396                         return h;\r
14397                 },\r
14398 \r
14399                 setContent : function(content, args) {\r
14400                         var self = this, rootNode, body = self.getBody(), forcedRootBlockName;\r
14401 \r
14402                         // Setup args object\r
14403                         args = args || {};\r
14404                         args.format = args.format || 'html';\r
14405                         args.set = true;\r
14406                         args.content = content;\r
14407 \r
14408                         // Do preprocessing\r
14409                         if (!args.no_events)\r
14410                                 self.onBeforeSetContent.dispatch(self, args);\r
14411 \r
14412                         content = args.content;\r
14413 \r
14414                         // Padd empty content in Gecko and Safari. Commands will otherwise fail on the content\r
14415                         // It will also be impossible to place the caret in the editor unless there is a BR element present\r
14416                         if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {\r
14417                                 forcedRootBlockName = self.settings.forced_root_block;\r
14418                                 if (forcedRootBlockName)\r
14419                                         content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';\r
14420                                 else\r
14421                                         content = '<br data-mce-bogus="1">';\r
14422 \r
14423                                 body.innerHTML = content;\r
14424                                 self.selection.select(body, true);\r
14425                                 self.selection.collapse(true);\r
14426                                 return;\r
14427                         }\r
14428 \r
14429                         // Parse and serialize the html\r
14430                         if (args.format !== 'raw') {\r
14431                                 content = new tinymce.html.Serializer({}, self.schema).serialize(\r
14432                                         self.parser.parse(content)\r
14433                                 );\r
14434                         }\r
14435 \r
14436                         // Set the new cleaned contents to the editor\r
14437                         args.content = tinymce.trim(content);\r
14438                         self.dom.setHTML(body, args.content);\r
14439 \r
14440                         // Do post processing\r
14441                         if (!args.no_events)\r
14442                                 self.onSetContent.dispatch(self, args);\r
14443 \r
14444                         // Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise\r
14445                         if (!self.settings.content_editable || document.activeElement === self.getBody()) {\r
14446                                 self.selection.normalize();\r
14447                         }\r
14448 \r
14449                         return args.content;\r
14450                 },\r
14451 \r
14452                 getContent : function(args) {\r
14453                         var self = this, content, body = self.getBody();\r
14454 \r
14455                         // Setup args object\r
14456                         args = args || {};\r
14457                         args.format = args.format || 'html';\r
14458                         args.get = true;\r
14459                         args.getInner = true;\r
14460 \r
14461                         // Do preprocessing\r
14462                         if (!args.no_events)\r
14463                                 self.onBeforeGetContent.dispatch(self, args);\r
14464 \r
14465                         // Get raw contents or by default the cleaned contents\r
14466                         if (args.format == 'raw')\r
14467                                 content = body.innerHTML;\r
14468                         else if (args.format == 'text')\r
14469                                 content = body.innerText || body.textContent;\r
14470                         else\r
14471                                 content = self.serializer.serialize(body, args);\r
14472 \r
14473                         // Trim whitespace in beginning/end of HTML\r
14474                         if (args.format != 'text') {\r
14475                                 args.content = tinymce.trim(content);\r
14476                         } else {\r
14477                                 args.content = content;\r
14478                         }\r
14479 \r
14480                         // Do post processing\r
14481                         if (!args.no_events)\r
14482                                 self.onGetContent.dispatch(self, args);\r
14483 \r
14484                         return args.content;\r
14485                 },\r
14486 \r
14487                 isDirty : function() {\r
14488                         var self = this;\r
14489 \r
14490                         return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;\r
14491                 },\r
14492 \r
14493                 getContainer : function() {\r
14494                         var self = this;\r
14495 \r
14496                         if (!self.container)\r
14497                                 self.container = DOM.get(self.editorContainer || self.id + '_parent');\r
14498 \r
14499                         return self.container;\r
14500                 },\r
14501 \r
14502                 getContentAreaContainer : function() {\r
14503                         return this.contentAreaContainer;\r
14504                 },\r
14505 \r
14506                 getElement : function() {\r
14507                         return DOM.get(this.settings.content_element || this.id);\r
14508                 },\r
14509 \r
14510                 getWin : function() {\r
14511                         var self = this, elm;\r
14512 \r
14513                         if (!self.contentWindow) {\r
14514                                 elm = DOM.get(self.id + "_ifr");\r
14515 \r
14516                                 if (elm)\r
14517                                         self.contentWindow = elm.contentWindow;\r
14518                         }\r
14519 \r
14520                         return self.contentWindow;\r
14521                 },\r
14522 \r
14523                 getDoc : function() {\r
14524                         var self = this, win;\r
14525 \r
14526                         if (!self.contentDocument) {\r
14527                                 win = self.getWin();\r
14528 \r
14529                                 if (win)\r
14530                                         self.contentDocument = win.document;\r
14531                         }\r
14532 \r
14533                         return self.contentDocument;\r
14534                 },\r
14535 \r
14536                 getBody : function() {\r
14537                         return this.bodyElement || this.getDoc().body;\r
14538                 },\r
14539 \r
14540                 convertURL : function(url, name, elm) {\r
14541                         var self = this, settings = self.settings;\r
14542 \r
14543                         // Use callback instead\r
14544                         if (settings.urlconverter_callback)\r
14545                                 return self.execCallback('urlconverter_callback', url, elm, true, name);\r
14546 \r
14547                         // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs\r
14548                         if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)\r
14549                                 return url;\r
14550 \r
14551                         // Convert to relative\r
14552                         if (settings.relative_urls)\r
14553                                 return self.documentBaseURI.toRelative(url);\r
14554 \r
14555                         // Convert to absolute\r
14556                         url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);\r
14557 \r
14558                         return url;\r
14559                 },\r
14560 \r
14561                 addVisual : function(elm) {\r
14562                         var self = this, settings = self.settings, dom = self.dom, cls;\r
14563 \r
14564                         elm = elm || self.getBody();\r
14565 \r
14566                         if (!is(self.hasVisual))\r
14567                                 self.hasVisual = settings.visual;\r
14568 \r
14569                         each(dom.select('table,a', elm), function(elm) {\r
14570                                 var value;\r
14571 \r
14572                                 switch (elm.nodeName) {\r
14573                                         case 'TABLE':\r
14574                                                 cls = settings.visual_table_class || 'mceItemTable';\r
14575                                                 value = dom.getAttrib(elm, 'border');\r
14576 \r
14577                                                 if (!value || value == '0') {\r
14578                                                         if (self.hasVisual)\r
14579                                                                 dom.addClass(elm, cls);\r
14580                                                         else\r
14581                                                                 dom.removeClass(elm, cls);\r
14582                                                 }\r
14583 \r
14584                                                 return;\r
14585 \r
14586                                         case 'A':\r
14587                                                 if (!dom.getAttrib(elm, 'href', false)) {\r
14588                                                         value = dom.getAttrib(elm, 'name') || elm.id;\r
14589                                                         cls = 'mceItemAnchor';\r
14590 \r
14591                                                         if (value) {\r
14592                                                                 if (self.hasVisual)\r
14593                                                                         dom.addClass(elm, cls);\r
14594                                                                 else\r
14595                                                                         dom.removeClass(elm, cls);\r
14596                                                         }\r
14597                                                 }\r
14598 \r
14599                                                 return;\r
14600                                 }\r
14601                         });\r
14602 \r
14603                         self.onVisualAid.dispatch(self, elm, self.hasVisual);\r
14604                 },\r
14605 \r
14606                 remove : function() {\r
14607                         var self = this, elm = self.getContainer(), doc = self.getDoc();\r
14608 \r
14609                         if (!self.removed) {\r
14610                                 self.removed = 1; // Cancels post remove event execution\r
14611 \r
14612                                 // Fixed bug where IE has a blinking cursor left from the editor\r
14613                                 if (isIE && doc)\r
14614                                         doc.execCommand('SelectAll');\r
14615 \r
14616                                 // We must save before we hide so Safari doesn't crash\r
14617                                 self.save();\r
14618 \r
14619                                 DOM.setStyle(self.id, 'display', self.orgDisplay);\r
14620 \r
14621                                 // Don't clear the window or document if content editable\r
14622                                 // is enabled since other instances might still be present\r
14623                                 if (!self.settings.content_editable) {\r
14624                                         Event.unbind(self.getWin());\r
14625                                         Event.unbind(self.getDoc());\r
14626                                 }\r
14627 \r
14628                                 Event.unbind(self.getBody());\r
14629                                 Event.clear(elm);\r
14630 \r
14631                                 self.execCallback('remove_instance_callback', self);\r
14632                                 self.onRemove.dispatch(self);\r
14633 \r
14634                                 // Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command\r
14635                                 self.onExecCommand.listeners = [];\r
14636 \r
14637                                 tinymce.remove(self);\r
14638                                 DOM.remove(elm);\r
14639                         }\r
14640                 },\r
14641 \r
14642                 destroy : function(s) {\r
14643                         var t = this;\r
14644 \r
14645                         // One time is enough\r
14646                         if (t.destroyed)\r
14647                                 return;\r
14648 \r
14649                         // 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
14650                         if (isGecko) {\r
14651                                 Event.unbind(t.getDoc());\r
14652                                 Event.unbind(t.getWin());\r
14653                                 Event.unbind(t.getBody());\r
14654                         }\r
14655 \r
14656                         if (!s) {\r
14657                                 tinymce.removeUnload(t.destroy);\r
14658                                 tinyMCE.onBeforeUnload.remove(t._beforeUnload);\r
14659 \r
14660                                 // Manual destroy\r
14661                                 if (t.theme && t.theme.destroy)\r
14662                                         t.theme.destroy();\r
14663 \r
14664                                 // Destroy controls, selection and dom\r
14665                                 t.controlManager.destroy();\r
14666                                 t.selection.destroy();\r
14667                                 t.dom.destroy();\r
14668                         }\r
14669 \r
14670                         if (t.formElement) {\r
14671                                 t.formElement.submit = t.formElement._mceOldSubmit;\r
14672                                 t.formElement._mceOldSubmit = null;\r
14673                         }\r
14674 \r
14675                         t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;\r
14676 \r
14677                         if (t.selection)\r
14678                                 t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;\r
14679 \r
14680                         t.destroyed = 1;\r
14681                 },\r
14682 \r
14683                 // Internal functions\r
14684 \r
14685                 _refreshContentEditable : function() {\r
14686                         var self = this, body, parent;\r
14687 \r
14688                         // Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again\r
14689                         if (self._isHidden()) {\r
14690                                 body = self.getBody();\r
14691                                 parent = body.parentNode;\r
14692 \r
14693                                 parent.removeChild(body);\r
14694                                 parent.appendChild(body);\r
14695 \r
14696                                 body.focus();\r
14697                         }\r
14698                 },\r
14699 \r
14700                 _isHidden : function() {\r
14701                         var s;\r
14702 \r
14703                         if (!isGecko)\r
14704                                 return 0;\r
14705 \r
14706                         // Weird, wheres that cursor selection?\r
14707                         s = this.selection.getSel();\r
14708                         return (!s || !s.rangeCount || s.rangeCount === 0);\r
14709                 }\r
14710         });\r
14711 })(tinymce);\r
14712 (function(tinymce) {\r
14713         var each = tinymce.each;\r
14714 \r
14715         tinymce.Editor.prototype.setupEvents = function() {\r
14716                 var self = this, settings = self.settings;\r
14717 \r
14718                 // Add events to the editor\r
14719                 each([\r
14720                         'onPreInit',\r
14721 \r
14722                         'onBeforeRenderUI',\r
14723 \r
14724                         'onPostRender',\r
14725 \r
14726                         'onLoad',\r
14727 \r
14728                         'onInit',\r
14729 \r
14730                         'onRemove',\r
14731 \r
14732                         'onActivate',\r
14733 \r
14734                         'onDeactivate',\r
14735 \r
14736                         'onClick',\r
14737 \r
14738                         'onEvent',\r
14739 \r
14740                         'onMouseUp',\r
14741 \r
14742                         'onMouseDown',\r
14743 \r
14744                         'onDblClick',\r
14745 \r
14746                         'onKeyDown',\r
14747 \r
14748                         'onKeyUp',\r
14749 \r
14750                         'onKeyPress',\r
14751 \r
14752                         'onContextMenu',\r
14753 \r
14754                         'onSubmit',\r
14755 \r
14756                         'onReset',\r
14757 \r
14758                         'onPaste',\r
14759 \r
14760                         'onPreProcess',\r
14761 \r
14762                         'onPostProcess',\r
14763 \r
14764                         'onBeforeSetContent',\r
14765 \r
14766                         'onBeforeGetContent',\r
14767 \r
14768                         'onSetContent',\r
14769 \r
14770                         'onGetContent',\r
14771 \r
14772                         'onLoadContent',\r
14773 \r
14774                         'onSaveContent',\r
14775 \r
14776                         'onNodeChange',\r
14777 \r
14778                         'onChange',\r
14779 \r
14780                         'onBeforeExecCommand',\r
14781 \r
14782                         'onExecCommand',\r
14783 \r
14784                         'onUndo',\r
14785 \r
14786                         'onRedo',\r
14787 \r
14788                         'onVisualAid',\r
14789 \r
14790                         'onSetProgressState',\r
14791 \r
14792                         'onSetAttrib'\r
14793                 ], function(name) {\r
14794                         self[name] = new tinymce.util.Dispatcher(self);\r
14795                 });\r
14796 \r
14797                 // Handle legacy cleanup_callback option\r
14798                 if (settings.cleanup_callback) {\r
14799                         self.onBeforeSetContent.add(function(ed, o) {\r
14800                                 o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
14801                         });\r
14802 \r
14803                         self.onPreProcess.add(function(ed, o) {\r
14804                                 if (o.set)\r
14805                                         ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);\r
14806 \r
14807                                 if (o.get)\r
14808                                         ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);\r
14809                         });\r
14810 \r
14811                         self.onPostProcess.add(function(ed, o) {\r
14812                                 if (o.set)\r
14813                                         o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);\r
14814 \r
14815                                 if (o.get)                                              \r
14816                                         o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);\r
14817                         });\r
14818                 }\r
14819 \r
14820                 // Handle legacy save_callback option\r
14821                 if (settings.save_callback) {\r
14822                         self.onGetContent.add(function(ed, o) {\r
14823                                 if (o.save)\r
14824                                         o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());\r
14825                         });\r
14826                 }\r
14827 \r
14828                 // Handle legacy handle_event_callback option\r
14829                 if (settings.handle_event_callback) {\r
14830                         self.onEvent.add(function(ed, e, o) {\r
14831                                 if (self.execCallback('handle_event_callback', e, ed, o) === false) {\r
14832                                         e.preventDefault();\r
14833                                         e.stopPropagation();\r
14834                                 }\r
14835                         });\r
14836                 }\r
14837 \r
14838                 // Handle legacy handle_node_change_callback option\r
14839                 if (settings.handle_node_change_callback) {\r
14840                         self.onNodeChange.add(function(ed, cm, n) {\r
14841                                 ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());\r
14842                         });\r
14843                 }\r
14844 \r
14845                 // Handle legacy save_callback option\r
14846                 if (settings.save_callback) {\r
14847                         self.onSaveContent.add(function(ed, o) {\r
14848                                 var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());\r
14849 \r
14850                                 if (h)\r
14851                                         o.content = h;\r
14852                         });\r
14853                 }\r
14854 \r
14855                 // Handle legacy onchange_callback option\r
14856                 if (settings.onchange_callback) {\r
14857                         self.onChange.add(function(ed, l) {\r
14858                                 ed.execCallback('onchange_callback', ed, l);\r
14859                         });\r
14860                 }\r
14861         };\r
14862 \r
14863         tinymce.Editor.prototype.bindNativeEvents = function() {\r
14864                 // 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset\r
14865                 var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;\r
14866 \r
14867                 nativeToDispatcherMap = {\r
14868                         mouseup : 'onMouseUp',\r
14869                         mousedown : 'onMouseDown',\r
14870                         click : 'onClick',\r
14871                         keyup : 'onKeyUp',\r
14872                         keydown : 'onKeyDown',\r
14873                         keypress : 'onKeyPress',\r
14874                         submit : 'onSubmit',\r
14875                         reset : 'onReset',\r
14876                         contextmenu : 'onContextMenu',\r
14877                         dblclick : 'onDblClick',\r
14878                         paste : 'onPaste' // Doesn't work in all browsers yet\r
14879                 };\r
14880 \r
14881                 // Handler that takes a native event and sends it out to a dispatcher like onKeyDown\r
14882                 function eventHandler(evt, args) {\r
14883                         var type = evt.type;\r
14884 \r
14885                         // Don't fire events when it's removed\r
14886                         if (self.removed)\r
14887                                 return;\r
14888 \r
14889                         // Sends the native event out to a global dispatcher then to the specific event dispatcher\r
14890                         if (self.onEvent.dispatch(self, evt, args) !== false) {\r
14891                                 self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);\r
14892                         }\r
14893                 };\r
14894 \r
14895                 // Opera doesn't support focus event for contentEditable elements so we need to fake it\r
14896                 function doOperaFocus(e) {\r
14897                         self.focus(true);\r
14898                 };\r
14899 \r
14900                 function nodeChanged(ed, e) {\r
14901                         // Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything\r
14902                         if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {\r
14903                                 self.selection.normalize();\r
14904                         }\r
14905 \r
14906                         self.nodeChanged();\r
14907                 }\r
14908 \r
14909                 // Add DOM events\r
14910                 each(nativeToDispatcherMap, function(dispatcherName, nativeName) {\r
14911                         var root = settings.content_editable ? self.getBody() : self.getDoc();\r
14912 \r
14913                         switch (nativeName) {\r
14914                                 case 'contextmenu':\r
14915                                         dom.bind(root, nativeName, eventHandler);\r
14916                                         break;\r
14917 \r
14918                                 case 'paste':\r
14919                                         dom.bind(self.getBody(), nativeName, eventHandler);\r
14920                                         break;\r
14921 \r
14922                                 case 'submit':\r
14923                                 case 'reset':\r
14924                                         dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);\r
14925                                         break;\r
14926 \r
14927                                 default:\r
14928                                         dom.bind(root, nativeName, eventHandler);\r
14929                         }\r
14930                 });\r
14931 \r
14932                 // Set the editor as active when focused\r
14933                 dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {\r
14934                         self.focus(true);\r
14935                 });\r
14936 \r
14937                 if (settings.content_editable && tinymce.isOpera) {\r
14938                         dom.bind(self.getBody(), 'click', doOperaFocus);\r
14939                         dom.bind(self.getBody(), 'keydown', doOperaFocus);\r
14940                 }\r
14941 \r
14942                 // Add node change handler\r
14943                 self.onMouseUp.add(nodeChanged);\r
14944 \r
14945                 self.onKeyUp.add(function(ed, e) {\r
14946                         var keyCode = e.keyCode;\r
14947 \r
14948                         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
14949                                 nodeChanged(ed, e);\r
14950                 });\r
14951 \r
14952                 // Add reset handler\r
14953                 self.onReset.add(function() {\r
14954                         self.setContent(self.startContent, {format : 'raw'});\r
14955                 });\r
14956 \r
14957                 // Add shortcuts\r
14958                 function handleShortcut(e, execute) {\r
14959                         if (e.altKey || e.ctrlKey || e.metaKey) {\r
14960                                 each(self.shortcuts, function(shortcut) {\r
14961                                         var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;\r
14962 \r
14963                                         if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)\r
14964                                                 return;\r
14965 \r
14966                                         if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {\r
14967                                                 e.preventDefault();\r
14968 \r
14969                                                 if (execute) {\r
14970                                                         shortcut.func.call(shortcut.scope);\r
14971                                                 }\r
14972 \r
14973                                                 return true;\r
14974                                         }\r
14975                                 });\r
14976                         }\r
14977                 };\r
14978 \r
14979                 self.onKeyUp.add(function(ed, e) {\r
14980                         handleShortcut(e);\r
14981                 });\r
14982 \r
14983                 self.onKeyPress.add(function(ed, e) {\r
14984                         handleShortcut(e);\r
14985                 });\r
14986 \r
14987                 self.onKeyDown.add(function(ed, e) {\r
14988                         handleShortcut(e, true);\r
14989                 });\r
14990 \r
14991                 if (tinymce.isOpera) {\r
14992                         self.onClick.add(function(ed, e) {\r
14993                                 e.preventDefault();\r
14994                         });\r
14995                 }\r
14996         };\r
14997 })(tinymce);\r
14998 (function(tinymce) {\r
14999         // Added for compression purposes\r
15000         var each = tinymce.each, undef, TRUE = true, FALSE = false;\r
15001 \r
15002         tinymce.EditorCommands = function(editor) {\r
15003                 var dom = editor.dom,\r
15004                         selection = editor.selection,\r
15005                         commands = {state: {}, exec : {}, value : {}},\r
15006                         settings = editor.settings,\r
15007                         formatter = editor.formatter,\r
15008                         bookmark;\r
15009 \r
15010                 function execCommand(command, ui, value) {\r
15011                         var func;\r
15012 \r
15013                         command = command.toLowerCase();\r
15014                         if (func = commands.exec[command]) {\r
15015                                 func(command, ui, value);\r
15016                                 return TRUE;\r
15017                         }\r
15018 \r
15019                         return FALSE;\r
15020                 };\r
15021 \r
15022                 function queryCommandState(command) {\r
15023                         var func;\r
15024 \r
15025                         command = command.toLowerCase();\r
15026                         if (func = commands.state[command])\r
15027                                 return func(command);\r
15028 \r
15029                         return -1;\r
15030                 };\r
15031 \r
15032                 function queryCommandValue(command) {\r
15033                         var func;\r
15034 \r
15035                         command = command.toLowerCase();\r
15036                         if (func = commands.value[command])\r
15037                                 return func(command);\r
15038 \r
15039                         return FALSE;\r
15040                 };\r
15041 \r
15042                 function addCommands(command_list, type) {\r
15043                         type = type || 'exec';\r
15044 \r
15045                         each(command_list, function(callback, command) {\r
15046                                 each(command.toLowerCase().split(','), function(command) {\r
15047                                         commands[type][command] = callback;\r
15048                                 });\r
15049                         });\r
15050                 };\r
15051 \r
15052                 // Expose public methods\r
15053                 tinymce.extend(this, {\r
15054                         execCommand : execCommand,\r
15055                         queryCommandState : queryCommandState,\r
15056                         queryCommandValue : queryCommandValue,\r
15057                         addCommands : addCommands\r
15058                 });\r
15059 \r
15060                 // Private methods\r
15061 \r
15062                 function execNativeCommand(command, ui, value) {\r
15063                         if (ui === undef)\r
15064                                 ui = FALSE;\r
15065 \r
15066                         if (value === undef)\r
15067                                 value = null;\r
15068 \r
15069                         return editor.getDoc().execCommand(command, ui, value);\r
15070                 };\r
15071 \r
15072                 function isFormatMatch(name) {\r
15073                         return formatter.match(name);\r
15074                 };\r
15075 \r
15076                 function toggleFormat(name, value) {\r
15077                         formatter.toggle(name, value ? {value : value} : undef);\r
15078                 };\r
15079 \r
15080                 function storeSelection(type) {\r
15081                         bookmark = selection.getBookmark(type);\r
15082                 };\r
15083 \r
15084                 function restoreSelection() {\r
15085                         selection.moveToBookmark(bookmark);\r
15086                 };\r
15087 \r
15088                 // Add execCommand overrides\r
15089                 addCommands({\r
15090                         // Ignore these, added for compatibility\r
15091                         'mceResetDesignMode,mceBeginUndoLevel' : function() {},\r
15092 \r
15093                         // Add undo manager logic\r
15094                         'mceEndUndoLevel,mceAddUndoLevel' : function() {\r
15095                                 editor.undoManager.add();\r
15096                         },\r
15097 \r
15098                         'Cut,Copy,Paste' : function(command) {\r
15099                                 var doc = editor.getDoc(), failed;\r
15100 \r
15101                                 // Try executing the native command\r
15102                                 try {\r
15103                                         execNativeCommand(command);\r
15104                                 } catch (ex) {\r
15105                                         // Command failed\r
15106                                         failed = TRUE;\r
15107                                 }\r
15108 \r
15109                                 // Present alert message about clipboard access not being available\r
15110                                 if (failed || !doc.queryCommandSupported(command)) {\r
15111                                         if (tinymce.isGecko) {\r
15112                                                 editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {\r
15113                                                         if (state)\r
15114                                                                 open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');\r
15115                                                 });\r
15116                                         } else\r
15117                                                 editor.windowManager.alert(editor.getLang('clipboard_no_support'));\r
15118                                 }\r
15119                         },\r
15120 \r
15121                         // Override unlink command\r
15122                         unlink : function(command) {\r
15123                                 if (selection.isCollapsed())\r
15124                                         selection.select(selection.getNode());\r
15125 \r
15126                                 execNativeCommand(command);\r
15127                                 selection.collapse(FALSE);\r
15128                         },\r
15129 \r
15130                         // Override justify commands to use the text formatter engine\r
15131                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
15132                                 var align = command.substring(7);\r
15133 \r
15134                                 // Remove all other alignments first\r
15135                                 each('left,center,right,full'.split(','), function(name) {\r
15136                                         if (align != name)\r
15137                                                 formatter.remove('align' + name);\r
15138                                 });\r
15139 \r
15140                                 toggleFormat('align' + align);\r
15141                                 execCommand('mceRepaint');\r
15142                         },\r
15143 \r
15144                         // Override list commands to fix WebKit bug\r
15145                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
15146                                 var listElm, listParent;\r
15147 \r
15148                                 execNativeCommand(command);\r
15149 \r
15150                                 // WebKit produces lists within block elements so we need to split them\r
15151                                 // we will replace the native list creation logic to custom logic later on\r
15152                                 // TODO: Remove this when the list creation logic is removed\r
15153                                 listElm = dom.getParent(selection.getNode(), 'ol,ul');\r
15154                                 if (listElm) {\r
15155                                         listParent = listElm.parentNode;\r
15156 \r
15157                                         // If list is within a text block then split that block\r
15158                                         if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {\r
15159                                                 storeSelection();\r
15160                                                 dom.split(listParent, listElm);\r
15161                                                 restoreSelection();\r
15162                                         }\r
15163                                 }\r
15164                         },\r
15165 \r
15166                         // Override commands to use the text formatter engine\r
15167                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
15168                                 toggleFormat(command);\r
15169                         },\r
15170 \r
15171                         // Override commands to use the text formatter engine\r
15172                         'ForeColor,HiliteColor,FontName' : function(command, ui, value) {\r
15173                                 toggleFormat(command, value);\r
15174                         },\r
15175 \r
15176                         FontSize : function(command, ui, value) {\r
15177                                 var fontClasses, fontSizes;\r
15178 \r
15179                                 // Convert font size 1-7 to styles\r
15180                                 if (value >= 1 && value <= 7) {\r
15181                                         fontSizes = tinymce.explode(settings.font_size_style_values);\r
15182                                         fontClasses = tinymce.explode(settings.font_size_classes);\r
15183 \r
15184                                         if (fontClasses)\r
15185                                                 value = fontClasses[value - 1] || value;\r
15186                                         else\r
15187                                                 value = fontSizes[value - 1] || value;\r
15188                                 }\r
15189 \r
15190                                 toggleFormat(command, value);\r
15191                         },\r
15192 \r
15193                         RemoveFormat : function(command) {\r
15194                                 formatter.remove(command);\r
15195                         },\r
15196 \r
15197                         mceBlockQuote : function(command) {\r
15198                                 toggleFormat('blockquote');\r
15199                         },\r
15200 \r
15201                         FormatBlock : function(command, ui, value) {\r
15202                                 return toggleFormat(value || 'p');\r
15203                         },\r
15204 \r
15205                         mceCleanup : function() {\r
15206                                 var bookmark = selection.getBookmark();\r
15207 \r
15208                                 editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});\r
15209 \r
15210                                 selection.moveToBookmark(bookmark);\r
15211                         },\r
15212 \r
15213                         mceRemoveNode : function(command, ui, value) {\r
15214                                 var node = value || selection.getNode();\r
15215 \r
15216                                 // Make sure that the body node isn't removed\r
15217                                 if (node != editor.getBody()) {\r
15218                                         storeSelection();\r
15219                                         editor.dom.remove(node, TRUE);\r
15220                                         restoreSelection();\r
15221                                 }\r
15222                         },\r
15223 \r
15224                         mceSelectNodeDepth : function(command, ui, value) {\r
15225                                 var counter = 0;\r
15226 \r
15227                                 dom.getParent(selection.getNode(), function(node) {\r
15228                                         if (node.nodeType == 1 && counter++ == value) {\r
15229                                                 selection.select(node);\r
15230                                                 return FALSE;\r
15231                                         }\r
15232                                 }, editor.getBody());\r
15233                         },\r
15234 \r
15235                         mceSelectNode : function(command, ui, value) {\r
15236                                 selection.select(value);\r
15237                         },\r
15238 \r
15239                         mceInsertContent : function(command, ui, value) {\r
15240                                 var parser, serializer, parentNode, rootNode, fragment, args,\r
15241                                         marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;\r
15242 \r
15243                                 //selection.normalize();\r
15244 \r
15245                                 // Setup parser and serializer\r
15246                                 parser = editor.parser;\r
15247                                 serializer = new tinymce.html.Serializer({}, editor.schema);\r
15248                                 bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';\r
15249 \r
15250                                 // Run beforeSetContent handlers on the HTML to be inserted\r
15251                                 args = {content: value, format: 'html'};\r
15252                                 selection.onBeforeSetContent.dispatch(selection, args);\r
15253                                 value = args.content;\r
15254 \r
15255                                 // Add caret at end of contents if it's missing\r
15256                                 if (value.indexOf('{$caret}') == -1)\r
15257                                         value += '{$caret}';\r
15258 \r
15259                                 // Replace the caret marker with a span bookmark element\r
15260                                 value = value.replace(/\{\$caret\}/, bookmarkHtml);\r
15261 \r
15262                                 // Insert node maker where we will insert the new HTML and get it's parent\r
15263                                 if (!selection.isCollapsed())\r
15264                                         editor.getDoc().execCommand('Delete', false, null);\r
15265 \r
15266                                 parentNode = selection.getNode();\r
15267 \r
15268                                 // Parse the fragment within the context of the parent node\r
15269                                 args = {context : parentNode.nodeName.toLowerCase()};\r
15270                                 fragment = parser.parse(value, args);\r
15271 \r
15272                                 // Move the caret to a more suitable location\r
15273                                 node = fragment.lastChild;\r
15274                                 if (node.attr('id') == 'mce_marker') {\r
15275                                         marker = node;\r
15276 \r
15277                                         for (node = node.prev; node; node = node.walk(true)) {\r
15278                                                 if (node.type == 3 || !dom.isBlock(node.name)) {\r
15279                                                         node.parent.insert(marker, node, node.name === 'br');\r
15280                                                         break;\r
15281                                                 }\r
15282                                         }\r
15283                                 }\r
15284 \r
15285                                 // If parser says valid we can insert the contents into that parent\r
15286                                 if (!args.invalid) {\r
15287                                         value = serializer.serialize(fragment);\r
15288 \r
15289                                         // Check if parent is empty or only has one BR element then set the innerHTML of that parent\r
15290                                         node = parentNode.firstChild;\r
15291                                         node2 = parentNode.lastChild;\r
15292                                         if (!node || (node === node2 && node.nodeName === 'BR'))\r
15293                                                 dom.setHTML(parentNode, value);\r
15294                                         else\r
15295                                                 selection.setContent(value);\r
15296                                 } else {\r
15297                                         // If the fragment was invalid within that context then we need\r
15298                                         // to parse and process the parent it's inserted into\r
15299 \r
15300                                         // Insert bookmark node and get the parent\r
15301                                         selection.setContent(bookmarkHtml);\r
15302                                         parentNode = selection.getNode();\r
15303                                         rootNode = editor.getBody();\r
15304 \r
15305                                         // Opera will return the document node when selection is in root\r
15306                                         if (parentNode.nodeType == 9)\r
15307                                                 parentNode = node = rootNode;\r
15308                                         else\r
15309                                                 node = parentNode;\r
15310 \r
15311                                         // Find the ancestor just before the root element\r
15312                                         while (node !== rootNode) {\r
15313                                                 parentNode = node;\r
15314                                                 node = node.parentNode;\r
15315                                         }\r
15316 \r
15317                                         // Get the outer/inner HTML depending on if we are in the root and parser and serialize that\r
15318                                         value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);\r
15319                                         value = serializer.serialize(\r
15320                                                 parser.parse(\r
15321                                                         // Need to replace by using a function since $ in the contents would otherwise be a problem\r
15322                                                         value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {\r
15323                                                                 return serializer.serialize(fragment);\r
15324                                                         })\r
15325                                                 )\r
15326                                         );\r
15327 \r
15328                                         // Set the inner/outer HTML depending on if we are in the root or not\r
15329                                         if (parentNode == rootNode)\r
15330                                                 dom.setHTML(rootNode, value);\r
15331                                         else\r
15332                                                 dom.setOuterHTML(parentNode, value);\r
15333                                 }\r
15334 \r
15335                                 marker = dom.get('mce_marker');\r
15336 \r
15337                                 // Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well\r
15338                                 nodeRect = dom.getRect(marker);\r
15339                                 viewPortRect = dom.getViewPort(editor.getWin());\r
15340 \r
15341                                 // Check if node is out side the viewport if it is then scroll to it\r
15342                                 if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||\r
15343                                         (nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {\r
15344                                         viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();\r
15345                                         viewportBodyElement.scrollLeft = nodeRect.x;\r
15346                                         viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;\r
15347                                 }\r
15348 \r
15349                                 // Move selection before marker and remove it\r
15350                                 rng = dom.createRng();\r
15351 \r
15352                                 // If previous sibling is a text node set the selection to the end of that node\r
15353                                 node = marker.previousSibling;\r
15354                                 if (node && node.nodeType == 3) {\r
15355                                         rng.setStart(node, node.nodeValue.length);\r
15356                                 } else {\r
15357                                         // If the previous sibling isn't a text node or doesn't exist set the selection before the marker node\r
15358                                         rng.setStartBefore(marker);\r
15359                                         rng.setEndBefore(marker);\r
15360                                 }\r
15361 \r
15362                                 // Remove the marker node and set the new range\r
15363                                 dom.remove(marker);\r
15364                                 selection.setRng(rng);\r
15365 \r
15366                                 // Dispatch after event and add any visual elements needed\r
15367                                 selection.onSetContent.dispatch(selection, args);\r
15368                                 editor.addVisual();\r
15369                         },\r
15370 \r
15371                         mceInsertRawHTML : function(command, ui, value) {\r
15372                                 selection.setContent('tiny_mce_marker');\r
15373                                 editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));\r
15374                         },\r
15375 \r
15376                         mceToggleFormat : function(command, ui, value) {\r
15377                                 toggleFormat(value);\r
15378                         },\r
15379 \r
15380                         mceSetContent : function(command, ui, value) {\r
15381                                 editor.setContent(value);\r
15382                         },\r
15383 \r
15384                         'Indent,Outdent' : function(command) {\r
15385                                 var intentValue, indentUnit, value;\r
15386 \r
15387                                 // Setup indent level\r
15388                                 intentValue = settings.indentation;\r
15389                                 indentUnit = /[a-z%]+$/i.exec(intentValue);\r
15390                                 intentValue = parseInt(intentValue);\r
15391 \r
15392                                 if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {\r
15393                                         // If forced_root_blocks is set to false we don't have a block to indent so lets create a div\r
15394                                         if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {\r
15395                                                 formatter.apply('div');\r
15396                                         }\r
15397 \r
15398                                         each(selection.getSelectedBlocks(), function(element) {\r
15399                                                 if (command == 'outdent') {\r
15400                                                         value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);\r
15401                                                         dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');\r
15402                                                 } else\r
15403                                                         dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);\r
15404                                         });\r
15405                                 } else\r
15406                                         execNativeCommand(command);\r
15407                         },\r
15408 \r
15409                         mceRepaint : function() {\r
15410                                 var bookmark;\r
15411 \r
15412                                 if (tinymce.isGecko) {\r
15413                                         try {\r
15414                                                 storeSelection(TRUE);\r
15415 \r
15416                                                 if (selection.getSel())\r
15417                                                         selection.getSel().selectAllChildren(editor.getBody());\r
15418 \r
15419                                                 selection.collapse(TRUE);\r
15420                                                 restoreSelection();\r
15421                                         } catch (ex) {\r
15422                                                 // Ignore\r
15423                                         }\r
15424                                 }\r
15425                         },\r
15426 \r
15427                         mceToggleFormat : function(command, ui, value) {\r
15428                                 formatter.toggle(value);\r
15429                         },\r
15430 \r
15431                         InsertHorizontalRule : function() {\r
15432                                 editor.execCommand('mceInsertContent', false, '<hr />');\r
15433                         },\r
15434 \r
15435                         mceToggleVisualAid : function() {\r
15436                                 editor.hasVisual = !editor.hasVisual;\r
15437                                 editor.addVisual();\r
15438                         },\r
15439 \r
15440                         mceReplaceContent : function(command, ui, value) {\r
15441                                 editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));\r
15442                         },\r
15443 \r
15444                         mceInsertLink : function(command, ui, value) {\r
15445                                 var anchor;\r
15446 \r
15447                                 if (typeof(value) == 'string')\r
15448                                         value = {href : value};\r
15449 \r
15450                                 anchor = dom.getParent(selection.getNode(), 'a');\r
15451 \r
15452                                 // Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.\r
15453                                 value.href = value.href.replace(' ', '%20');\r
15454 \r
15455                                 // Remove existing links if there could be child links or that the href isn't specified\r
15456                                 if (!anchor || !value.href) {\r
15457                                         formatter.remove('link');\r
15458                                 }               \r
15459 \r
15460                                 // Apply new link to selection\r
15461                                 if (value.href) {\r
15462                                         formatter.apply('link', value, anchor);\r
15463                                 }\r
15464                         },\r
15465 \r
15466                         selectAll : function() {\r
15467                                 var root = dom.getRoot(), rng = dom.createRng();\r
15468 \r
15469                                 // Old IE does a better job with selectall than new versions\r
15470                                 if (selection.getRng().setStart) {\r
15471                                         rng.setStart(root, 0);\r
15472                                         rng.setEnd(root, root.childNodes.length);\r
15473 \r
15474                                         selection.setRng(rng);\r
15475                                 } else {\r
15476                                         execNativeCommand('SelectAll');\r
15477                                 }\r
15478                         }\r
15479                 });\r
15480 \r
15481                 // Add queryCommandState overrides\r
15482                 addCommands({\r
15483                         // Override justify commands\r
15484                         'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {\r
15485                                 var name = 'align' + command.substring(7);\r
15486                                 var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();\r
15487                                 var matches = tinymce.map(nodes, function(node) {\r
15488                                         return !!formatter.matchNode(node, name);\r
15489                                 });\r
15490                                 return tinymce.inArray(matches, TRUE) !== -1;\r
15491                         },\r
15492 \r
15493                         'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {\r
15494                                 return isFormatMatch(command);\r
15495                         },\r
15496 \r
15497                         mceBlockQuote : function() {\r
15498                                 return isFormatMatch('blockquote');\r
15499                         },\r
15500 \r
15501                         Outdent : function() {\r
15502                                 var node;\r
15503 \r
15504                                 if (settings.inline_styles) {\r
15505                                         if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
15506                                                 return TRUE;\r
15507 \r
15508                                         if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)\r
15509                                                 return TRUE;\r
15510                                 }\r
15511 \r
15512                                 return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));\r
15513                         },\r
15514 \r
15515                         'InsertUnorderedList,InsertOrderedList' : function(command) {\r
15516                                 var list = dom.getParent(selection.getNode(), 'ul,ol');\r
15517                                 return list && \r
15518                                      (command === 'insertunorderedlist' && list.tagName === 'UL'\r
15519                                    || command === 'insertorderedlist' && list.tagName === 'OL');\r
15520                         }\r
15521                 }, 'state');\r
15522 \r
15523                 // Add queryCommandValue overrides\r
15524                 addCommands({\r
15525                         'FontSize,FontName' : function(command) {\r
15526                                 var value = 0, parent;\r
15527 \r
15528                                 if (parent = dom.getParent(selection.getNode(), 'span')) {\r
15529                                         if (command == 'fontsize')\r
15530                                                 value = parent.style.fontSize;\r
15531                                         else\r
15532                                                 value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();\r
15533                                 }\r
15534 \r
15535                                 return value;\r
15536                         }\r
15537                 }, 'value');\r
15538 \r
15539                 // Add undo manager logic\r
15540                 addCommands({\r
15541                         Undo : function() {\r
15542                                 editor.undoManager.undo();\r
15543                         },\r
15544 \r
15545                         Redo : function() {\r
15546                                 editor.undoManager.redo();\r
15547                         }\r
15548                 });\r
15549         };\r
15550 })(tinymce);\r
15551 \r
15552 (function(tinymce) {\r
15553         var Dispatcher = tinymce.util.Dispatcher;\r
15554 \r
15555         tinymce.UndoManager = function(editor) {\r
15556                 var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;\r
15557 \r
15558                 function getContent() {\r
15559                         // Remove whitespace before/after and remove pure bogus nodes\r
15560                         return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));\r
15561                 };\r
15562 \r
15563                 function addNonTypingUndoLevel() {\r
15564                         self.typing = false;\r
15565                         self.add();\r
15566                 };\r
15567 \r
15568                 // Create event instances\r
15569                 onBeforeAdd = new Dispatcher(self);\r
15570                 onAdd       = new Dispatcher(self);\r
15571                 onUndo      = new Dispatcher(self);\r
15572                 onRedo      = new Dispatcher(self);\r
15573 \r
15574                 // Pass though onAdd event from UndoManager to Editor as onChange\r
15575                 onAdd.add(function(undoman, level) {\r
15576                         if (undoman.hasUndo())\r
15577                                 return editor.onChange.dispatch(editor, level, undoman);\r
15578                 });\r
15579 \r
15580                 // Pass though onUndo event from UndoManager to Editor\r
15581                 onUndo.add(function(undoman, level) {\r
15582                         return editor.onUndo.dispatch(editor, level, undoman);\r
15583                 });\r
15584 \r
15585                 // Pass though onRedo event from UndoManager to Editor\r
15586                 onRedo.add(function(undoman, level) {\r
15587                         return editor.onRedo.dispatch(editor, level, undoman);\r
15588                 });\r
15589 \r
15590                 // Add initial undo level when the editor is initialized\r
15591                 editor.onInit.add(function() {\r
15592                         self.add();\r
15593                 });\r
15594 \r
15595                 // Get position before an execCommand is processed\r
15596                 editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {\r
15597                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {\r
15598                                 self.beforeChange();\r
15599                         }\r
15600                 });\r
15601 \r
15602                 // Add undo level after an execCommand call was made\r
15603                 editor.onExecCommand.add(function(ed, cmd, ui, val, args) {\r
15604                         if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {\r
15605                                 self.add();\r
15606                         }\r
15607                 });\r
15608 \r
15609                 // Add undo level on save contents, drag end and blur/focusout\r
15610                 editor.onSaveContent.add(addNonTypingUndoLevel);\r
15611                 editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);\r
15612                 editor.dom.bind(editor.getBody(), 'focusout', function(e) {\r
15613                         if (!editor.removed && self.typing) {\r
15614                                 addNonTypingUndoLevel();\r
15615                         }\r
15616                 });\r
15617 \r
15618                 editor.onKeyUp.add(function(editor, e) {\r
15619                         var keyCode = e.keyCode;\r
15620 \r
15621                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {\r
15622                                 addNonTypingUndoLevel();\r
15623                         }\r
15624                 });\r
15625 \r
15626                 editor.onKeyDown.add(function(editor, e) {\r
15627                         var keyCode = e.keyCode;\r
15628 \r
15629                         // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter\r
15630                         if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {\r
15631                                 if (self.typing) {\r
15632                                         addNonTypingUndoLevel();\r
15633                                 }\r
15634 \r
15635                                 return;\r
15636                         }\r
15637 \r
15638                         // If key isn't shift,ctrl,alt,capslock,metakey\r
15639                         if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {\r
15640                                 self.beforeChange();\r
15641                                 self.typing = true;\r
15642                                 self.add();\r
15643                         }\r
15644                 });\r
15645 \r
15646                 editor.onMouseDown.add(function(editor, e) {\r
15647                         if (self.typing) {\r
15648                                 addNonTypingUndoLevel();\r
15649                         }\r
15650                 });\r
15651 \r
15652                 // Add keyboard shortcuts for undo/redo keys\r
15653                 editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');\r
15654                 editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');\r
15655 \r
15656                 self = {\r
15657                         // Explose for debugging reasons\r
15658                         data : data,\r
15659 \r
15660                         typing : false,\r
15661                         \r
15662                         onBeforeAdd: onBeforeAdd,\r
15663 \r
15664                         onAdd : onAdd,\r
15665 \r
15666                         onUndo : onUndo,\r
15667 \r
15668                         onRedo : onRedo,\r
15669 \r
15670                         beforeChange : function() {\r
15671                                 beforeBookmark = editor.selection.getBookmark(2, true);\r
15672                         },\r
15673 \r
15674                         add : function(level) {\r
15675                                 var i, settings = editor.settings, lastLevel;\r
15676 \r
15677                                 level = level || {};\r
15678                                 level.content = getContent();\r
15679                                 \r
15680                                 self.onBeforeAdd.dispatch(self, level);\r
15681 \r
15682                                 // Add undo level if needed\r
15683                                 lastLevel = data[index];\r
15684                                 if (lastLevel && lastLevel.content == level.content)\r
15685                                         return null;\r
15686 \r
15687                                 // Set before bookmark on previous level\r
15688                                 if (data[index])\r
15689                                         data[index].beforeBookmark = beforeBookmark;\r
15690 \r
15691                                 // Time to compress\r
15692                                 if (settings.custom_undo_redo_levels) {\r
15693                                         if (data.length > settings.custom_undo_redo_levels) {\r
15694                                                 for (i = 0; i < data.length - 1; i++)\r
15695                                                         data[i] = data[i + 1];\r
15696 \r
15697                                                 data.length--;\r
15698                                                 index = data.length;\r
15699                                         }\r
15700                                 }\r
15701 \r
15702                                 // Get a non intrusive normalized bookmark\r
15703                                 level.bookmark = editor.selection.getBookmark(2, true);\r
15704 \r
15705                                 // Crop array if needed\r
15706                                 if (index < data.length - 1)\r
15707                                         data.length = index + 1;\r
15708 \r
15709                                 data.push(level);\r
15710                                 index = data.length - 1;\r
15711 \r
15712                                 self.onAdd.dispatch(self, level);\r
15713                                 editor.isNotDirty = 0;\r
15714 \r
15715                                 return level;\r
15716                         },\r
15717 \r
15718                         undo : function() {\r
15719                                 var level, i;\r
15720 \r
15721                                 if (self.typing) {\r
15722                                         self.add();\r
15723                                         self.typing = false;\r
15724                                 }\r
15725 \r
15726                                 if (index > 0) {\r
15727                                         level = data[--index];\r
15728 \r
15729                                         editor.setContent(level.content, {format : 'raw'});\r
15730                                         editor.selection.moveToBookmark(level.beforeBookmark);\r
15731 \r
15732                                         self.onUndo.dispatch(self, level);\r
15733                                 }\r
15734 \r
15735                                 return level;\r
15736                         },\r
15737 \r
15738                         redo : function() {\r
15739                                 var level;\r
15740 \r
15741                                 if (index < data.length - 1) {\r
15742                                         level = data[++index];\r
15743 \r
15744                                         editor.setContent(level.content, {format : 'raw'});\r
15745                                         editor.selection.moveToBookmark(level.bookmark);\r
15746 \r
15747                                         self.onRedo.dispatch(self, level);\r
15748                                 }\r
15749 \r
15750                                 return level;\r
15751                         },\r
15752 \r
15753                         clear : function() {\r
15754                                 data = [];\r
15755                                 index = 0;\r
15756                                 self.typing = false;\r
15757                         },\r
15758 \r
15759                         hasUndo : function() {\r
15760                                 return index > 0 || this.typing;\r
15761                         },\r
15762 \r
15763                         hasRedo : function() {\r
15764                                 return index < data.length - 1 && !this.typing;\r
15765                         }\r
15766                 };\r
15767 \r
15768                 return self;\r
15769         };\r
15770 })(tinymce);\r
15771 \r
15772 tinymce.ForceBlocks = function(editor) {\r
15773         var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();\r
15774 \r
15775         function addRootBlocks() {\r
15776                 var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;\r
15777 \r
15778                 if (!node || node.nodeType !== 1 || !settings.forced_root_block)\r
15779                         return;\r
15780 \r
15781                 // Check if node is wrapped in block\r
15782                 while (node && node != rootNode) {\r
15783                         if (blockElements[node.nodeName])\r
15784                                 return;\r
15785 \r
15786                         node = node.parentNode;\r
15787                 }\r
15788 \r
15789                 // Get current selection\r
15790                 rng = selection.getRng();\r
15791                 if (rng.setStart) {\r
15792                         startContainer = rng.startContainer;\r
15793                         startOffset = rng.startOffset;\r
15794                         endContainer = rng.endContainer;\r
15795                         endOffset = rng.endOffset;\r
15796                 } else {\r
15797                         // Force control range into text range\r
15798                         if (rng.item) {\r
15799                                 node = rng.item(0);\r
15800                                 rng = editor.getDoc().body.createTextRange();\r
15801                                 rng.moveToElementText(node);\r
15802                         }\r
15803 \r
15804                         isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();\r
15805                         tmpRng = rng.duplicate();\r
15806                         tmpRng.collapse(true);\r
15807                         startOffset = tmpRng.move('character', offset) * -1;\r
15808 \r
15809                         if (!tmpRng.collapsed) {\r
15810                                 tmpRng = rng.duplicate();\r
15811                                 tmpRng.collapse(false);\r
15812                                 endOffset = (tmpRng.move('character', offset) * -1) - startOffset;\r
15813                         }\r
15814                 }\r
15815 \r
15816                 // Wrap non block elements and text nodes\r
15817                 node = rootNode.firstChild;\r
15818                 while (node) {\r
15819                         if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {\r
15820                                 // Remove empty text nodes\r
15821                                 if (node.nodeType === 3 && node.nodeValue.length == 0) {\r
15822                                         tempNode = node;\r
15823                                         node = node.nextSibling;\r
15824                                         dom.remove(tempNode);\r
15825                                         continue;\r
15826                                 }\r
15827 \r
15828                                 if (!rootBlockNode) {\r
15829                                         rootBlockNode = dom.create(settings.forced_root_block);\r
15830                                         node.parentNode.insertBefore(rootBlockNode, node);\r
15831                                         wrapped = true;\r
15832                                 }\r
15833 \r
15834                                 tempNode = node;\r
15835                                 node = node.nextSibling;\r
15836                                 rootBlockNode.appendChild(tempNode);\r
15837                         } else {\r
15838                                 rootBlockNode = null;\r
15839                                 node = node.nextSibling;\r
15840                         }\r
15841                 }\r
15842 \r
15843                 if (wrapped) {\r
15844                         if (rng.setStart) {\r
15845                                 rng.setStart(startContainer, startOffset);\r
15846                                 rng.setEnd(endContainer, endOffset);\r
15847                                 selection.setRng(rng);\r
15848                         } else {\r
15849                                 // Only select if the previous selection was inside the document to prevent auto focus in quirks mode\r
15850                                 if (isInEditorDocument) {\r
15851                                         try {\r
15852                                                 rng = editor.getDoc().body.createTextRange();\r
15853                                                 rng.moveToElementText(rootNode);\r
15854                                                 rng.collapse(true);\r
15855                                                 rng.moveStart('character', startOffset);\r
15856 \r
15857                                                 if (endOffset > 0)\r
15858                                                         rng.moveEnd('character', endOffset);\r
15859 \r
15860                                                 rng.select();\r
15861                                         } catch (ex) {\r
15862                                                 // Ignore\r
15863                                         }\r
15864                                 }\r
15865                         }\r
15866 \r
15867                         editor.nodeChanged();\r
15868                 }\r
15869         };\r
15870 \r
15871         // Force root blocks\r
15872         if (settings.forced_root_block) {\r
15873                 editor.onKeyUp.add(addRootBlocks);\r
15874                 editor.onNodeChange.add(addRootBlocks);\r
15875         }\r
15876 };\r
15877 \r
15878 (function(tinymce) {\r
15879         // Shorten names\r
15880         var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;\r
15881 \r
15882         tinymce.create('tinymce.ControlManager', {\r
15883                 ControlManager : function(ed, s) {\r
15884                         var t = this, i;\r
15885 \r
15886                         s = s || {};\r
15887                         t.editor = ed;\r
15888                         t.controls = {};\r
15889                         t.onAdd = new tinymce.util.Dispatcher(t);\r
15890                         t.onPostRender = new tinymce.util.Dispatcher(t);\r
15891                         t.prefix = s.prefix || ed.id + '_';\r
15892                         t._cls = {};\r
15893 \r
15894                         t.onPostRender.add(function() {\r
15895                                 each(t.controls, function(c) {\r
15896                                         c.postRender();\r
15897                                 });\r
15898                         });\r
15899                 },\r
15900 \r
15901                 get : function(id) {\r
15902                         return this.controls[this.prefix + id] || this.controls[id];\r
15903                 },\r
15904 \r
15905                 setActive : function(id, s) {\r
15906                         var c = null;\r
15907 \r
15908                         if (c = this.get(id))\r
15909                                 c.setActive(s);\r
15910 \r
15911                         return c;\r
15912                 },\r
15913 \r
15914                 setDisabled : function(id, s) {\r
15915                         var c = null;\r
15916 \r
15917                         if (c = this.get(id))\r
15918                                 c.setDisabled(s);\r
15919 \r
15920                         return c;\r
15921                 },\r
15922 \r
15923                 add : function(c) {\r
15924                         var t = this;\r
15925 \r
15926                         if (c) {\r
15927                                 t.controls[c.id] = c;\r
15928                                 t.onAdd.dispatch(c, t);\r
15929                         }\r
15930 \r
15931                         return c;\r
15932                 },\r
15933 \r
15934                 createControl : function(name) {\r
15935                         var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;\r
15936 \r
15937                         // Build control factory cache\r
15938                         if (!self.controlFactories) {\r
15939                                 self.controlFactories = [];\r
15940                                 each(editor.plugins, function(plugin) {\r
15941                                         if (plugin.createControl) {\r
15942                                                 self.controlFactories.push(plugin);\r
15943                                         }\r
15944                                 });\r
15945                         }\r
15946 \r
15947                         // Create controls by asking cached factories\r
15948                         factories = self.controlFactories;\r
15949                         for (i = 0, l = factories.length; i < l; i++) {\r
15950                                 ctrl = factories[i].createControl(name, self);\r
15951 \r
15952                                 if (ctrl) {\r
15953                                         return self.add(ctrl);\r
15954                                 }\r
15955                         }\r
15956 \r
15957                         // Create sepearator\r
15958                         if (name === "|" || name === "separator") {\r
15959                                 return self.createSeparator();\r
15960                         }\r
15961 \r
15962                         // Create control from button collection\r
15963                         if (editor.buttons && (ctrl = editor.buttons[name])) {\r
15964                                 return self.createButton(name, ctrl);\r
15965                         }\r
15966 \r
15967                         return self.add(ctrl);\r
15968                 },\r
15969 \r
15970                 createDropMenu : function(id, s, cc) {\r
15971                         var t = this, ed = t.editor, c, bm, v, cls;\r
15972 \r
15973                         s = extend({\r
15974                                 'class' : 'mceDropDown',\r
15975                                 constrain : ed.settings.constrain_menus\r
15976                         }, s);\r
15977 \r
15978                         s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';\r
15979                         if (v = ed.getParam('skin_variant'))\r
15980                                 s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);\r
15981 \r
15982                         s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';\r
15983 \r
15984                         id = t.prefix + id;\r
15985                         cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;\r
15986                         c = t.controls[id] = new cls(id, s);\r
15987                         c.onAddItem.add(function(c, o) {\r
15988                                 var s = o.settings;\r
15989 \r
15990                                 s.title = ed.getLang(s.title, s.title);\r
15991 \r
15992                                 if (!s.onclick) {\r
15993                                         s.onclick = function(v) {\r
15994                                                 if (s.cmd)\r
15995                                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
15996                                         };\r
15997                                 }\r
15998                         });\r
15999 \r
16000                         ed.onRemove.add(function() {\r
16001                                 c.destroy();\r
16002                         });\r
16003 \r
16004                         // Fix for bug #1897785, #1898007\r
16005                         if (tinymce.isIE) {\r
16006                                 c.onShowMenu.add(function() {\r
16007                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
16008                                         ed.focus();\r
16009 \r
16010                                         bm = ed.selection.getBookmark(1);\r
16011                                 });\r
16012 \r
16013                                 c.onHideMenu.add(function() {\r
16014                                         if (bm) {\r
16015                                                 ed.selection.moveToBookmark(bm);\r
16016                                                 bm = 0;\r
16017                                         }\r
16018                                 });\r
16019                         }\r
16020 \r
16021                         return t.add(c);\r
16022                 },\r
16023 \r
16024                 createListBox : function(id, s, cc) {\r
16025                         var t = this, ed = t.editor, cmd, c, cls;\r
16026 \r
16027                         if (t.get(id))\r
16028                                 return null;\r
16029 \r
16030                         s.title = ed.translate(s.title);\r
16031                         s.scope = s.scope || ed;\r
16032 \r
16033                         if (!s.onselect) {\r
16034                                 s.onselect = function(v) {\r
16035                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
16036                                 };\r
16037                         }\r
16038 \r
16039                         s = extend({\r
16040                                 title : s.title,\r
16041                                 'class' : 'mce_' + id,\r
16042                                 scope : s.scope,\r
16043                                 control_manager : t\r
16044                         }, s);\r
16045 \r
16046                         id = t.prefix + id;\r
16047 \r
16048 \r
16049                         function useNativeListForAccessibility(ed) {\r
16050                                 return ed.settings.use_accessible_selects && !tinymce.isGecko\r
16051                         }\r
16052 \r
16053                         if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))\r
16054                                 c = new tinymce.ui.NativeListBox(id, s);\r
16055                         else {\r
16056                                 cls = cc || t._cls.listbox || tinymce.ui.ListBox;\r
16057                                 c = new cls(id, s, ed);\r
16058                         }\r
16059 \r
16060                         t.controls[id] = c;\r
16061 \r
16062                         // Fix focus problem in Safari\r
16063                         if (tinymce.isWebKit) {\r
16064                                 c.onPostRender.add(function(c, n) {\r
16065                                         // Store bookmark on mousedown\r
16066                                         Event.add(n, 'mousedown', function() {\r
16067                                                 ed.bookmark = ed.selection.getBookmark(1);\r
16068                                         });\r
16069 \r
16070                                         // Restore on focus, since it might be lost\r
16071                                         Event.add(n, 'focus', function() {\r
16072                                                 ed.selection.moveToBookmark(ed.bookmark);\r
16073                                                 ed.bookmark = null;\r
16074                                         });\r
16075                                 });\r
16076                         }\r
16077 \r
16078                         if (c.hideMenu)\r
16079                                 ed.onMouseDown.add(c.hideMenu, c);\r
16080 \r
16081                         return t.add(c);\r
16082                 },\r
16083 \r
16084                 createButton : function(id, s, cc) {\r
16085                         var t = this, ed = t.editor, o, c, cls;\r
16086 \r
16087                         if (t.get(id))\r
16088                                 return null;\r
16089 \r
16090                         s.title = ed.translate(s.title);\r
16091                         s.label = ed.translate(s.label);\r
16092                         s.scope = s.scope || ed;\r
16093 \r
16094                         if (!s.onclick && !s.menu_button) {\r
16095                                 s.onclick = function() {\r
16096                                         ed.execCommand(s.cmd, s.ui || false, s.value);\r
16097                                 };\r
16098                         }\r
16099 \r
16100                         s = extend({\r
16101                                 title : s.title,\r
16102                                 'class' : 'mce_' + id,\r
16103                                 unavailable_prefix : ed.getLang('unavailable', ''),\r
16104                                 scope : s.scope,\r
16105                                 control_manager : t\r
16106                         }, s);\r
16107 \r
16108                         id = t.prefix + id;\r
16109 \r
16110                         if (s.menu_button) {\r
16111                                 cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;\r
16112                                 c = new cls(id, s, ed);\r
16113                                 ed.onMouseDown.add(c.hideMenu, c);\r
16114                         } else {\r
16115                                 cls = t._cls.button || tinymce.ui.Button;\r
16116                                 c = new cls(id, s, ed);\r
16117                         }\r
16118 \r
16119                         return t.add(c);\r
16120                 },\r
16121 \r
16122                 createMenuButton : function(id, s, cc) {\r
16123                         s = s || {};\r
16124                         s.menu_button = 1;\r
16125 \r
16126                         return this.createButton(id, s, cc);\r
16127                 },\r
16128 \r
16129                 createSplitButton : function(id, s, cc) {\r
16130                         var t = this, ed = t.editor, cmd, c, cls;\r
16131 \r
16132                         if (t.get(id))\r
16133                                 return null;\r
16134 \r
16135                         s.title = ed.translate(s.title);\r
16136                         s.scope = s.scope || ed;\r
16137 \r
16138                         if (!s.onclick) {\r
16139                                 s.onclick = function(v) {\r
16140                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
16141                                 };\r
16142                         }\r
16143 \r
16144                         if (!s.onselect) {\r
16145                                 s.onselect = function(v) {\r
16146                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
16147                                 };\r
16148                         }\r
16149 \r
16150                         s = extend({\r
16151                                 title : s.title,\r
16152                                 'class' : 'mce_' + id,\r
16153                                 scope : s.scope,\r
16154                                 control_manager : t\r
16155                         }, s);\r
16156 \r
16157                         id = t.prefix + id;\r
16158                         cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;\r
16159                         c = t.add(new cls(id, s, ed));\r
16160                         ed.onMouseDown.add(c.hideMenu, c);\r
16161 \r
16162                         return c;\r
16163                 },\r
16164 \r
16165                 createColorSplitButton : function(id, s, cc) {\r
16166                         var t = this, ed = t.editor, cmd, c, cls, bm;\r
16167 \r
16168                         if (t.get(id))\r
16169                                 return null;\r
16170 \r
16171                         s.title = ed.translate(s.title);\r
16172                         s.scope = s.scope || ed;\r
16173 \r
16174                         if (!s.onclick) {\r
16175                                 s.onclick = function(v) {\r
16176                                         if (tinymce.isIE)\r
16177                                                 bm = ed.selection.getBookmark(1);\r
16178 \r
16179                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
16180                                 };\r
16181                         }\r
16182 \r
16183                         if (!s.onselect) {\r
16184                                 s.onselect = function(v) {\r
16185                                         ed.execCommand(s.cmd, s.ui || false, v || s.value);\r
16186                                 };\r
16187                         }\r
16188 \r
16189                         s = extend({\r
16190                                 title : s.title,\r
16191                                 'class' : 'mce_' + id,\r
16192                                 'menu_class' : ed.getParam('skin') + 'Skin',\r
16193                                 scope : s.scope,\r
16194                                 more_colors_title : ed.getLang('more_colors')\r
16195                         }, s);\r
16196 \r
16197                         id = t.prefix + id;\r
16198                         cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;\r
16199                         c = new cls(id, s, ed);\r
16200                         ed.onMouseDown.add(c.hideMenu, c);\r
16201 \r
16202                         // Remove the menu element when the editor is removed\r
16203                         ed.onRemove.add(function() {\r
16204                                 c.destroy();\r
16205                         });\r
16206 \r
16207                         // Fix for bug #1897785, #1898007\r
16208                         if (tinymce.isIE) {\r
16209                                 c.onShowMenu.add(function() {\r
16210                                         // IE 8 needs focus in order to store away a range with the current collapsed caret location\r
16211                                         ed.focus();\r
16212                                         bm = ed.selection.getBookmark(1);\r
16213                                 });\r
16214 \r
16215                                 c.onHideMenu.add(function() {\r
16216                                         if (bm) {\r
16217                                                 ed.selection.moveToBookmark(bm);\r
16218                                                 bm = 0;\r
16219                                         }\r
16220                                 });\r
16221                         }\r
16222 \r
16223                         return t.add(c);\r
16224                 },\r
16225 \r
16226                 createToolbar : function(id, s, cc) {\r
16227                         var c, t = this, cls;\r
16228 \r
16229                         id = t.prefix + id;\r
16230                         cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;\r
16231                         c = new cls(id, s, t.editor);\r
16232 \r
16233                         if (t.get(id))\r
16234                                 return null;\r
16235 \r
16236                         return t.add(c);\r
16237                 },\r
16238                 \r
16239                 createToolbarGroup : function(id, s, cc) {\r
16240                         var c, t = this, cls;\r
16241                         id = t.prefix + id;\r
16242                         cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;\r
16243                         c = new cls(id, s, t.editor);\r
16244                         \r
16245                         if (t.get(id))\r
16246                                 return null;\r
16247                         \r
16248                         return t.add(c);\r
16249                 },\r
16250 \r
16251                 createSeparator : function(cc) {\r
16252                         var cls = cc || this._cls.separator || tinymce.ui.Separator;\r
16253 \r
16254                         return new cls();\r
16255                 },\r
16256 \r
16257                 setControlType : function(n, c) {\r
16258                         return this._cls[n.toLowerCase()] = c;\r
16259                 },\r
16260         \r
16261                 destroy : function() {\r
16262                         each(this.controls, function(c) {\r
16263                                 c.destroy();\r
16264                         });\r
16265 \r
16266                         this.controls = null;\r
16267                 }\r
16268         });\r
16269 })(tinymce);\r
16270 \r
16271 (function(tinymce) {\r
16272         var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;\r
16273 \r
16274         tinymce.create('tinymce.WindowManager', {\r
16275                 WindowManager : function(ed) {\r
16276                         var t = this;\r
16277 \r
16278                         t.editor = ed;\r
16279                         t.onOpen = new Dispatcher(t);\r
16280                         t.onClose = new Dispatcher(t);\r
16281                         t.params = {};\r
16282                         t.features = {};\r
16283                 },\r
16284 \r
16285                 open : function(s, p) {\r
16286                         var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;\r
16287 \r
16288                         // Default some options\r
16289                         s = s || {};\r
16290                         p = p || {};\r
16291                         sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window\r
16292                         sh = isOpera ? vp.h : screen.height;\r
16293                         s.name = s.name || 'mc_' + new Date().getTime();\r
16294                         s.width = parseInt(s.width || 320);\r
16295                         s.height = parseInt(s.height || 240);\r
16296                         s.resizable = true;\r
16297                         s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);\r
16298                         s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);\r
16299                         p.inline = false;\r
16300                         p.mce_width = s.width;\r
16301                         p.mce_height = s.height;\r
16302                         p.mce_auto_focus = s.auto_focus;\r
16303 \r
16304                         if (mo) {\r
16305                                 if (isIE) {\r
16306                                         s.center = true;\r
16307                                         s.help = false;\r
16308                                         s.dialogWidth = s.width + 'px';\r
16309                                         s.dialogHeight = s.height + 'px';\r
16310                                         s.scroll = s.scrollbars || false;\r
16311                                 }\r
16312                         }\r
16313 \r
16314                         // Build features string\r
16315                         each(s, function(v, k) {\r
16316                                 if (tinymce.is(v, 'boolean'))\r
16317                                         v = v ? 'yes' : 'no';\r
16318 \r
16319                                 if (!/^(name|url)$/.test(k)) {\r
16320                                         if (isIE && mo)\r
16321                                                 f += (f ? ';' : '') + k + ':' + v;\r
16322                                         else\r
16323                                                 f += (f ? ',' : '') + k + '=' + v;\r
16324                                 }\r
16325                         });\r
16326 \r
16327                         t.features = s;\r
16328                         t.params = p;\r
16329                         t.onOpen.dispatch(t, s, p);\r
16330 \r
16331                         u = s.url || s.file;\r
16332                         u = tinymce._addVer(u);\r
16333 \r
16334                         try {\r
16335                                 if (isIE && mo) {\r
16336                                         w = 1;\r
16337                                         window.showModalDialog(u, window, f);\r
16338                                 } else\r
16339                                         w = window.open(u, s.name, f);\r
16340                         } catch (ex) {\r
16341                                 // Ignore\r
16342                         }\r
16343 \r
16344                         if (!w)\r
16345                                 alert(t.editor.getLang('popup_blocked'));\r
16346                 },\r
16347 \r
16348                 close : function(w) {\r
16349                         w.close();\r
16350                         this.onClose.dispatch(this);\r
16351                 },\r
16352 \r
16353                 createInstance : function(cl, a, b, c, d, e) {\r
16354                         var f = tinymce.resolve(cl);\r
16355 \r
16356                         return new f(a, b, c, d, e);\r
16357                 },\r
16358 \r
16359                 confirm : function(t, cb, s, w) {\r
16360                         w = w || window;\r
16361 \r
16362                         cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));\r
16363                 },\r
16364 \r
16365                 alert : function(tx, cb, s, w) {\r
16366                         var t = this;\r
16367 \r
16368                         w = w || window;\r
16369                         w.alert(t._decode(t.editor.getLang(tx, tx)));\r
16370 \r
16371                         if (cb)\r
16372                                 cb.call(s || t);\r
16373                 },\r
16374 \r
16375                 resizeBy : function(dw, dh, win) {\r
16376                         win.resizeBy(dw, dh);\r
16377                 },\r
16378 \r
16379                 // Internal functions\r
16380 \r
16381                 _decode : function(s) {\r
16382                         return tinymce.DOM.decode(s).replace(/\\n/g, '\n');\r
16383                 }\r
16384         });\r
16385 }(tinymce));\r
16386 (function(tinymce) {\r
16387         tinymce.Formatter = function(ed) {\r
16388                 var formats = {},\r
16389                         each = tinymce.each,\r
16390                         dom = ed.dom,\r
16391                         selection = ed.selection,\r
16392                         TreeWalker = tinymce.dom.TreeWalker,\r
16393                         rangeUtils = new tinymce.dom.RangeUtils(dom),\r
16394                         isValid = ed.schema.isValidChild,\r
16395                         isArray = tinymce.isArray,\r
16396                         isBlock = dom.isBlock,\r
16397                         forcedRootBlock = ed.settings.forced_root_block,\r
16398                         nodeIndex = dom.nodeIndex,\r
16399                         INVISIBLE_CHAR = '\uFEFF',\r
16400                         MCE_ATTR_RE = /^(src|href|style)$/,\r
16401                         FALSE = false,\r
16402                         TRUE = true,\r
16403                         formatChangeData,\r
16404                         undef,\r
16405                         getContentEditable = dom.getContentEditable;\r
16406 \r
16407                 function isTextBlock(name) {\r
16408                         return !!ed.schema.getTextBlocks()[name.toLowerCase()];\r
16409                 }\r
16410 \r
16411                 function getParents(node, selector) {\r
16412                         return dom.getParents(node, selector, dom.getRoot());\r
16413                 };\r
16414 \r
16415                 function isCaretNode(node) {\r
16416                         return node.nodeType === 1 && node.id === '_mce_caret';\r
16417                 };\r
16418 \r
16419                 function defaultFormats() {\r
16420                         register({\r
16421                                 alignleft : [\r
16422                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},\r
16423                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}\r
16424                                 ],\r
16425 \r
16426                                 aligncenter : [\r
16427                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},\r
16428                                         {selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},\r
16429                                         {selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}\r
16430                                 ],\r
16431 \r
16432                                 alignright : [\r
16433                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},\r
16434                                         {selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}\r
16435                                 ],\r
16436 \r
16437                                 alignfull : [\r
16438                                         {selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}\r
16439                                 ],\r
16440 \r
16441                                 bold : [\r
16442                                         {inline : 'strong', remove : 'all'},\r
16443                                         {inline : 'span', styles : {fontWeight : 'bold'}},\r
16444                                         {inline : 'b', remove : 'all'}\r
16445                                 ],\r
16446 \r
16447                                 italic : [\r
16448                                         {inline : 'em', remove : 'all'},\r
16449                                         {inline : 'span', styles : {fontStyle : 'italic'}},\r
16450                                         {inline : 'i', remove : 'all'}\r
16451                                 ],\r
16452 \r
16453                                 underline : [\r
16454                                         {inline : 'span', styles : {textDecoration : 'underline'}, exact : true},\r
16455                                         {inline : 'u', remove : 'all'}\r
16456                                 ],\r
16457 \r
16458                                 strikethrough : [\r
16459                                         {inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},\r
16460                                         {inline : 'strike', remove : 'all'}\r
16461                                 ],\r
16462 \r
16463                                 forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},\r
16464                                 hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},\r
16465                                 fontname : {inline : 'span', styles : {fontFamily : '%value'}},\r
16466                                 fontsize : {inline : 'span', styles : {fontSize : '%value'}},\r
16467                                 fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},\r
16468                                 blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},\r
16469                                 subscript : {inline : 'sub'},\r
16470                                 superscript : {inline : 'sup'},\r
16471 \r
16472                                 link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,\r
16473                                         onmatch : function(node) {\r
16474                                                 return true;\r
16475                                         },\r
16476 \r
16477                                         onformat : function(elm, fmt, vars) {\r
16478                                                 each(vars, function(value, key) {\r
16479                                                         dom.setAttrib(elm, key, value);\r
16480                                                 });\r
16481                                         }\r
16482                                 },\r
16483 \r
16484                                 removeformat : [\r
16485                                         {selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},\r
16486                                         {selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},\r
16487                                         {selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}\r
16488                                 ]\r
16489                         });\r
16490 \r
16491                         // Register default block formats\r
16492                         each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {\r
16493                                 register(name, {block : name, remove : 'all'});\r
16494                         });\r
16495 \r
16496                         // Register user defined formats\r
16497                         register(ed.settings.formats);\r
16498                 };\r
16499 \r
16500                 function addKeyboardShortcuts() {\r
16501                         // Add some inline shortcuts\r
16502                         ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');\r
16503                         ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');\r
16504                         ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');\r
16505 \r
16506                         // BlockFormat shortcuts keys\r
16507                         for (var i = 1; i <= 6; i++) {\r
16508                                 ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);\r
16509                         }\r
16510 \r
16511                         ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);\r
16512                         ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);\r
16513                         ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);\r
16514                 };\r
16515 \r
16516                 // Public functions\r
16517 \r
16518                 function get(name) {\r
16519                         return name ? formats[name] : formats;\r
16520                 };\r
16521 \r
16522                 function register(name, format) {\r
16523                         if (name) {\r
16524                                 if (typeof(name) !== 'string') {\r
16525                                         each(name, function(format, name) {\r
16526                                                 register(name, format);\r
16527                                         });\r
16528                                 } else {\r
16529                                         // Force format into array and add it to internal collection\r
16530                                         format = format.length ? format : [format];\r
16531 \r
16532                                         each(format, function(format) {\r
16533                                                 // Set deep to false by default on selector formats this to avoid removing\r
16534                                                 // alignment on images inside paragraphs when alignment is changed on paragraphs\r
16535                                                 if (format.deep === undef)\r
16536                                                         format.deep = !format.selector;\r
16537 \r
16538                                                 // Default to true\r
16539                                                 if (format.split === undef)\r
16540                                                         format.split = !format.selector || format.inline;\r
16541 \r
16542                                                 // Default to true\r
16543                                                 if (format.remove === undef && format.selector && !format.inline)\r
16544                                                         format.remove = 'none';\r
16545 \r
16546                                                 // Mark format as a mixed format inline + block level\r
16547                                                 if (format.selector && format.inline) {\r
16548                                                         format.mixed = true;\r
16549                                                         format.block_expand = true;\r
16550                                                 }\r
16551 \r
16552                                                 // Split classes if needed\r
16553                                                 if (typeof(format.classes) === 'string')\r
16554                                                         format.classes = format.classes.split(/\s+/);\r
16555                                         });\r
16556 \r
16557                                         formats[name] = format;\r
16558                                 }\r
16559                         }\r
16560                 };\r
16561 \r
16562                 var getTextDecoration = function(node) {\r
16563                         var decoration;\r
16564 \r
16565                         ed.dom.getParent(node, function(n) {\r
16566                                 decoration = ed.dom.getStyle(n, 'text-decoration');\r
16567                                 return decoration && decoration !== 'none';\r
16568                         });\r
16569 \r
16570                         return decoration;\r
16571                 };\r
16572 \r
16573                 var processUnderlineAndColor = function(node) {\r
16574                         var textDecoration;\r
16575                         if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {\r
16576                                 textDecoration = getTextDecoration(node.parentNode);\r
16577                                 if (ed.dom.getStyle(node, 'color') && textDecoration) {\r
16578                                         ed.dom.setStyle(node, 'text-decoration', textDecoration);\r
16579                                 } else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {\r
16580                                         ed.dom.setStyle(node, 'text-decoration', null);\r
16581                                 }\r
16582                         }\r
16583                 };\r
16584 \r
16585                 function apply(name, vars, node) {\r
16586                         var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();\r
16587 \r
16588                         function setElementFormat(elm, fmt) {\r
16589                                 fmt = fmt || format;\r
16590 \r
16591                                 if (elm) {\r
16592                                         if (fmt.onformat) {\r
16593                                                 fmt.onformat(elm, fmt, vars, node);\r
16594                                         }\r
16595 \r
16596                                         each(fmt.styles, function(value, name) {\r
16597                                                 dom.setStyle(elm, name, replaceVars(value, vars));\r
16598                                         });\r
16599 \r
16600                                         each(fmt.attributes, function(value, name) {\r
16601                                                 dom.setAttrib(elm, name, replaceVars(value, vars));\r
16602                                         });\r
16603 \r
16604                                         each(fmt.classes, function(value) {\r
16605                                                 value = replaceVars(value, vars);\r
16606 \r
16607                                                 if (!dom.hasClass(elm, value))\r
16608                                                         dom.addClass(elm, value);\r
16609                                         });\r
16610                                 }\r
16611                         };\r
16612                         function adjustSelectionToVisibleSelection() {\r
16613                                 function findSelectionEnd(start, end) {\r
16614                                         var walker = new TreeWalker(end);\r
16615                                         for (node = walker.current(); node; node = walker.prev()) {\r
16616                                                 if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {\r
16617                                                         return node;\r
16618                                                 }\r
16619                                         }\r
16620                                 };\r
16621 \r
16622                                 // Adjust selection so that a end container with a end offset of zero is not included in the selection\r
16623                                 // as this isn't visible to the user.\r
16624                                 var rng = ed.selection.getRng();\r
16625                                 var start = rng.startContainer;\r
16626                                 var end = rng.endContainer;\r
16627 \r
16628                                 if (start != end && rng.endOffset === 0) {\r
16629                                         var newEnd = findSelectionEnd(start, end);\r
16630                                         var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;\r
16631 \r
16632                                         rng.setEnd(newEnd, endOffset);\r
16633                                 }\r
16634 \r
16635                                 return rng;\r
16636                         }\r
16637                         \r
16638                         function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){\r
16639                                 var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;\r
16640                                 \r
16641                                 // find the index of the first child list.\r
16642                                 each(node.childNodes, function(n, index) {\r
16643                                         if (n.nodeName === "UL" || n.nodeName === "OL") {\r
16644                                                 listIndex = index;\r
16645                                                 list = n;\r
16646                                                 return false;\r
16647                                         }\r
16648                                 });\r
16649                                 \r
16650                                 // get the index of the bookmarks\r
16651                                 each(node.childNodes, function(n, index) {\r
16652                                         if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {\r
16653                                                 if (n.id == bookmark.id + "_start") {\r
16654                                                         startIndex = index;\r
16655                                                 } else if (n.id == bookmark.id + "_end") {\r
16656                                                         endIndex = index;\r
16657                                                 }\r
16658                                         }\r
16659                                 });\r
16660                                 \r
16661                                 // if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally\r
16662                                 if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {\r
16663                                         each(tinymce.grep(node.childNodes), process);\r
16664                                         return 0;\r
16665                                 } else {\r
16666                                         currentWrapElm = dom.clone(wrapElm, FALSE);\r
16667 \r
16668                                         // create a list of the nodes on the same side of the list as the selection\r
16669                                         each(tinymce.grep(node.childNodes), function(n, index) {\r
16670                                                 if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {\r
16671                                                         nodes.push(n); \r
16672                                                         n.parentNode.removeChild(n);\r
16673                                                 }\r
16674                                         });\r
16675 \r
16676                                         // insert the wrapping element either before or after the list.\r
16677                                         if (startIndex < listIndex) {\r
16678                                                 node.insertBefore(currentWrapElm, list);\r
16679                                         } else if (startIndex > listIndex) {\r
16680                                                 node.insertBefore(currentWrapElm, list.nextSibling);\r
16681                                         }\r
16682                                         \r
16683                                         // add the new nodes to the list.\r
16684                                         newWrappers.push(currentWrapElm);\r
16685 \r
16686                                         each(nodes, function(node) {\r
16687                                                 currentWrapElm.appendChild(node);\r
16688                                         });\r
16689 \r
16690                                         return currentWrapElm;\r
16691                                 }\r
16692                         };\r
16693 \r
16694                         function applyRngStyle(rng, bookmark, node_specific) {\r
16695                                 var newWrappers = [], wrapName, wrapElm, contentEditable = true;\r
16696 \r
16697                                 // Setup wrapper element\r
16698                                 wrapName = format.inline || format.block;\r
16699                                 wrapElm = dom.create(wrapName);\r
16700                                 setElementFormat(wrapElm);\r
16701 \r
16702                                 rangeUtils.walk(rng, function(nodes) {\r
16703                                         var currentWrapElm;\r
16704 \r
16705                                         function process(node) {\r
16706                                                 var nodeName, parentName, found, hasContentEditableState, lastContentEditable;\r
16707 \r
16708                                                 lastContentEditable = contentEditable;\r
16709                                                 nodeName = node.nodeName.toLowerCase();\r
16710                                                 parentName = node.parentNode.nodeName.toLowerCase();\r
16711 \r
16712                                                 // Node has a contentEditable value\r
16713                                                 if (node.nodeType === 1 && getContentEditable(node)) {\r
16714                                                         lastContentEditable = contentEditable;\r
16715                                                         contentEditable = getContentEditable(node) === "true";\r
16716                                                         hasContentEditableState = true; // We don't want to wrap the container only it's children\r
16717                                                 }\r
16718 \r
16719                                                 // Stop wrapping on br elements\r
16720                                                 if (isEq(nodeName, 'br')) {\r
16721                                                         currentWrapElm = 0;\r
16722 \r
16723                                                         // Remove any br elements when we wrap things\r
16724                                                         if (format.block)\r
16725                                                                 dom.remove(node);\r
16726 \r
16727                                                         return;\r
16728                                                 }\r
16729 \r
16730                                                 // If node is wrapper type\r
16731                                                 if (format.wrapper && matchNode(node, name, vars)) {\r
16732                                                         currentWrapElm = 0;\r
16733                                                         return;\r
16734                                                 }\r
16735 \r
16736                                                 // Can we rename the block\r
16737                                                 if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {\r
16738                                                         node = dom.rename(node, wrapName);\r
16739                                                         setElementFormat(node);\r
16740                                                         newWrappers.push(node);\r
16741                                                         currentWrapElm = 0;\r
16742                                                         return;\r
16743                                                 }\r
16744 \r
16745                                                 // Handle selector patterns\r
16746                                                 if (format.selector) {\r
16747                                                         // Look for matching formats\r
16748                                                         each(formatList, function(format) {\r
16749                                                                 // Check collapsed state if it exists\r
16750                                                                 if ('collapsed' in format && format.collapsed !== isCollapsed) {\r
16751                                                                         return;\r
16752                                                                 }\r
16753 \r
16754                                                                 if (dom.is(node, format.selector) && !isCaretNode(node)) {\r
16755                                                                         setElementFormat(node, format);\r
16756                                                                         found = true;\r
16757                                                                 }\r
16758                                                         });\r
16759 \r
16760                                                         // Continue processing if a selector match wasn't found and a inline element is defined\r
16761                                                         if (!format.inline || found) {\r
16762                                                                 currentWrapElm = 0;\r
16763                                                                 return;\r
16764                                                         }\r
16765                                                 }\r
16766 \r
16767                                                 // Is it valid to wrap this item\r
16768                                                 if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&\r
16769                                                                 !(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {\r
16770                                                         // Start wrapping\r
16771                                                         if (!currentWrapElm) {\r
16772                                                                 // Wrap the node\r
16773                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);\r
16774                                                                 node.parentNode.insertBefore(currentWrapElm, node);\r
16775                                                                 newWrappers.push(currentWrapElm);\r
16776                                                         }\r
16777 \r
16778                                                         currentWrapElm.appendChild(node);\r
16779                                                 } else if (nodeName == 'li' && bookmark) {\r
16780                                                         // 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
16781                                                         currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);\r
16782                                                 } else {\r
16783                                                         // Start a new wrapper for possible children\r
16784                                                         currentWrapElm = 0;\r
16785                                                         \r
16786                                                         each(tinymce.grep(node.childNodes), process);\r
16787 \r
16788                                                         if (hasContentEditableState) {\r
16789                                                                 contentEditable = lastContentEditable; // Restore last contentEditable state from stack\r
16790                                                         }\r
16791 \r
16792                                                         // End the last wrapper\r
16793                                                         currentWrapElm = 0;\r
16794                                                 }\r
16795                                         };\r
16796 \r
16797                                         // Process siblings from range\r
16798                                         each(nodes, process);\r
16799                                 });\r
16800 \r
16801                                 // Wrap links inside as well, for example color inside a link when the wrapper is around the link\r
16802                                 if (format.wrap_links === false) {\r
16803                                         each(newWrappers, function(node) {\r
16804                                                 function process(node) {\r
16805                                                         var i, currentWrapElm, children;\r
16806 \r
16807                                                         if (node.nodeName === 'A') {\r
16808                                                                 currentWrapElm = dom.clone(wrapElm, FALSE);\r
16809                                                                 newWrappers.push(currentWrapElm);\r
16810 \r
16811                                                                 children = tinymce.grep(node.childNodes);\r
16812                                                                 for (i = 0; i < children.length; i++)\r
16813                                                                         currentWrapElm.appendChild(children[i]);\r
16814 \r
16815                                                                 node.appendChild(currentWrapElm);\r
16816                                                         }\r
16817 \r
16818                                                         each(tinymce.grep(node.childNodes), process);\r
16819                                                 };\r
16820 \r
16821                                                 process(node);\r
16822                                         });\r
16823                                 }\r
16824 \r
16825                                 // Cleanup\r
16826                                 \r
16827                                 each(newWrappers, function(node) {\r
16828                                         var childCount;\r
16829 \r
16830                                         function getChildCount(node) {\r
16831                                                 var count = 0;\r
16832 \r
16833                                                 each(node.childNodes, function(node) {\r
16834                                                         if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))\r
16835                                                                 count++;\r
16836                                                 });\r
16837 \r
16838                                                 return count;\r
16839                                         };\r
16840 \r
16841                                         function mergeStyles(node) {\r
16842                                                 var child, clone;\r
16843 \r
16844                                                 each(node.childNodes, function(node) {\r
16845                                                         if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {\r
16846                                                                 child = node;\r
16847                                                                 return FALSE; // break loop\r
16848                                                         }\r
16849                                                 });\r
16850 \r
16851                                                 // If child was found and of the same type as the current node\r
16852                                                 if (child && matchName(child, format)) {\r
16853                                                         clone = dom.clone(child, FALSE);\r
16854                                                         setElementFormat(clone);\r
16855 \r
16856                                                         dom.replace(clone, node, TRUE);\r
16857                                                         dom.remove(child, 1);\r
16858                                                 }\r
16859 \r
16860                                                 return clone || node;\r
16861                                         };\r
16862 \r
16863                                         childCount = getChildCount(node);\r
16864 \r
16865                                         // Remove empty nodes but only if there is multiple wrappers and they are not block\r
16866                                         // elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at\r
16867                                         if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {\r
16868                                                 dom.remove(node, 1);\r
16869                                                 return;\r
16870                                         }\r
16871 \r
16872                                         if (format.inline || format.wrapper) {\r
16873                                                 // Merges the current node with it's children of similar type to reduce the number of elements\r
16874                                                 if (!format.exact && childCount === 1)\r
16875                                                         node = mergeStyles(node);\r
16876 \r
16877                                                 // Remove/merge children\r
16878                                                 each(formatList, function(format) {\r
16879                                                         // Merge all children of similar type will move styles from child to parent\r
16880                                                         // this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>\r
16881                                                         // will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>\r
16882                                                         each(dom.select(format.inline, node), function(child) {\r
16883                                                                 var parent;\r
16884 \r
16885                                                                 // When wrap_links is set to false we don't want\r
16886                                                                 // to remove the format on children within links\r
16887                                                                 if (format.wrap_links === false) {\r
16888                                                                         parent = child.parentNode;\r
16889 \r
16890                                                                         do {\r
16891                                                                                 if (parent.nodeName === 'A')\r
16892                                                                                         return;\r
16893                                                                         } while (parent = parent.parentNode);\r
16894                                                                 }\r
16895 \r
16896                                                                 removeFormat(format, vars, child, format.exact ? child : null);\r
16897                                                         });\r
16898                                                 });\r
16899 \r
16900                                                 // Remove child if direct parent is of same type\r
16901                                                 if (matchNode(node.parentNode, name, vars)) {\r
16902                                                         dom.remove(node, 1);\r
16903                                                         node = 0;\r
16904                                                         return TRUE;\r
16905                                                 }\r
16906 \r
16907                                                 // Look for parent with similar style format\r
16908                                                 if (format.merge_with_parents) {\r
16909                                                         dom.getParent(node.parentNode, function(parent) {\r
16910                                                                 if (matchNode(parent, name, vars)) {\r
16911                                                                         dom.remove(node, 1);\r
16912                                                                         node = 0;\r
16913                                                                         return TRUE;\r
16914                                                                 }\r
16915                                                         });\r
16916                                                 }\r
16917 \r
16918                                                 // Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>\r
16919                                                 if (node && format.merge_siblings !== false) {\r
16920                                                         node = mergeSiblings(getNonWhiteSpaceSibling(node), node);\r
16921                                                         node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));\r
16922                                                 }\r
16923                                         }\r
16924                                 });\r
16925                         };\r
16926 \r
16927                         if (format) {\r
16928                                 if (node) {\r
16929                                         if (node.nodeType) {\r
16930                                                 rng = dom.createRng();\r
16931                                                 rng.setStartBefore(node);\r
16932                                                 rng.setEndAfter(node);\r
16933                                                 applyRngStyle(expandRng(rng, formatList), null, true);\r
16934                                         } else {\r
16935                                                 applyRngStyle(node, null, true);\r
16936                                         }\r
16937                                 } else {\r
16938                                         if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
16939                                                 // Obtain selection node before selection is unselected by applyRngStyle()\r
16940                                                 var curSelNode = ed.selection.getNode();\r
16941 \r
16942                                                 // 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
16943                                                 // It's kind of a hack but people should be using the default block type P since all desktop editors work that way\r
16944                                                 if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {\r
16945                                                         apply(formatList[0].defaultBlock);\r
16946                                                 }\r
16947 \r
16948                                                 // Apply formatting to selection\r
16949                                                 ed.selection.setRng(adjustSelectionToVisibleSelection());\r
16950                                                 bookmark = selection.getBookmark();\r
16951                                                 applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);\r
16952 \r
16953                                                 // Colored nodes should be underlined so that the color of the underline matches the text color.\r
16954                                                 if (format.styles && (format.styles.color || format.styles.textDecoration)) {\r
16955                                                         tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');\r
16956                                                         processUnderlineAndColor(curSelNode);\r
16957                                                 }\r
16958 \r
16959                                                 selection.moveToBookmark(bookmark);\r
16960                                                 moveStart(selection.getRng(TRUE));\r
16961                                                 ed.nodeChanged();\r
16962                                         } else\r
16963                                                 performCaretAction('apply', name, vars);\r
16964                                 }\r
16965                         }\r
16966                 };\r
16967 \r
16968                 function remove(name, vars, node) {\r
16969                         var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;\r
16970 \r
16971                         // Merges the styles for each node\r
16972                         function process(node) {\r
16973                                 var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;\r
16974 \r
16975                                 // Skip on text nodes as they have neither format to remove nor children\r
16976                                 if (node.nodeType === 3) {\r
16977                                         return;\r
16978                                 }\r
16979 \r
16980                                 // Node has a contentEditable value\r
16981                                 if (node.nodeType === 1 && getContentEditable(node)) {\r
16982                                         lastContentEditable = contentEditable;\r
16983                                         contentEditable = getContentEditable(node) === "true";\r
16984                                         hasContentEditableState = true; // We don't want to wrap the container only it's children\r
16985                                 }\r
16986 \r
16987                                 // Grab the children first since the nodelist might be changed\r
16988                                 children = tinymce.grep(node.childNodes);\r
16989 \r
16990                                 // Process current node\r
16991                                 if (contentEditable && !hasContentEditableState) {\r
16992                                         for (i = 0, l = formatList.length; i < l; i++) {\r
16993                                                 if (removeFormat(formatList[i], vars, node, node))\r
16994                                                         break;\r
16995                                         }\r
16996                                 }\r
16997 \r
16998                                 // Process the children\r
16999                                 if (format.deep) {\r
17000                                         if (children.length) {                                  \r
17001                                                 for (i = 0, l = children.length; i < l; i++)\r
17002                                                         process(children[i]);\r
17003 \r
17004                                                 if (hasContentEditableState) {\r
17005                                                         contentEditable = lastContentEditable; // Restore last contentEditable state from stack\r
17006                                                 }\r
17007                                         }\r
17008                                 }\r
17009                         };\r
17010 \r
17011                         function findFormatRoot(container) {\r
17012                                 var formatRoot;\r
17013 \r
17014                                 // Find format root\r
17015                                 each(getParents(container.parentNode).reverse(), function(parent) {\r
17016                                         var format;\r
17017 \r
17018                                         // Find format root element\r
17019                                         if (!formatRoot && parent.id != '_start' && parent.id != '_end') {\r
17020                                                 // Is the node matching the format we are looking for\r
17021                                                 format = matchNode(parent, name, vars);\r
17022                                                 if (format && format.split !== false)\r
17023                                                         formatRoot = parent;\r
17024                                         }\r
17025                                 });\r
17026 \r
17027                                 return formatRoot;\r
17028                         };\r
17029 \r
17030                         function wrapAndSplit(format_root, container, target, split) {\r
17031                                 var parent, clone, lastClone, firstClone, i, formatRootParent;\r
17032 \r
17033                                 // Format root found then clone formats and split it\r
17034                                 if (format_root) {\r
17035                                         formatRootParent = format_root.parentNode;\r
17036 \r
17037                                         for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {\r
17038                                                 clone = dom.clone(parent, FALSE);\r
17039 \r
17040                                                 for (i = 0; i < formatList.length; i++) {\r
17041                                                         if (removeFormat(formatList[i], vars, clone, clone)) {\r
17042                                                                 clone = 0;\r
17043                                                                 break;\r
17044                                                         }\r
17045                                                 }\r
17046 \r
17047                                                 // Build wrapper node\r
17048                                                 if (clone) {\r
17049                                                         if (lastClone)\r
17050                                                                 clone.appendChild(lastClone);\r
17051 \r
17052                                                         if (!firstClone)\r
17053                                                                 firstClone = clone;\r
17054 \r
17055                                                         lastClone = clone;\r
17056                                                 }\r
17057                                         }\r
17058 \r
17059                                         // Never split block elements if the format is mixed\r
17060                                         if (split && (!format.mixed || !isBlock(format_root)))\r
17061                                                 container = dom.split(format_root, container);\r
17062 \r
17063                                         // Wrap container in cloned formats\r
17064                                         if (lastClone) {\r
17065                                                 target.parentNode.insertBefore(lastClone, target);\r
17066                                                 firstClone.appendChild(target);\r
17067                                         }\r
17068                                 }\r
17069 \r
17070                                 return container;\r
17071                         };\r
17072 \r
17073                         function splitToFormatRoot(container) {\r
17074                                 return wrapAndSplit(findFormatRoot(container), container, container, true);\r
17075                         };\r
17076 \r
17077                         function unwrap(start) {\r
17078                                 var node = dom.get(start ? '_start' : '_end'),\r
17079                                         out = node[start ? 'firstChild' : 'lastChild'];\r
17080 \r
17081                                 // If the end is placed within the start the result will be removed\r
17082                                 // So this checks if the out node is a bookmark node if it is it\r
17083                                 // checks for another more suitable node\r
17084                                 if (isBookmarkNode(out))\r
17085                                         out = out[start ? 'firstChild' : 'lastChild'];\r
17086 \r
17087                                 dom.remove(node, true);\r
17088 \r
17089                                 return out;\r
17090                         };\r
17091 \r
17092                         function removeRngStyle(rng) {\r
17093                                 var startContainer, endContainer, node;\r
17094 \r
17095                                 rng = expandRng(rng, formatList, TRUE);\r
17096 \r
17097                                 if (format.split) {\r
17098                                         startContainer = getContainer(rng, TRUE);\r
17099                                         endContainer = getContainer(rng);\r
17100 \r
17101                                         if (startContainer != endContainer) {\r
17102                                                 // 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
17103                                                 // This will happen if you tripple click a table cell and use remove formatting\r
17104                                                 if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {\r
17105                                                         startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;\r
17106                                                 }\r
17107 \r
17108                                                 // Wrap start/end nodes in span element since these might be cloned/moved\r
17109                                                 startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});\r
17110                                                 endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});\r
17111 \r
17112                                                 // Split start/end\r
17113                                                 splitToFormatRoot(startContainer);\r
17114                                                 splitToFormatRoot(endContainer);\r
17115 \r
17116                                                 // Unwrap start/end to get real elements again\r
17117                                                 startContainer = unwrap(TRUE);\r
17118                                                 endContainer = unwrap();\r
17119                                         } else\r
17120                                                 startContainer = endContainer = splitToFormatRoot(startContainer);\r
17121 \r
17122                                         // Update range positions since they might have changed after the split operations\r
17123                                         rng.startContainer = startContainer.parentNode;\r
17124                                         rng.startOffset = nodeIndex(startContainer);\r
17125                                         rng.endContainer = endContainer.parentNode;\r
17126                                         rng.endOffset = nodeIndex(endContainer) + 1;\r
17127                                 }\r
17128 \r
17129                                 // Remove items between start/end\r
17130                                 rangeUtils.walk(rng, function(nodes) {\r
17131                                         each(nodes, function(node) {\r
17132                                                 process(node);\r
17133 \r
17134                                                 // Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.\r
17135                                                 if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {\r
17136                                                         removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);\r
17137                                                 }\r
17138                                         });\r
17139                                 });\r
17140                         };\r
17141 \r
17142                         // Handle node\r
17143                         if (node) {\r
17144                                 if (node.nodeType) {\r
17145                                         rng = dom.createRng();\r
17146                                         rng.setStartBefore(node);\r
17147                                         rng.setEndAfter(node);\r
17148                                         removeRngStyle(rng);\r
17149                                 } else {\r
17150                                         removeRngStyle(node);\r
17151                                 }\r
17152 \r
17153                                 return;\r
17154                         }\r
17155 \r
17156                         if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {\r
17157                                 bookmark = selection.getBookmark();\r
17158                                 removeRngStyle(selection.getRng(TRUE));\r
17159                                 selection.moveToBookmark(bookmark);\r
17160 \r
17161                                 // 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
17162                                 if (format.inline && match(name, vars, selection.getStart())) {\r
17163                                         moveStart(selection.getRng(true));\r
17164                                 }\r
17165 \r
17166                                 ed.nodeChanged();\r
17167                         } else\r
17168                                 performCaretAction('remove', name, vars);\r
17169                 };\r
17170 \r
17171                 function toggle(name, vars, node) {\r
17172                         var fmt = get(name);\r
17173 \r
17174                         if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))\r
17175                                 remove(name, vars, node);\r
17176                         else\r
17177                                 apply(name, vars, node);\r
17178                 };\r
17179 \r
17180                 function matchNode(node, name, vars, similar) {\r
17181                         var formatList = get(name), format, i, classes;\r
17182 \r
17183                         function matchItems(node, format, item_name) {\r
17184                                 var key, value, items = format[item_name], i;\r
17185 \r
17186                                 // Custom match\r
17187                                 if (format.onmatch) {\r
17188                                         return format.onmatch(node, format, item_name);\r
17189                                 }\r
17190 \r
17191                                 // Check all items\r
17192                                 if (items) {\r
17193                                         // Non indexed object\r
17194                                         if (items.length === undef) {\r
17195                                                 for (key in items) {\r
17196                                                         if (items.hasOwnProperty(key)) {\r
17197                                                                 if (item_name === 'attributes')\r
17198                                                                         value = dom.getAttrib(node, key);\r
17199                                                                 else\r
17200                                                                         value = getStyle(node, key);\r
17201 \r
17202                                                                 if (similar && !value && !format.exact)\r
17203                                                                         return;\r
17204 \r
17205                                                                 if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))\r
17206                                                                         return;\r
17207                                                         }\r
17208                                                 }\r
17209                                         } else {\r
17210                                                 // Only one match needed for indexed arrays\r
17211                                                 for (i = 0; i < items.length; i++) {\r
17212                                                         if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))\r
17213                                                                 return format;\r
17214                                                 }\r
17215                                         }\r
17216                                 }\r
17217 \r
17218                                 return format;\r
17219                         };\r
17220 \r
17221                         if (formatList && node) {\r
17222                                 // Check each format in list\r
17223                                 for (i = 0; i < formatList.length; i++) {\r
17224                                         format = formatList[i];\r
17225 \r
17226                                         // Name name, attributes, styles and classes\r
17227                                         if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {\r
17228                                                 // Match classes\r
17229                                                 if (classes = format.classes) {\r
17230                                                         for (i = 0; i < classes.length; i++) {\r
17231                                                                 if (!dom.hasClass(node, classes[i]))\r
17232                                                                         return;\r
17233                                                         }\r
17234                                                 }\r
17235 \r
17236                                                 return format;\r
17237                                         }\r
17238                                 }\r
17239                         }\r
17240                 };\r
17241 \r
17242                 function match(name, vars, node) {\r
17243                         var startNode;\r
17244 \r
17245                         function matchParents(node) {\r
17246                                 // Find first node with similar format settings\r
17247                                 node = dom.getParent(node, function(node) {\r
17248                                         return !!matchNode(node, name, vars, true);\r
17249                                 });\r
17250 \r
17251                                 // Do an exact check on the similar format element\r
17252                                 return matchNode(node, name, vars);\r
17253                         };\r
17254 \r
17255                         // Check specified node\r
17256                         if (node)\r
17257                                 return matchParents(node);\r
17258 \r
17259                         // Check selected node\r
17260                         node = selection.getNode();\r
17261                         if (matchParents(node))\r
17262                                 return TRUE;\r
17263 \r
17264                         // Check start node if it's different\r
17265                         startNode = selection.getStart();\r
17266                         if (startNode != node) {\r
17267                                 if (matchParents(startNode))\r
17268                                         return TRUE;\r
17269                         }\r
17270 \r
17271                         return FALSE;\r
17272                 };\r
17273 \r
17274                 function matchAll(names, vars) {\r
17275                         var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;\r
17276 \r
17277                         // Check start of selection for formats\r
17278                         startElement = selection.getStart();\r
17279                         dom.getParent(startElement, function(node) {\r
17280                                 var i, name;\r
17281 \r
17282                                 for (i = 0; i < names.length; i++) {\r
17283                                         name = names[i];\r
17284 \r
17285                                         if (!checkedMap[name] && matchNode(node, name, vars)) {\r
17286                                                 checkedMap[name] = true;\r
17287                                                 matchedFormatNames.push(name);\r
17288                                         }\r
17289                                 }\r
17290                         }, dom.getRoot());\r
17291 \r
17292                         return matchedFormatNames;\r
17293                 };\r
17294 \r
17295                 function canApply(name) {\r
17296                         var formatList = get(name), startNode, parents, i, x, selector;\r
17297 \r
17298                         if (formatList) {\r
17299                                 startNode = selection.getStart();\r
17300                                 parents = getParents(startNode);\r
17301 \r
17302                                 for (x = formatList.length - 1; x >= 0; x--) {\r
17303                                         selector = formatList[x].selector;\r
17304 \r
17305                                         // Format is not selector based, then always return TRUE\r
17306                                         if (!selector)\r
17307                                                 return TRUE;\r
17308 \r
17309                                         for (i = parents.length - 1; i >= 0; i--) {\r
17310                                                 if (dom.is(parents[i], selector))\r
17311                                                         return TRUE;\r
17312                                         }\r
17313                                 }\r
17314                         }\r
17315 \r
17316                         return FALSE;\r
17317                 };\r
17318 \r
17319                 function formatChanged(formats, callback, similar) {\r
17320                         var currentFormats;\r
17321 \r
17322                         // Setup format node change logic\r
17323                         if (!formatChangeData) {\r
17324                                 formatChangeData = {};\r
17325                                 currentFormats = {};\r
17326 \r
17327                                 ed.onNodeChange.addToTop(function(ed, cm, node) {\r
17328                                         var parents = getParents(node), matchedFormats = {};\r
17329 \r
17330                                         // Check for new formats\r
17331                                         each(formatChangeData, function(callbacks, format) {\r
17332                                                 each(parents, function(node) {\r
17333                                                         if (matchNode(node, format, {}, callbacks.similar)) {\r
17334                                                                 if (!currentFormats[format]) {\r
17335                                                                         // Execute callbacks\r
17336                                                                         each(callbacks, function(callback) {\r
17337                                                                                 callback(true, {node: node, format: format, parents: parents});\r
17338                                                                         });\r
17339 \r
17340                                                                         currentFormats[format] = callbacks;\r
17341                                                                 }\r
17342 \r
17343                                                                 matchedFormats[format] = callbacks;\r
17344                                                                 return false;\r
17345                                                         }\r
17346                                                 });\r
17347                                         });\r
17348 \r
17349                                         // Check if current formats still match\r
17350                                         each(currentFormats, function(callbacks, format) {\r
17351                                                 if (!matchedFormats[format]) {\r
17352                                                         delete currentFormats[format];\r
17353 \r
17354                                                         each(callbacks, function(callback) {\r
17355                                                                 callback(false, {node: node, format: format, parents: parents});\r
17356                                                         });\r
17357                                                 }\r
17358                                         });\r
17359                                 });\r
17360                         }\r
17361 \r
17362                         // Add format listeners\r
17363                         each(formats.split(','), function(format) {\r
17364                                 if (!formatChangeData[format]) {\r
17365                                         formatChangeData[format] = [];\r
17366                                         formatChangeData[format].similar = similar;\r
17367                                 }\r
17368 \r
17369                                 formatChangeData[format].push(callback);\r
17370                         });\r
17371 \r
17372                         return this;\r
17373                 };\r
17374 \r
17375                 // Expose to public\r
17376                 tinymce.extend(this, {\r
17377                         get : get,\r
17378                         register : register,\r
17379                         apply : apply,\r
17380                         remove : remove,\r
17381                         toggle : toggle,\r
17382                         match : match,\r
17383                         matchAll : matchAll,\r
17384                         matchNode : matchNode,\r
17385                         canApply : canApply,\r
17386                         formatChanged: formatChanged\r
17387                 });\r
17388 \r
17389                 // Initialize\r
17390                 defaultFormats();\r
17391                 addKeyboardShortcuts();\r
17392 \r
17393                 // Private functions\r
17394 \r
17395                 function matchName(node, format) {\r
17396                         // Check for inline match\r
17397                         if (isEq(node, format.inline))\r
17398                                 return TRUE;\r
17399 \r
17400                         // Check for block match\r
17401                         if (isEq(node, format.block))\r
17402                                 return TRUE;\r
17403 \r
17404                         // Check for selector match\r
17405                         if (format.selector)\r
17406                                 return dom.is(node, format.selector);\r
17407                 };\r
17408 \r
17409                 function isEq(str1, str2) {\r
17410                         str1 = str1 || '';\r
17411                         str2 = str2 || '';\r
17412 \r
17413                         str1 = '' + (str1.nodeName || str1);\r
17414                         str2 = '' + (str2.nodeName || str2);\r
17415 \r
17416                         return str1.toLowerCase() == str2.toLowerCase();\r
17417                 };\r
17418 \r
17419                 function getStyle(node, name) {\r
17420                         var styleVal = dom.getStyle(node, name);\r
17421 \r
17422                         // Force the format to hex\r
17423                         if (name == 'color' || name == 'backgroundColor')\r
17424                                 styleVal = dom.toHex(styleVal);\r
17425 \r
17426                         // Opera will return bold as 700\r
17427                         if (name == 'fontWeight' && styleVal == 700)\r
17428                                 styleVal = 'bold';\r
17429 \r
17430                         return '' + styleVal;\r
17431                 };\r
17432 \r
17433                 function replaceVars(value, vars) {\r
17434                         if (typeof(value) != "string")\r
17435                                 value = value(vars);\r
17436                         else if (vars) {\r
17437                                 value = value.replace(/%(\w+)/g, function(str, name) {\r
17438                                         return vars[name] || str;\r
17439                                 });\r
17440                         }\r
17441 \r
17442                         return value;\r
17443                 };\r
17444 \r
17445                 function isWhiteSpaceNode(node) {\r
17446                         return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);\r
17447                 };\r
17448 \r
17449                 function wrap(node, name, attrs) {\r
17450                         var wrapper = dom.create(name, attrs);\r
17451 \r
17452                         node.parentNode.insertBefore(wrapper, node);\r
17453                         wrapper.appendChild(node);\r
17454 \r
17455                         return wrapper;\r
17456                 };\r
17457 \r
17458                 function expandRng(rng, format, remove) {\r
17459                         var sibling, lastIdx, leaf, endPoint,\r
17460                                 startContainer = rng.startContainer,\r
17461                                 startOffset = rng.startOffset,\r
17462                                 endContainer = rng.endContainer,\r
17463                                 endOffset = rng.endOffset;\r
17464 \r
17465                         // This function walks up the tree if there is no siblings before/after the node\r
17466                         function findParentContainer(start) {\r
17467                                 var container, parent, child, sibling, siblingName, root;\r
17468 \r
17469                                 container = parent = start ? startContainer : endContainer;\r
17470                                 siblingName = start ? 'previousSibling' : 'nextSibling';\r
17471                                 root = dom.getRoot();\r
17472 \r
17473                                 function isBogusBr(node) {\r
17474                                         return node.nodeName == "BR" && node.getAttribute('data-mce-bogus') && !node.nextSibling;\r
17475                                 };\r
17476 \r
17477                                 // If it's a text node and the offset is inside the text\r
17478                                 if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {\r
17479                                         if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {\r
17480                                                 return container;\r
17481                                         }\r
17482                                 }\r
17483 \r
17484                                 for (;;) {\r
17485                                         // Stop expanding on block elements\r
17486                                         if (!format[0].block_expand && isBlock(parent))\r
17487                                                 return parent;\r
17488 \r
17489                                         // Walk left/right\r
17490                                         for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {\r
17491                                                 if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling) && !isBogusBr(sibling)) {\r
17492                                                         return parent;\r
17493                                                 }\r
17494                                         }\r
17495 \r
17496                                         // Check if we can move up are we at root level or body level\r
17497                                         if (parent.parentNode == root) {\r
17498                                                 container = parent;\r
17499                                                 break;\r
17500                                         }\r
17501 \r
17502                                         parent = parent.parentNode;\r
17503                                 }\r
17504 \r
17505                                 return container;\r
17506                         };\r
17507 \r
17508                         // This function walks down the tree to find the leaf at the selection.\r
17509                         // The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.\r
17510                         function findLeaf(node, offset) {\r
17511                                 if (offset === undef)\r
17512                                         offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
17513                                 while (node && node.hasChildNodes()) {\r
17514                                         node = node.childNodes[offset];\r
17515                                         if (node)\r
17516                                                 offset = node.nodeType === 3 ? node.length : node.childNodes.length;\r
17517                                 }\r
17518                                 return { node: node, offset: offset };\r
17519                         }\r
17520 \r
17521                         // If index based start position then resolve it\r
17522                         if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {\r
17523                                 lastIdx = startContainer.childNodes.length - 1;\r
17524                                 startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];\r
17525 \r
17526                                 if (startContainer.nodeType == 3)\r
17527                                         startOffset = 0;\r
17528                         }\r
17529 \r
17530                         // If index based end position then resolve it\r
17531                         if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {\r
17532                                 lastIdx = endContainer.childNodes.length - 1;\r
17533                                 endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];\r
17534 \r
17535                                 if (endContainer.nodeType == 3)\r
17536                                         endOffset = endContainer.nodeValue.length;\r
17537                         }\r
17538 \r
17539                         // Expands the node to the closes contentEditable false element if it exists\r
17540                         function findParentContentEditable(node) {\r
17541                                 var parent = node;\r
17542 \r
17543                                 while (parent) {\r
17544                                         if (parent.nodeType === 1 && getContentEditable(parent)) {\r
17545                                                 return getContentEditable(parent) === "false" ? parent : node;\r
17546                                         }\r
17547 \r
17548                                         parent = parent.parentNode;\r
17549                                 }\r
17550 \r
17551                                 return node;\r
17552                         };\r
17553 \r
17554                         function findWordEndPoint(container, offset, start) {\r
17555                                 var walker, node, pos, lastTextNode;\r
17556 \r
17557                                 function findSpace(node, offset) {\r
17558                                         var pos, pos2, str = node.nodeValue;\r
17559 \r
17560                                         if (typeof(offset) == "undefined") {\r
17561                                                 offset = start ? str.length : 0;\r
17562                                         }\r
17563 \r
17564                                         if (start) {\r
17565                                                 pos = str.lastIndexOf(' ', offset);\r
17566                                                 pos2 = str.lastIndexOf('\u00a0', offset);\r
17567                                                 pos = pos > pos2 ? pos : pos2;\r
17568 \r
17569                                                 // Include the space on remove to avoid tag soup\r
17570                                                 if (pos !== -1 && !remove) {\r
17571                                                         pos++;\r
17572                                                 }\r
17573                                         } else {\r
17574                                                 pos = str.indexOf(' ', offset);\r
17575                                                 pos2 = str.indexOf('\u00a0', offset);\r
17576                                                 pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;\r
17577                                         }\r
17578 \r
17579                                         return pos;\r
17580                                 };\r
17581 \r
17582                                 if (container.nodeType === 3) {\r
17583                                         pos = findSpace(container, offset);\r
17584 \r
17585                                         if (pos !== -1) {\r
17586                                                 return {container : container, offset : pos};\r
17587                                         }\r
17588 \r
17589                                         lastTextNode = container;\r
17590                                 }\r
17591 \r
17592                                 // Walk the nodes inside the block\r
17593                                 walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());\r
17594                                 while (node = walker[start ? 'prev' : 'next']()) {\r
17595                                         if (node.nodeType === 3) {\r
17596                                                 lastTextNode = node;\r
17597                                                 pos = findSpace(node);\r
17598 \r
17599                                                 if (pos !== -1) {\r
17600                                                         return {container : node, offset : pos};\r
17601                                                 }\r
17602                                         } else if (isBlock(node)) {\r
17603                                                 break;\r
17604                                         }\r
17605                                 }\r
17606 \r
17607                                 if (lastTextNode) {\r
17608                                         if (start) {\r
17609                                                 offset = 0;\r
17610                                         } else {\r
17611                                                 offset = lastTextNode.length;\r
17612                                         }\r
17613 \r
17614                                         return {container: lastTextNode, offset: offset};\r
17615                                 }\r
17616                         };\r
17617 \r
17618                         function findSelectorEndPoint(container, sibling_name) {\r
17619                                 var parents, i, y, curFormat;\r
17620 \r
17621                                 if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])\r
17622                                         container = container[sibling_name];\r
17623 \r
17624                                 parents = getParents(container);\r
17625                                 for (i = 0; i < parents.length; i++) {\r
17626                                         for (y = 0; y < format.length; y++) {\r
17627                                                 curFormat = format[y];\r
17628 \r
17629                                                 // If collapsed state is set then skip formats that doesn't match that\r
17630                                                 if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)\r
17631                                                         continue;\r
17632 \r
17633                                                 if (dom.is(parents[i], curFormat.selector))\r
17634                                                         return parents[i];\r
17635                                         }\r
17636                                 }\r
17637 \r
17638                                 return container;\r
17639                         };\r
17640 \r
17641                         function findBlockEndPoint(container, sibling_name, sibling_name2) {\r
17642                                 var node;\r
17643 \r
17644                                 // Expand to block of similar type\r
17645                                 if (!format[0].wrapper)\r
17646                                         node = dom.getParent(container, format[0].block);\r
17647 \r
17648                                 // Expand to first wrappable block element or any block element\r
17649                                 if (!node)\r
17650                                         node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isTextBlock);\r
17651 \r
17652                                 // Exclude inner lists from wrapping\r
17653                                 if (node && format[0].wrapper)\r
17654                                         node = getParents(node, 'ul,ol').reverse()[0] || node;\r
17655 \r
17656                                 // Didn't find a block element look for first/last wrappable element\r
17657                                 if (!node) {\r
17658                                         node = container;\r
17659 \r
17660                                         while (node[sibling_name] && !isBlock(node[sibling_name])) {\r
17661                                                 node = node[sibling_name];\r
17662 \r
17663                                                 // Break on BR but include it will be removed later on\r
17664                                                 // we can't remove it now since we need to check if it can be wrapped\r
17665                                                 if (isEq(node, 'br'))\r
17666                                                         break;\r
17667                                         }\r
17668                                 }\r
17669 \r
17670                                 return node || container;\r
17671                         };\r
17672 \r
17673                         // Expand to closest contentEditable element\r
17674                         startContainer = findParentContentEditable(startContainer);\r
17675                         endContainer = findParentContentEditable(endContainer);\r
17676 \r
17677                         // Exclude bookmark nodes if possible\r
17678                         if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {\r
17679                                 startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;\r
17680                                 startContainer = startContainer.nextSibling || startContainer;\r
17681 \r
17682                                 if (startContainer.nodeType == 3)\r
17683                                         startOffset = 0;\r
17684                         }\r
17685 \r
17686                         if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {\r
17687                                 endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;\r
17688                                 endContainer = endContainer.previousSibling || endContainer;\r
17689 \r
17690                                 if (endContainer.nodeType == 3)\r
17691                                         endOffset = endContainer.length;\r
17692                         }\r
17693 \r
17694                         if (format[0].inline) {\r
17695                                 if (rng.collapsed) {\r
17696                                         // Expand left to closest word boundery\r
17697                                         endPoint = findWordEndPoint(startContainer, startOffset, true);\r
17698                                         if (endPoint) {\r
17699                                                 startContainer = endPoint.container;\r
17700                                                 startOffset = endPoint.offset;\r
17701                                         }\r
17702 \r
17703                                         // Expand right to closest word boundery\r
17704                                         endPoint = findWordEndPoint(endContainer, endOffset);\r
17705                                         if (endPoint) {\r
17706                                                 endContainer = endPoint.container;\r
17707                                                 endOffset = endPoint.offset;\r
17708                                         }\r
17709                                 }\r
17710 \r
17711                                 // Avoid applying formatting to a trailing space.\r
17712                                 leaf = findLeaf(endContainer, endOffset);\r
17713                                 if (leaf.node) {\r
17714                                         while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)\r
17715                                                 leaf = findLeaf(leaf.node.previousSibling);\r
17716 \r
17717                                         if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&\r
17718                                                         leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {\r
17719 \r
17720                                                 if (leaf.offset > 1) {\r
17721                                                         endContainer = leaf.node;\r
17722                                                         endContainer.splitText(leaf.offset - 1);\r
17723                                                 }\r
17724                                         }\r
17725                                 }\r
17726                         }\r
17727 \r
17728                         // Move start/end point up the tree if the leaves are sharp and if we are in different containers\r
17729                         // Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!\r
17730                         // This will reduce the number of wrapper elements that needs to be created\r
17731                         // Move start point up the tree\r
17732                         if (format[0].inline || format[0].block_expand) {\r
17733                                 if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {\r
17734                                         startContainer = findParentContainer(true);\r
17735                                 }\r
17736 \r
17737                                 if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {\r
17738                                         endContainer = findParentContainer();\r
17739                                 }\r
17740                         }\r
17741 \r
17742                         // Expand start/end container to matching selector\r
17743                         if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {\r
17744                                 // Find new startContainer/endContainer if there is better one\r
17745                                 startContainer = findSelectorEndPoint(startContainer, 'previousSibling');\r
17746                                 endContainer = findSelectorEndPoint(endContainer, 'nextSibling');\r
17747                         }\r
17748 \r
17749                         // Expand start/end container to matching block element or text node\r
17750                         if (format[0].block || format[0].selector) {\r
17751                                 // Find new startContainer/endContainer if there is better one\r
17752                                 startContainer = findBlockEndPoint(startContainer, 'previousSibling');\r
17753                                 endContainer = findBlockEndPoint(endContainer, 'nextSibling');\r
17754 \r
17755                                 // Non block element then try to expand up the leaf\r
17756                                 if (format[0].block) {\r
17757                                         if (!isBlock(startContainer))\r
17758                                                 startContainer = findParentContainer(true);\r
17759 \r
17760                                         if (!isBlock(endContainer))\r
17761                                                 endContainer = findParentContainer();\r
17762                                 }\r
17763                         }\r
17764 \r
17765                         // Setup index for startContainer\r
17766                         if (startContainer.nodeType == 1) {\r
17767                                 startOffset = nodeIndex(startContainer);\r
17768                                 startContainer = startContainer.parentNode;\r
17769                         }\r
17770 \r
17771                         // Setup index for endContainer\r
17772                         if (endContainer.nodeType == 1) {\r
17773                                 endOffset = nodeIndex(endContainer) + 1;\r
17774                                 endContainer = endContainer.parentNode;\r
17775                         }\r
17776 \r
17777                         // Return new range like object\r
17778                         return {\r
17779                                 startContainer : startContainer,\r
17780                                 startOffset : startOffset,\r
17781                                 endContainer : endContainer,\r
17782                                 endOffset : endOffset\r
17783                         };\r
17784                 }\r
17785 \r
17786                 function removeFormat(format, vars, node, compare_node) {\r
17787                         var i, attrs, stylesModified;\r
17788 \r
17789                         // Check if node matches format\r
17790                         if (!matchName(node, format))\r
17791                                 return FALSE;\r
17792 \r
17793                         // Should we compare with format attribs and styles\r
17794                         if (format.remove != 'all') {\r
17795                                 // Remove styles\r
17796                                 each(format.styles, function(value, name) {\r
17797                                         value = replaceVars(value, vars);\r
17798 \r
17799                                         // Indexed array\r
17800                                         if (typeof(name) === 'number') {\r
17801                                                 name = value;\r
17802                                                 compare_node = 0;\r
17803                                         }\r
17804 \r
17805                                         if (!compare_node || isEq(getStyle(compare_node, name), value))\r
17806                                                 dom.setStyle(node, name, '');\r
17807 \r
17808                                         stylesModified = 1;\r
17809                                 });\r
17810 \r
17811                                 // Remove style attribute if it's empty\r
17812                                 if (stylesModified && dom.getAttrib(node, 'style') == '') {\r
17813                                         node.removeAttribute('style');\r
17814                                         node.removeAttribute('data-mce-style');\r
17815                                 }\r
17816 \r
17817                                 // Remove attributes\r
17818                                 each(format.attributes, function(value, name) {\r
17819                                         var valueOut;\r
17820 \r
17821                                         value = replaceVars(value, vars);\r
17822 \r
17823                                         // Indexed array\r
17824                                         if (typeof(name) === 'number') {\r
17825                                                 name = value;\r
17826                                                 compare_node = 0;\r
17827                                         }\r
17828 \r
17829                                         if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {\r
17830                                                 // Keep internal classes\r
17831                                                 if (name == 'class') {\r
17832                                                         value = dom.getAttrib(node, name);\r
17833                                                         if (value) {\r
17834                                                                 // Build new class value where everything is removed except the internal prefixed classes\r
17835                                                                 valueOut = '';\r
17836                                                                 each(value.split(/\s+/), function(cls) {\r
17837                                                                         if (/mce\w+/.test(cls))\r
17838                                                                                 valueOut += (valueOut ? ' ' : '') + cls;\r
17839                                                                 });\r
17840 \r
17841                                                                 // We got some internal classes left\r
17842                                                                 if (valueOut) {\r
17843                                                                         dom.setAttrib(node, name, valueOut);\r
17844                                                                         return;\r
17845                                                                 }\r
17846                                                         }\r
17847                                                 }\r
17848 \r
17849                                                 // IE6 has a bug where the attribute doesn't get removed correctly\r
17850                                                 if (name == "class")\r
17851                                                         node.removeAttribute('className');\r
17852 \r
17853                                                 // Remove mce prefixed attributes\r
17854                                                 if (MCE_ATTR_RE.test(name))\r
17855                                                         node.removeAttribute('data-mce-' + name);\r
17856 \r
17857                                                 node.removeAttribute(name);\r
17858                                         }\r
17859                                 });\r
17860 \r
17861                                 // Remove classes\r
17862                                 each(format.classes, function(value) {\r
17863                                         value = replaceVars(value, vars);\r
17864 \r
17865                                         if (!compare_node || dom.hasClass(compare_node, value))\r
17866                                                 dom.removeClass(node, value);\r
17867                                 });\r
17868 \r
17869                                 // Check for non internal attributes\r
17870                                 attrs = dom.getAttribs(node);\r
17871                                 for (i = 0; i < attrs.length; i++) {\r
17872                                         if (attrs[i].nodeName.indexOf('_') !== 0)\r
17873                                                 return FALSE;\r
17874                                 }\r
17875                         }\r
17876 \r
17877                         // Remove the inline child if it's empty for example <b> or <span>\r
17878                         if (format.remove != 'none') {\r
17879                                 removeNode(node, format);\r
17880                                 return TRUE;\r
17881                         }\r
17882                 };\r
17883 \r
17884                 function removeNode(node, format) {\r
17885                         var parentNode = node.parentNode, rootBlockElm;\r
17886 \r
17887                         function find(node, next, inc) {\r
17888                                 node = getNonWhiteSpaceSibling(node, next, inc);\r
17889 \r
17890                                 return !node || (node.nodeName == 'BR' || isBlock(node));\r
17891                         };\r
17892 \r
17893                         if (format.block) {\r
17894                                 if (!forcedRootBlock) {\r
17895                                         // Append BR elements if needed before we remove the block\r
17896                                         if (isBlock(node) && !isBlock(parentNode)) {\r
17897                                                 if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))\r
17898                                                         node.insertBefore(dom.create('br'), node.firstChild);\r
17899 \r
17900                                                 if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))\r
17901                                                         node.appendChild(dom.create('br'));\r
17902                                         }\r
17903                                 } else {\r
17904                                         // Wrap the block in a forcedRootBlock if we are at the root of document\r
17905                                         if (parentNode == dom.getRoot()) {\r
17906                                                 if (!format.list_block || !isEq(node, format.list_block)) {\r
17907                                                         each(tinymce.grep(node.childNodes), function(node) {\r
17908                                                                 if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {\r
17909                                                                         if (!rootBlockElm)\r
17910                                                                                 rootBlockElm = wrap(node, forcedRootBlock);\r
17911                                                                         else\r
17912                                                                                 rootBlockElm.appendChild(node);\r
17913                                                                 } else\r
17914                                                                         rootBlockElm = 0;\r
17915                                                         });\r
17916                                                 }\r
17917                                         }\r
17918                                 }\r
17919                         }\r
17920 \r
17921                         // Never remove nodes that isn't the specified inline element if a selector is specified too\r
17922                         if (format.selector && format.inline && !isEq(format.inline, node))\r
17923                                 return;\r
17924 \r
17925                         dom.remove(node, 1);\r
17926                 };\r
17927 \r
17928                 function getNonWhiteSpaceSibling(node, next, inc) {\r
17929                         if (node) {\r
17930                                 next = next ? 'nextSibling' : 'previousSibling';\r
17931 \r
17932                                 for (node = inc ? node : node[next]; node; node = node[next]) {\r
17933                                         if (node.nodeType == 1 || !isWhiteSpaceNode(node))\r
17934                                                 return node;\r
17935                                 }\r
17936                         }\r
17937                 };\r
17938 \r
17939                 function isBookmarkNode(node) {\r
17940                         return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';\r
17941                 };\r
17942 \r
17943                 function mergeSiblings(prev, next) {\r
17944                         var marker, sibling, tmpSibling;\r
17945 \r
17946                         function compareElements(node1, node2) {\r
17947                                 // Not the same name\r
17948                                 if (node1.nodeName != node2.nodeName)\r
17949                                         return FALSE;\r
17950 \r
17951                                 function getAttribs(node) {\r
17952                                         var attribs = {};\r
17953 \r
17954                                         each(dom.getAttribs(node), function(attr) {\r
17955                                                 var name = attr.nodeName.toLowerCase();\r
17956 \r
17957                                                 // Don't compare internal attributes or style\r
17958                                                 if (name.indexOf('_') !== 0 && name !== 'style')\r
17959                                                         attribs[name] = dom.getAttrib(node, name);\r
17960                                         });\r
17961 \r
17962                                         return attribs;\r
17963                                 };\r
17964 \r
17965                                 function compareObjects(obj1, obj2) {\r
17966                                         var value, name;\r
17967 \r
17968                                         for (name in obj1) {\r
17969                                                 // Obj1 has item obj2 doesn't have\r
17970                                                 if (obj1.hasOwnProperty(name)) {\r
17971                                                         value = obj2[name];\r
17972 \r
17973                                                         // Obj2 doesn't have obj1 item\r
17974                                                         if (value === undef)\r
17975                                                                 return FALSE;\r
17976 \r
17977                                                         // Obj2 item has a different value\r
17978                                                         if (obj1[name] != value)\r
17979                                                                 return FALSE;\r
17980 \r
17981                                                         // Delete similar value\r
17982                                                         delete obj2[name];\r
17983                                                 }\r
17984                                         }\r
17985 \r
17986                                         // Check if obj 2 has something obj 1 doesn't have\r
17987                                         for (name in obj2) {\r
17988                                                 // Obj2 has item obj1 doesn't have\r
17989                                                 if (obj2.hasOwnProperty(name))\r
17990                                                         return FALSE;\r
17991                                         }\r
17992 \r
17993                                         return TRUE;\r
17994                                 };\r
17995 \r
17996                                 // Attribs are not the same\r
17997                                 if (!compareObjects(getAttribs(node1), getAttribs(node2)))\r
17998                                         return FALSE;\r
17999 \r
18000                                 // Styles are not the same\r
18001                                 if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))\r
18002                                         return FALSE;\r
18003 \r
18004                                 return TRUE;\r
18005                         };\r
18006 \r
18007                         function findElementSibling(node, sibling_name) {\r
18008                                 for (sibling = node; sibling; sibling = sibling[sibling_name]) {\r
18009                                         if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)\r
18010                                                 return node;\r
18011 \r
18012                                         if (sibling.nodeType == 1 && !isBookmarkNode(sibling))\r
18013                                                 return sibling;\r
18014                                 }\r
18015 \r
18016                                 return node;\r
18017                         };\r
18018 \r
18019                         // Check if next/prev exists and that they are elements\r
18020                         if (prev && next) {\r
18021                                 // If previous sibling is empty then jump over it\r
18022                                 prev = findElementSibling(prev, 'previousSibling');\r
18023                                 next = findElementSibling(next, 'nextSibling');\r
18024 \r
18025                                 // Compare next and previous nodes\r
18026                                 if (compareElements(prev, next)) {\r
18027                                         // Append nodes between\r
18028                                         for (sibling = prev.nextSibling; sibling && sibling != next;) {\r
18029                                                 tmpSibling = sibling;\r
18030                                                 sibling = sibling.nextSibling;\r
18031                                                 prev.appendChild(tmpSibling);\r
18032                                         }\r
18033 \r
18034                                         // Remove next node\r
18035                                         dom.remove(next);\r
18036 \r
18037                                         // Move children into prev node\r
18038                                         each(tinymce.grep(next.childNodes), function(node) {\r
18039                                                 prev.appendChild(node);\r
18040                                         });\r
18041 \r
18042                                         return prev;\r
18043                                 }\r
18044                         }\r
18045 \r
18046                         return next;\r
18047                 };\r
18048 \r
18049                 function isTextBlock(name) {\r
18050                         return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);\r
18051                 };\r
18052 \r
18053                 function getContainer(rng, start) {\r
18054                         var container, offset, lastIdx, walker;\r
18055 \r
18056                         container = rng[start ? 'startContainer' : 'endContainer'];\r
18057                         offset = rng[start ? 'startOffset' : 'endOffset'];\r
18058 \r
18059                         if (container.nodeType == 1) {\r
18060                                 lastIdx = container.childNodes.length - 1;\r
18061 \r
18062                                 if (!start && offset)\r
18063                                         offset--;\r
18064 \r
18065                                 container = container.childNodes[offset > lastIdx ? lastIdx : offset];\r
18066                         }\r
18067 \r
18068                         // If start text node is excluded then walk to the next node\r
18069                         if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {\r
18070                                 container = new TreeWalker(container, ed.getBody()).next() || container;\r
18071                         }\r
18072 \r
18073                         // If end text node is excluded then walk to the previous node\r
18074                         if (container.nodeType === 3 && !start && offset === 0) {\r
18075                                 container = new TreeWalker(container, ed.getBody()).prev() || container;\r
18076                         }\r
18077 \r
18078                         return container;\r
18079                 };\r
18080 \r
18081                 function performCaretAction(type, name, vars) {\r
18082                         var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;\r
18083 \r
18084                         // Creates a caret container bogus element\r
18085                         function createCaretContainer(fill) {\r
18086                                 var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});\r
18087 \r
18088                                 if (fill) {\r
18089                                         caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));\r
18090                                 }\r
18091 \r
18092                                 return caretContainer;\r
18093                         };\r
18094 \r
18095                         function isCaretContainerEmpty(node, nodes) {\r
18096                                 while (node) {\r
18097                                         if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {\r
18098                                                 return false;\r
18099                                         }\r
18100 \r
18101                                         // Collect nodes\r
18102                                         if (nodes && node.nodeType === 1) {\r
18103                                                 nodes.push(node);\r
18104                                         }\r
18105 \r
18106                                         node = node.firstChild;\r
18107                                 }\r
18108 \r
18109                                 return true;\r
18110                         };\r
18111                         \r
18112                         // Returns any parent caret container element\r
18113                         function getParentCaretContainer(node) {\r
18114                                 while (node) {\r
18115                                         if (node.id === caretContainerId) {\r
18116                                                 return node;\r
18117                                         }\r
18118 \r
18119                                         node = node.parentNode;\r
18120                                 }\r
18121                         };\r
18122 \r
18123                         // Finds the first text node in the specified node\r
18124                         function findFirstTextNode(node) {\r
18125                                 var walker;\r
18126 \r
18127                                 if (node) {\r
18128                                         walker = new TreeWalker(node, node);\r
18129 \r
18130                                         for (node = walker.current(); node; node = walker.next()) {\r
18131                                                 if (node.nodeType === 3) {\r
18132                                                         return node;\r
18133                                                 }\r
18134                                         }\r
18135                                 }\r
18136                         };\r
18137 \r
18138                         // Removes the caret container for the specified node or all on the current document\r
18139                         function removeCaretContainer(node, move_caret) {\r
18140                                 var child, rng;\r
18141 \r
18142                                 if (!node) {\r
18143                                         node = getParentCaretContainer(selection.getStart());\r
18144 \r
18145                                         if (!node) {\r
18146                                                 while (node = dom.get(caretContainerId)) {\r
18147                                                         removeCaretContainer(node, false);\r
18148                                                 }\r
18149                                         }\r
18150                                 } else {\r
18151                                         rng = selection.getRng(true);\r
18152 \r
18153                                         if (isCaretContainerEmpty(node)) {\r
18154                                                 if (move_caret !== false) {\r
18155                                                         rng.setStartBefore(node);\r
18156                                                         rng.setEndBefore(node);\r
18157                                                 }\r
18158 \r
18159                                                 dom.remove(node);\r
18160                                         } else {\r
18161                                                 child = findFirstTextNode(node);\r
18162 \r
18163                                                 if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {\r
18164                                                         child = child.deleteData(0, 1);\r
18165                                                 }\r
18166 \r
18167                                                 dom.remove(node, 1);\r
18168                                         }\r
18169 \r
18170                                         selection.setRng(rng);\r
18171                                 }\r
18172                         };\r
18173                         \r
18174                         // Applies formatting to the caret postion\r
18175                         function applyCaretFormat() {\r
18176                                 var rng, caretContainer, textNode, offset, bookmark, container, text;\r
18177 \r
18178                                 rng = selection.getRng(true);\r
18179                                 offset = rng.startOffset;\r
18180                                 container = rng.startContainer;\r
18181                                 text = container.nodeValue;\r
18182 \r
18183                                 caretContainer = getParentCaretContainer(selection.getStart());\r
18184                                 if (caretContainer) {\r
18185                                         textNode = findFirstTextNode(caretContainer);\r
18186                                 }\r
18187 \r
18188                                 // Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character\r
18189                                 if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {\r
18190                                         // Get bookmark of caret position\r
18191                                         bookmark = selection.getBookmark();\r
18192 \r
18193                                         // Collapse bookmark range (WebKit)\r
18194                                         rng.collapse(true);\r
18195 \r
18196                                         // Expand the range to the closest word and split it at those points\r
18197                                         rng = expandRng(rng, get(name));\r
18198                                         rng = rangeUtils.split(rng);\r
18199 \r
18200                                         // Apply the format to the range\r
18201                                         apply(name, vars, rng);\r
18202 \r
18203                                         // Move selection back to caret position\r
18204                                         selection.moveToBookmark(bookmark);\r
18205                                 } else {\r
18206                                         if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {\r
18207                                                 caretContainer = createCaretContainer(true);\r
18208                                                 textNode = caretContainer.firstChild;\r
18209 \r
18210                                                 rng.insertNode(caretContainer);\r
18211                                                 offset = 1;\r
18212 \r
18213                                                 apply(name, vars, caretContainer);\r
18214                                         } else {\r
18215                                                 apply(name, vars, caretContainer);\r
18216                                         }\r
18217 \r
18218                                         // Move selection to text node\r
18219                                         selection.setCursorLocation(textNode, offset);\r
18220                                 }\r
18221                         };\r
18222 \r
18223                         function removeCaretFormat() {\r
18224                                 var rng = selection.getRng(true), container, offset, bookmark,\r
18225                                         hasContentAfter, node, formatNode, parents = [], i, caretContainer;\r
18226 \r
18227                                 container = rng.startContainer;\r
18228                                 offset = rng.startOffset;\r
18229                                 node = container;\r
18230 \r
18231                                 if (container.nodeType == 3) {\r
18232                                         if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {\r
18233                                                 hasContentAfter = true;\r
18234                                         }\r
18235 \r
18236                                         node = node.parentNode;\r
18237                                 }\r
18238 \r
18239                                 while (node) {\r
18240                                         if (matchNode(node, name, vars)) {\r
18241                                                 formatNode = node;\r
18242                                                 break;\r
18243                                         }\r
18244 \r
18245                                         if (node.nextSibling) {\r
18246                                                 hasContentAfter = true;\r
18247                                         }\r
18248 \r
18249                                         parents.push(node);\r
18250                                         node = node.parentNode;\r
18251                                 }\r
18252 \r
18253                                 // Node doesn't have the specified format\r
18254                                 if (!formatNode) {\r
18255                                         return;\r
18256                                 }\r
18257 \r
18258                                 // Is there contents after the caret then remove the format on the element\r
18259                                 if (hasContentAfter) {\r
18260                                         // Get bookmark of caret position\r
18261                                         bookmark = selection.getBookmark();\r
18262 \r
18263                                         // Collapse bookmark range (WebKit)\r
18264                                         rng.collapse(true);\r
18265 \r
18266                                         // Expand the range to the closest word and split it at those points\r
18267                                         rng = expandRng(rng, get(name), true);\r
18268                                         rng = rangeUtils.split(rng);\r
18269 \r
18270                                         // Remove the format from the range\r
18271                                         remove(name, vars, rng);\r
18272 \r
18273                                         // Move selection back to caret position\r
18274                                         selection.moveToBookmark(bookmark);\r
18275                                 } else {\r
18276                                         caretContainer = createCaretContainer();\r
18277 \r
18278                                         node = caretContainer;\r
18279                                         for (i = parents.length - 1; i >= 0; i--) {\r
18280                                                 node.appendChild(dom.clone(parents[i], false));\r
18281                                                 node = node.firstChild;\r
18282                                         }\r
18283 \r
18284                                         // Insert invisible character into inner most format element\r
18285                                         node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));\r
18286                                         node = node.firstChild;\r
18287 \r
18288                                         // Insert caret container after the formated node\r
18289                                         dom.insertAfter(caretContainer, formatNode);\r
18290 \r
18291                                         // Move selection to text node\r
18292                                         selection.setCursorLocation(node, 1);\r
18293                                 }\r
18294                         };\r
18295 \r
18296                         // Checks if the parent caret container node isn't empty if that is the case it\r
18297                         // will remove the bogus state on all children that isn't empty\r
18298                         function unmarkBogusCaretParents() {\r
18299                                 var i, caretContainer, node;\r
18300 \r
18301                                 caretContainer = getParentCaretContainer(selection.getStart());\r
18302                                 if (caretContainer && !dom.isEmpty(caretContainer)) {\r
18303                                         tinymce.walk(caretContainer, function(node) {\r
18304                                                 if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {\r
18305                                                         dom.setAttrib(node, 'data-mce-bogus', null);\r
18306                                                 }\r
18307                                         }, 'childNodes');\r
18308                                 }\r
18309                         };\r
18310 \r
18311                         // Only bind the caret events once\r
18312                         if (!self._hasCaretEvents) {\r
18313                                 // Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements\r
18314                                 ed.onBeforeGetContent.addToTop(function() {\r
18315                                         var nodes = [], i;\r
18316 \r
18317                                         if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {\r
18318                                                 // Mark children\r
18319                                                 i = nodes.length;\r
18320                                                 while (i--) {\r
18321                                                         dom.setAttrib(nodes[i], 'data-mce-bogus', '1');\r
18322                                                 }\r
18323                                         }\r
18324                                 });\r
18325 \r
18326                                 // Remove caret container on mouse up and on key up\r
18327                                 tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {\r
18328                                         ed[name].addToTop(function() {\r
18329                                                 removeCaretContainer();\r
18330                                                 unmarkBogusCaretParents();\r
18331                                         });\r
18332                                 });\r
18333 \r
18334                                 // Remove caret container on keydown and it's a backspace, enter or left/right arrow keys\r
18335                                 ed.onKeyDown.addToTop(function(ed, e) {\r
18336                                         var keyCode = e.keyCode;\r
18337 \r
18338                                         if (keyCode == 8 || keyCode == 37 || keyCode == 39) {\r
18339                                                 removeCaretContainer(getParentCaretContainer(selection.getStart()));\r
18340                                         }\r
18341 \r
18342                                         unmarkBogusCaretParents();\r
18343                                 });\r
18344 \r
18345                                 // Remove bogus state if they got filled by contents using editor.selection.setContent\r
18346                                 selection.onSetContent.add(unmarkBogusCaretParents);\r
18347 \r
18348                                 self._hasCaretEvents = true;\r
18349                         }\r
18350 \r
18351                         // Do apply or remove caret format\r
18352                         if (type == "apply") {\r
18353                                 applyCaretFormat();\r
18354                         } else {\r
18355                                 removeCaretFormat();\r
18356                         }\r
18357                 };\r
18358 \r
18359                 function moveStart(rng) {\r
18360                         var container = rng.startContainer,\r
18361                                         offset = rng.startOffset, isAtEndOfText,\r
18362                                         walker, node, nodes, tmpNode;\r
18363 \r
18364                         // Convert text node into index if possible\r
18365                         if (container.nodeType == 3 && offset >= container.nodeValue.length) {\r
18366                                 // Get the parent container location and walk from there\r
18367                                 offset = nodeIndex(container);\r
18368                                 container = container.parentNode;\r
18369                                 isAtEndOfText = true;\r
18370                         }\r
18371 \r
18372                         // Move startContainer/startOffset in to a suitable node\r
18373                         if (container.nodeType == 1) {\r
18374                                 nodes = container.childNodes;\r
18375                                 container = nodes[Math.min(offset, nodes.length - 1)];\r
18376                                 walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));\r
18377 \r
18378                                 // If offset is at end of the parent node walk to the next one\r
18379                                 if (offset > nodes.length - 1 || isAtEndOfText)\r
18380                                         walker.next();\r
18381 \r
18382                                 for (node = walker.current(); node; node = walker.next()) {\r
18383                                         if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {\r
18384                                                 // IE has a "neat" feature where it moves the start node into the closest element\r
18385                                                 // we can avoid this by inserting an element before it and then remove it after we set the selection\r
18386                                                 tmpNode = dom.create('a', null, INVISIBLE_CHAR);\r
18387                                                 node.parentNode.insertBefore(tmpNode, node);\r
18388 \r
18389                                                 // Set selection and remove tmpNode\r
18390                                                 rng.setStart(node, 0);\r
18391                                                 selection.setRng(rng);\r
18392                                                 dom.remove(tmpNode);\r
18393 \r
18394                                                 return;\r
18395                                         }\r
18396                                 }\r
18397                         }\r
18398                 };\r
18399         };\r
18400 })(tinymce);\r
18401 \r
18402 tinymce.onAddEditor.add(function(tinymce, ed) {\r
18403         var filters, fontSizes, dom, settings = ed.settings;\r
18404 \r
18405         function replaceWithSpan(node, styles) {\r
18406                 tinymce.each(styles, function(value, name) {\r
18407                         if (value)\r
18408                                 dom.setStyle(node, name, value);\r
18409                 });\r
18410 \r
18411                 dom.rename(node, 'span');\r
18412         };\r
18413 \r
18414         function convert(editor, params) {\r
18415                 dom = editor.dom;\r
18416 \r
18417                 if (settings.convert_fonts_to_spans) {\r
18418                         tinymce.each(dom.select('font,u,strike', params.node), function(node) {\r
18419                                 filters[node.nodeName.toLowerCase()](ed.dom, node);\r
18420                         });\r
18421                 }\r
18422         };\r
18423 \r
18424         if (settings.inline_styles) {\r
18425                 fontSizes = tinymce.explode(settings.font_size_legacy_values);\r
18426 \r
18427                 filters = {\r
18428                         font : function(dom, node) {\r
18429                                 replaceWithSpan(node, {\r
18430                                         backgroundColor : node.style.backgroundColor,\r
18431                                         color : node.color,\r
18432                                         fontFamily : node.face,\r
18433                                         fontSize : fontSizes[parseInt(node.size, 10) - 1]\r
18434                                 });\r
18435                         },\r
18436 \r
18437                         u : function(dom, node) {\r
18438                                 replaceWithSpan(node, {\r
18439                                         textDecoration : 'underline'\r
18440                                 });\r
18441                         },\r
18442 \r
18443                         strike : function(dom, node) {\r
18444                                 replaceWithSpan(node, {\r
18445                                         textDecoration : 'line-through'\r
18446                                 });\r
18447                         }\r
18448                 };\r
18449 \r
18450                 ed.onPreProcess.add(convert);\r
18451                 ed.onSetContent.add(convert);\r
18452 \r
18453                 ed.onInit.add(function() {\r
18454                         ed.selection.onSetContent.add(convert);\r
18455                 });\r
18456         }\r
18457 });\r
18458 \r
18459 (function(tinymce) {\r
18460         var TreeWalker = tinymce.dom.TreeWalker;\r
18461 \r
18462         tinymce.EnterKey = function(editor) {\r
18463                 var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();\r
18464 \r
18465                 function handleEnterKey(evt) {\r
18466                         var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,\r
18467                                 newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;\r
18468 \r
18469                         // Returns true if the block can be split into two blocks or not\r
18470                         function canSplitBlock(node) {\r
18471                                 return node &&\r
18472                                         dom.isBlock(node) &&\r
18473                                         !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&\r
18474                                         !/^(fixed|absolute)/i.test(node.style.position) && \r
18475                                         dom.getContentEditable(node) !== "true";\r
18476                         };\r
18477 \r
18478                         // Renders empty block on IE\r
18479                         function renderBlockOnIE(block) {\r
18480                                 var oldRng;\r
18481 \r
18482                                 if (tinymce.isIE && dom.isBlock(block)) {\r
18483                                         oldRng = selection.getRng();\r
18484                                         block.appendChild(dom.create('span', null, '\u00a0'));\r
18485                                         selection.select(block);\r
18486                                         block.lastChild.outerHTML = '';\r
18487                                         selection.setRng(oldRng);\r
18488                                 }\r
18489                         };\r
18490 \r
18491                         // Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>\r
18492                         function trimInlineElementsOnLeftSideOfBlock(block) {\r
18493                                 var node = block, firstChilds = [], i;\r
18494 \r
18495                                 // Find inner most first child ex: <p><i><b>*</b></i></p>\r
18496                                 while (node = node.firstChild) {\r
18497                                         if (dom.isBlock(node)) {\r
18498                                                 return;\r
18499                                         }\r
18500 \r
18501                                         if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
18502                                                 firstChilds.push(node);\r
18503                                         }\r
18504                                 }\r
18505 \r
18506                                 i = firstChilds.length;\r
18507                                 while (i--) {\r
18508                                         node = firstChilds[i];\r
18509                                         if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {\r
18510                                                 dom.remove(node);\r
18511                                         } else {\r
18512                                                 // Remove <a> </a> see #5381\r
18513                                                 if (node.nodeName == "A" && (node.innerText || node.textContent) === ' ') {\r
18514                                                         dom.remove(node);\r
18515                                                 }\r
18516                                         }\r
18517                                 }\r
18518                         };\r
18519                         \r
18520                         // 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
18521                         function moveToCaretPosition(root) {\r
18522                                 var walker, node, rng, y, viewPort, lastNode = root, tempElm;\r
18523 \r
18524                                 rng = dom.createRng();\r
18525 \r
18526                                 if (root.hasChildNodes()) {\r
18527                                         walker = new TreeWalker(root, root);\r
18528 \r
18529                                         while (node = walker.current()) {\r
18530                                                 if (node.nodeType == 3) {\r
18531                                                         rng.setStart(node, 0);\r
18532                                                         rng.setEnd(node, 0);\r
18533                                                         break;\r
18534                                                 }\r
18535 \r
18536                                                 if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {\r
18537                                                         rng.setStartBefore(node);\r
18538                                                         rng.setEndBefore(node);\r
18539                                                         break;\r
18540                                                 }\r
18541 \r
18542                                                 lastNode = node;\r
18543                                                 node = walker.next();\r
18544                                         }\r
18545 \r
18546                                         if (!node) {\r
18547                                                 rng.setStart(lastNode, 0);\r
18548                                                 rng.setEnd(lastNode, 0);\r
18549                                         }\r
18550                                 } else {\r
18551                                         if (root.nodeName == 'BR') {\r
18552                                                 if (root.nextSibling && dom.isBlock(root.nextSibling)) {\r
18553                                                         // Trick on older IE versions to render the caret before the BR between two lists\r
18554                                                         if (!documentMode || documentMode < 9) {\r
18555                                                                 tempElm = dom.create('br');\r
18556                                                                 root.parentNode.insertBefore(tempElm, root);\r
18557                                                         }\r
18558 \r
18559                                                         rng.setStartBefore(root);\r
18560                                                         rng.setEndBefore(root);\r
18561                                                 } else {\r
18562                                                         rng.setStartAfter(root);\r
18563                                                         rng.setEndAfter(root);\r
18564                                                 }\r
18565                                         } else {\r
18566                                                 rng.setStart(root, 0);\r
18567                                                 rng.setEnd(root, 0);\r
18568                                         }\r
18569                                 }\r
18570 \r
18571                                 selection.setRng(rng);\r
18572 \r
18573                                 // Remove tempElm created for old IE:s\r
18574                                 dom.remove(tempElm);\r
18575 \r
18576                                 viewPort = dom.getViewPort(editor.getWin());\r
18577 \r
18578                                 // 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
18579                                 y = dom.getPos(root).y;\r
18580                                 if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {\r
18581                                         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
18582                                 }\r
18583                         };\r
18584 \r
18585                         // Creates a new block element by cloning the current one or creating a new one if the name is specified\r
18586                         // This function will also copy any text formatting from the parent block and add it to the new one\r
18587                         function createNewBlock(name) {\r
18588                                 var node = container, block, clonedNode, caretNode;\r
18589 \r
18590                                 block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);\r
18591                                 caretNode = block;\r
18592 \r
18593                                 // Clone any parent styles\r
18594                                 if (settings.keep_styles !== false) {\r
18595                                         do {\r
18596                                                 if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {\r
18597                                                         // Never clone a caret containers\r
18598                                                         if (node.id == '_mce_caret') {\r
18599                                                                 continue;\r
18600                                                         }\r
18601 \r
18602                                                         clonedNode = node.cloneNode(false);\r
18603                                                         dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique\r
18604 \r
18605                                                         if (block.hasChildNodes()) {\r
18606                                                                 clonedNode.appendChild(block.firstChild);\r
18607                                                                 block.appendChild(clonedNode);\r
18608                                                         } else {\r
18609                                                                 caretNode = clonedNode;\r
18610                                                                 block.appendChild(clonedNode);\r
18611                                                         }\r
18612                                                 }\r
18613                                         } while (node = node.parentNode);\r
18614                                 }\r
18615 \r
18616                                 // BR is needed in empty blocks on non IE browsers\r
18617                                 if (!tinymce.isIE) {\r
18618                                         caretNode.innerHTML = '<br data-mce-bogus="1">';\r
18619                                 }\r
18620 \r
18621                                 return block;\r
18622                         };\r
18623 \r
18624                         // Returns true/false if the caret is at the start/end of the parent block element\r
18625                         function isCaretAtStartOrEndOfBlock(start) {\r
18626                                 var walker, node, name;\r
18627 \r
18628                                 // Caret is in the middle of a text node like "a|b"\r
18629                                 if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {\r
18630                                         return false;\r
18631                                 }\r
18632 \r
18633                                 // If after the last element in block node edge case for #5091\r
18634                                 if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {\r
18635                                         return true;\r
18636                                 }\r
18637 \r
18638                                 // If the caret if before the first element in parentBlock\r
18639                                 if (start && container.nodeType == 1 && container == parentBlock.firstChild) {\r
18640                                         return true;\r
18641                                 }\r
18642 \r
18643                                 // Caret can be before/after a table\r
18644                                 if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {\r
18645                                         return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);\r
18646                                 }\r
18647 \r
18648                                 // Walk the DOM and look for text nodes or non empty elements\r
18649                                 walker = new TreeWalker(container, parentBlock);\r
18650         \r
18651                                 // If caret is in beginning or end of a text block then jump to the next/previous node\r
18652                                 if (container.nodeType == 3) {\r
18653                                         if (start && offset == 0) {\r
18654                                                 walker.prev();\r
18655                                         } else if (!start && offset == container.nodeValue.length) {\r
18656                                                 walker.next();\r
18657                                         }\r
18658                                 }\r
18659 \r
18660                                 while (node = walker.current()) {\r
18661                                         if (node.nodeType === 1) {\r
18662                                                 // Ignore bogus elements\r
18663                                                 if (!node.getAttribute('data-mce-bogus')) {\r
18664                                                         // Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>\r
18665                                                         name = node.nodeName.toLowerCase();\r
18666                                                         if (nonEmptyElementsMap[name] && name !== 'br') {\r
18667                                                                 return false;\r
18668                                                         }\r
18669                                                 }\r
18670                                         } else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {\r
18671                                                 return false;\r
18672                                         }\r
18673 \r
18674                                         if (start) {\r
18675                                                 walker.prev();\r
18676                                         } else {\r
18677                                                 walker.next();\r
18678                                         }\r
18679                                 }\r
18680 \r
18681                                 return true;\r
18682                         };\r
18683 \r
18684                         // Wraps any text nodes or inline elements in the specified forced root block name\r
18685                         function wrapSelfAndSiblingsInDefaultBlock(container, offset) {\r
18686                                 var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';\r
18687 \r
18688                                 // Not in a block element or in a table cell or caption\r
18689                                 parentBlock = dom.getParent(container, dom.isBlock);\r
18690                                 if (!parentBlock || !canSplitBlock(parentBlock)) {\r
18691                                         parentBlock = parentBlock || editableRoot;\r
18692 \r
18693                                         if (!parentBlock.hasChildNodes()) {\r
18694                                                 newBlock = dom.create(blockName);\r
18695                                                 parentBlock.appendChild(newBlock);\r
18696                                                 rng.setStart(newBlock, 0);\r
18697                                                 rng.setEnd(newBlock, 0);\r
18698                                                 return newBlock;\r
18699                                         }\r
18700 \r
18701                                         // Find parent that is the first child of parentBlock\r
18702                                         node = container;\r
18703                                         while (node.parentNode != parentBlock) {\r
18704                                                 node = node.parentNode;\r
18705                                         }\r
18706 \r
18707                                         // Loop left to find start node start wrapping at\r
18708                                         while (node && !dom.isBlock(node)) {\r
18709                                                 startNode = node;\r
18710                                                 node = node.previousSibling;\r
18711                                         }\r
18712 \r
18713                                         if (startNode) {\r
18714                                                 newBlock = dom.create(blockName);\r
18715                                                 startNode.parentNode.insertBefore(newBlock, startNode);\r
18716 \r
18717                                                 // Start wrapping until we hit a block\r
18718                                                 node = startNode;\r
18719                                                 while (node && !dom.isBlock(node)) {\r
18720                                                         next = node.nextSibling;\r
18721                                                         newBlock.appendChild(node);\r
18722                                                         node = next;\r
18723                                                 }\r
18724 \r
18725                                                 // Restore range to it's past location\r
18726                                                 rng.setStart(container, offset);\r
18727                                                 rng.setEnd(container, offset);\r
18728                                         }\r
18729                                 }\r
18730 \r
18731                                 return container;\r
18732                         };\r
18733 \r
18734                         // Inserts a block or br before/after or in the middle of a split list of the LI is empty\r
18735                         function handleEmptyListItem() {\r
18736                                 function isFirstOrLastLi(first) {\r
18737                                         var node = containerBlock[first ? 'firstChild' : 'lastChild'];\r
18738 \r
18739                                         // Find first/last element since there might be whitespace there\r
18740                                         while (node) {\r
18741                                                 if (node.nodeType == 1) {\r
18742                                                         break;\r
18743                                                 }\r
18744 \r
18745                                                 node = node[first ? 'nextSibling' : 'previousSibling'];\r
18746                                         }\r
18747 \r
18748                                         return node === parentBlock;\r
18749                                 };\r
18750 \r
18751                                 newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');\r
18752 \r
18753                                 if (isFirstOrLastLi(true) && isFirstOrLastLi()) {\r
18754                                         // Is first and last list item then replace the OL/UL with a text block\r
18755                                         dom.replace(newBlock, containerBlock);\r
18756                                 } else if (isFirstOrLastLi(true)) {\r
18757                                         // First LI in list then remove LI and add text block before list\r
18758                                         containerBlock.parentNode.insertBefore(newBlock, containerBlock);\r
18759                                 } else if (isFirstOrLastLi()) {\r
18760                                         // Last LI in list then temove LI and add text block after list\r
18761                                         dom.insertAfter(newBlock, containerBlock);\r
18762                                         renderBlockOnIE(newBlock);\r
18763                                 } else {\r
18764                                         // Middle LI in list the split the list and insert a text block in the middle\r
18765                                         // Extract after fragment and insert it after the current block\r
18766                                         tmpRng = rng.cloneRange();\r
18767                                         tmpRng.setStartAfter(parentBlock);\r
18768                                         tmpRng.setEndAfter(containerBlock);\r
18769                                         fragment = tmpRng.extractContents();\r
18770                                         dom.insertAfter(fragment, containerBlock);\r
18771                                         dom.insertAfter(newBlock, containerBlock);\r
18772                                 }\r
18773 \r
18774                                 dom.remove(parentBlock);\r
18775                                 moveToCaretPosition(newBlock);\r
18776                                 undoManager.add();\r
18777                         };\r
18778 \r
18779                         // Walks the parent block to the right and look for BR elements\r
18780                         function hasRightSideBr() {\r
18781                                 var walker = new TreeWalker(container, parentBlock), node;\r
18782 \r
18783                                 while (node = walker.current()) {\r
18784                                         if (node.nodeName == 'BR') {\r
18785                                                 return true;\r
18786                                         }\r
18787 \r
18788                                         node = walker.next();\r
18789                                 }\r
18790                         }\r
18791                         \r
18792                         // Inserts a BR element if the forced_root_block option is set to false or empty string\r
18793                         function insertBr() {\r
18794                                 var brElm, extraBr, marker;\r
18795 \r
18796                                 if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {\r
18797                                         // Insert extra BR element at the end block elements\r
18798                                         if (!tinymce.isIE && !hasRightSideBr()) {\r
18799                                                 brElm = dom.create('br');\r
18800                                                 rng.insertNode(brElm);\r
18801                                                 rng.setStartAfter(brElm);\r
18802                                                 rng.setEndAfter(brElm);\r
18803                                                 extraBr = true;\r
18804                                         }\r
18805                                 }\r
18806 \r
18807                                 brElm = dom.create('br');\r
18808                                 rng.insertNode(brElm);\r
18809 \r
18810                                 // Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it\r
18811                                 if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {\r
18812                                         brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);\r
18813                                 }\r
18814 \r
18815                                 // Insert temp marker and scroll to that\r
18816                                 marker = dom.create('span', {}, '&nbsp;');\r
18817                                 brElm.parentNode.insertBefore(marker, brElm);\r
18818                                 selection.scrollIntoView(marker);\r
18819                                 dom.remove(marker);\r
18820 \r
18821                                 if (!extraBr) {\r
18822                                         rng.setStartAfter(brElm);\r
18823                                         rng.setEndAfter(brElm);\r
18824                                 } else {\r
18825                                         rng.setStartBefore(brElm);\r
18826                                         rng.setEndBefore(brElm);\r
18827                                 }\r
18828 \r
18829                                 selection.setRng(rng);\r
18830                                 undoManager.add();\r
18831                         };\r
18832 \r
18833                         // Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element\r
18834                         function trimLeadingLineBreaks(node) {\r
18835                                 do {\r
18836                                         if (node.nodeType === 3) {\r
18837                                                 node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');\r
18838                                         }\r
18839 \r
18840                                         node = node.firstChild;\r
18841                                 } while (node);\r
18842                         };\r
18843 \r
18844                         function getEditableRoot(node) {\r
18845                                 var root = dom.getRoot(), parent, editableRoot;\r
18846 \r
18847                                 // Get all parents until we hit a non editable parent or the root\r
18848                                 parent = node;\r
18849                                 while (parent !== root && dom.getContentEditable(parent) !== "false") {\r
18850                                         if (dom.getContentEditable(parent) === "true") {\r
18851                                                 editableRoot = parent;\r
18852                                         }\r
18853 \r
18854                                         parent = parent.parentNode;\r
18855                                 }\r
18856                                 \r
18857                                 return parent !== root ? editableRoot : root;\r
18858                         };\r
18859 \r
18860                         // Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block\r
18861                         function addBrToBlockIfNeeded(block) {\r
18862                                 var lastChild;\r
18863 \r
18864                                 // IE will render the blocks correctly other browsers needs a BR\r
18865                                 if (!tinymce.isIE) {\r
18866                                         block.normalize(); // Remove empty text nodes that got left behind by the extract\r
18867 \r
18868                                         // Check if the block is empty or contains a floated last child\r
18869                                         lastChild = block.lastChild;\r
18870                                         if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {\r
18871                                                 dom.add(block, 'br');\r
18872                                         }\r
18873                                 }\r
18874                         };\r
18875 \r
18876                         // Delete any selected contents\r
18877                         if (!rng.collapsed) {\r
18878                                 editor.execCommand('Delete');\r
18879                                 return;\r
18880                         }\r
18881 \r
18882                         // Event is blocked by some other handler for example the lists plugin\r
18883                         if (evt.isDefaultPrevented()) {\r
18884                                 return;\r
18885                         }\r
18886 \r
18887                         // Setup range items and newBlockName\r
18888                         container = rng.startContainer;\r
18889                         offset = rng.startOffset;\r
18890                         newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;\r
18891                         newBlockName = newBlockName ? newBlockName.toUpperCase() : '';\r
18892                         documentMode = dom.doc.documentMode;\r
18893                         shiftKey = evt.shiftKey;\r
18894 \r
18895                         // Resolve node index\r
18896                         if (container.nodeType == 1 && container.hasChildNodes()) {\r
18897                                 isAfterLastNodeInContainer = offset > container.childNodes.length - 1;\r
18898                                 container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;\r
18899                                 if (isAfterLastNodeInContainer && container.nodeType == 3) {\r
18900                                         offset = container.nodeValue.length;\r
18901                                 } else {\r
18902                                         offset = 0;\r
18903                                 }\r
18904                         }\r
18905 \r
18906                         // Get editable root node normaly the body element but sometimes a div or span\r
18907                         editableRoot = getEditableRoot(container);\r
18908 \r
18909                         // If there is no editable root then enter is done inside a contentEditable false element\r
18910                         if (!editableRoot) {\r
18911                                 return;\r
18912                         }\r
18913 \r
18914                         undoManager.beforeChange();\r
18915 \r
18916                         // If editable root isn't block nor the root of the editor\r
18917                         if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {\r
18918                                 if (!newBlockName || shiftKey) {\r
18919                                         insertBr();\r
18920                                 }\r
18921 \r
18922                                 return;\r
18923                         }\r
18924 \r
18925                         // Wrap the current node and it's sibling in a default block if it's needed.\r
18926                         // for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>\r
18927                         // This won't happen if root blocks are disabled or the shiftKey is pressed\r
18928                         if ((newBlockName && !shiftKey) || (!newBlockName && shiftKey)) {\r
18929                                 container = wrapSelfAndSiblingsInDefaultBlock(container, offset);\r
18930                         }\r
18931 \r
18932                         // Find parent block and setup empty block paddings\r
18933                         parentBlock = dom.getParent(container, dom.isBlock);\r
18934                         containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;\r
18935 \r
18936                         // Setup block names\r
18937                         parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5\r
18938                         containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5\r
18939 \r
18940                         // Enter inside block contained within a LI then split or insert before/after LI\r
18941                         if (containerBlockName == 'LI' && !evt.ctrlKey) {\r
18942                                 parentBlock = containerBlock;\r
18943                                 parentBlockName = containerBlockName;\r
18944                         }\r
18945 \r
18946                         // Handle enter in LI\r
18947                         if (parentBlockName == 'LI') {\r
18948                                 if (!newBlockName && shiftKey) {\r
18949                                         insertBr();\r
18950                                         return;\r
18951                                 }\r
18952 \r
18953                                 // Handle enter inside an empty list item\r
18954                                 if (dom.isEmpty(parentBlock)) {\r
18955                                         // Let the list plugin or browser handle nested lists for now\r
18956                                         if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {\r
18957                                                 return false;\r
18958                                         }\r
18959 \r
18960                                         handleEmptyListItem();\r
18961                                         return;\r
18962                                 }\r
18963                         }\r
18964 \r
18965                         // Don't split PRE tags but insert a BR instead easier when writing code samples etc\r
18966                         if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {\r
18967                                 if (!shiftKey) {\r
18968                                         insertBr();\r
18969                                         return;\r
18970                                 }\r
18971                         } else {\r
18972                                 // If no root block is configured then insert a BR by default or if the shiftKey is pressed\r
18973                                 if ((!newBlockName && !shiftKey && parentBlockName != 'LI') || (newBlockName && shiftKey)) {\r
18974                                         insertBr();\r
18975                                         return;\r
18976                                 }\r
18977                         }\r
18978 \r
18979                         // Default block name if it's not configured\r
18980                         newBlockName = newBlockName || 'P';\r
18981 \r
18982                         // Insert new block before/after the parent block depending on caret location\r
18983                         if (isCaretAtStartOrEndOfBlock()) {\r
18984                                 // 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
18985                                 if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {\r
18986                                         newBlock = createNewBlock(newBlockName);\r
18987                                 } else {\r
18988                                         newBlock = createNewBlock();\r
18989                                 }\r
18990 \r
18991                                 // Split the current container block element if enter is pressed inside an empty inner block element\r
18992                                 if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {\r
18993                                         // Split container block for example a BLOCKQUOTE at the current blockParent location for example a P\r
18994                                         newBlock = dom.split(containerBlock, parentBlock);\r
18995                                 } else {\r
18996                                         dom.insertAfter(newBlock, parentBlock);\r
18997                                 }\r
18998 \r
18999                                 moveToCaretPosition(newBlock);\r
19000                         } else if (isCaretAtStartOrEndOfBlock(true)) {\r
19001                                 // Insert new block before\r
19002                                 newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);\r
19003                                 renderBlockOnIE(newBlock);\r
19004                         } else {\r
19005                                 // Extract after fragment and insert it after the current block\r
19006                                 tmpRng = rng.cloneRange();\r
19007                                 tmpRng.setEndAfter(parentBlock);\r
19008                                 fragment = tmpRng.extractContents();\r
19009                                 trimLeadingLineBreaks(fragment);\r
19010                                 newBlock = fragment.firstChild;\r
19011                                 dom.insertAfter(fragment, parentBlock);\r
19012                                 trimInlineElementsOnLeftSideOfBlock(newBlock);\r
19013                                 addBrToBlockIfNeeded(parentBlock);\r
19014                                 moveToCaretPosition(newBlock);\r
19015                         }\r
19016 \r
19017                         dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique\r
19018                         undoManager.add();\r
19019                 }\r
19020 \r
19021                 editor.onKeyDown.add(function(ed, evt) {\r
19022                         if (evt.keyCode == 13) {\r
19023                                 if (handleEnterKey(evt) !== false) {\r
19024                                         evt.preventDefault();\r
19025                                 }\r
19026                         }\r
19027                 });\r
19028         };\r
19029 })(tinymce);\r
19030 \r