Subversion-Projekte lars-tiefland.content-management

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
(function(win) {
2
	var whiteSpaceRe = /^\s*|\s*$/g,
3
		undefined, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
4
 
5
	var tinymce = {
6
		majorVersion : '3',
7
 
8
		minorVersion : '4.6',
9
 
10
		releaseDate : '2011-09-29',
11
 
12
		_init : function() {
13
			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
14
 
15
			t.isOpera = win.opera && opera.buildNumber;
16
 
17
			t.isWebKit = /WebKit/.test(ua);
18
 
19
			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
20
 
21
			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
22
 
23
			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
24
 
25
			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
26
 
27
			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
28
 
29
			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
30
 
31
			t.isMac = ua.indexOf('Mac') != -1;
32
 
33
			t.isAir = /adobeair/i.test(ua);
34
 
35
			t.isIDevice = /(iPad|iPhone)/.test(ua);
36
 
37
			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
38
 
39
			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
40
			if (win.tinyMCEPreInit) {
41
				t.suffix = tinyMCEPreInit.suffix;
42
				t.baseURL = tinyMCEPreInit.base;
43
				t.query = tinyMCEPreInit.query;
44
				return;
45
			}
46
 
47
			// Get suffix and base
48
			t.suffix = '';
49
 
50
			// If base element found, add that infront of baseURL
51
			nl = d.getElementsByTagName('base');
52
			for (i=0; i<nl.length; i++) {
53
				if (v = nl[i].href) {
54
					// Host only value like http://site.com or http://site.com:8008
55
					if (/^https?:\/\/[^\/]+$/.test(v))
56
						v += '/';
57
 
58
					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
59
				}
60
			}
61
 
62
			function getBase(n) {
63
				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
64
					if (/_(src|dev)\.js/g.test(n.src))
65
						t.suffix = '_src';
66
 
67
					if ((p = n.src.indexOf('?')) != -1)
68
						t.query = n.src.substring(p + 1);
69
 
70
					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
71
 
72
					// If path to script is relative and a base href was found add that one infront
73
					// the src property will always be an absolute one on non IE browsers and IE 8
74
					// so this logic will basically only be executed on older IE versions
75
					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
76
						t.baseURL = base + t.baseURL;
77
 
78
					return t.baseURL;
79
				}
80
 
81
				return null;
82
			};
83
 
84
			// Check document
85
			nl = d.getElementsByTagName('script');
86
			for (i=0; i<nl.length; i++) {
87
				if (getBase(nl[i]))
88
					return;
89
			}
90
 
91
			// Check head
92
			n = d.getElementsByTagName('head')[0];
93
			if (n) {
94
				nl = n.getElementsByTagName('script');
95
				for (i=0; i<nl.length; i++) {
96
					if (getBase(nl[i]))
97
						return;
98
				}
99
			}
100
 
101
			return;
102
		},
103
 
104
		is : function(o, t) {
105
			if (!t)
106
				return o !== undefined;
107
 
108
			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
109
				return true;
110
 
111
			return typeof(o) == t;
112
		},
113
 
114
		makeMap : function(items, delim, map) {
115
			var i;
116
 
117
			items = items || [];
118
			delim = delim || ',';
119
 
120
			if (typeof(items) == "string")
121
				items = items.split(delim);
122
 
123
			map = map || {};
124
 
125
			i = items.length;
126
			while (i--)
127
				map[items[i]] = {};
128
 
129
			return map;
130
		},
131
 
132
		each : function(o, cb, s) {
133
			var n, l;
134
 
135
			if (!o)
136
				return 0;
137
 
138
			s = s || o;
139
 
140
			if (o.length !== undefined) {
141
				// Indexed arrays, needed for Safari
142
				for (n=0, l = o.length; n < l; n++) {
143
					if (cb.call(s, o[n], n, o) === false)
144
						return 0;
145
				}
146
			} else {
147
				// Hashtables
148
				for (n in o) {
149
					if (o.hasOwnProperty(n)) {
150
						if (cb.call(s, o[n], n, o) === false)
151
							return 0;
152
					}
153
				}
154
			}
155
 
156
			return 1;
157
		},
158
 
159
 
160
		trim : function(s) {
161
			return (s ? '' + s : '').replace(whiteSpaceRe, '');
162
		},
163
 
164
		create : function(s, p, root) {
165
			var t = this, sp, ns, cn, scn, c, de = 0;
166
 
167
			// Parse : <prefix> <class>:<super class>
168
			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
169
			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
170
 
171
			// Create namespace for new class
172
			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
173
 
174
			// Class already exists
175
			if (ns[cn])
176
				return;
177
 
178
			// Make pure static class
179
			if (s[2] == 'static') {
180
				ns[cn] = p;
181
 
182
				if (this.onCreate)
183
					this.onCreate(s[2], s[3], ns[cn]);
184
 
185
				return;
186
			}
187
 
188
			// Create default constructor
189
			if (!p[cn]) {
190
				p[cn] = function() {};
191
				de = 1;
192
			}
193
 
194
			// Add constructor and methods
195
			ns[cn] = p[cn];
196
			t.extend(ns[cn].prototype, p);
197
 
198
			// Extend
199
			if (s[5]) {
200
				sp = t.resolve(s[5]).prototype;
201
				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
202
 
203
				// Extend constructor
204
				c = ns[cn];
205
				if (de) {
206
					// Add passthrough constructor
207
					ns[cn] = function() {
208
						return sp[scn].apply(this, arguments);
209
					};
210
				} else {
211
					// Add inherit constructor
212
					ns[cn] = function() {
213
						this.parent = sp[scn];
214
						return c.apply(this, arguments);
215
					};
216
				}
217
				ns[cn].prototype[cn] = ns[cn];
218
 
219
				// Add super methods
220
				t.each(sp, function(f, n) {
221
					ns[cn].prototype[n] = sp[n];
222
				});
223
 
224
				// Add overridden methods
225
				t.each(p, function(f, n) {
226
					// Extend methods if needed
227
					if (sp[n]) {
228
						ns[cn].prototype[n] = function() {
229
							this.parent = sp[n];
230
							return f.apply(this, arguments);
231
						};
232
					} else {
233
						if (n != cn)
234
							ns[cn].prototype[n] = f;
235
					}
236
				});
237
			}
238
 
239
			// Add static methods
240
			t.each(p['static'], function(f, n) {
241
				ns[cn][n] = f;
242
			});
243
 
244
			if (this.onCreate)
245
				this.onCreate(s[2], s[3], ns[cn].prototype);
246
		},
247
 
248
		walk : function(o, f, n, s) {
249
			s = s || this;
250
 
251
			if (o) {
252
				if (n)
253
					o = o[n];
254
 
255
				tinymce.each(o, function(o, i) {
256
					if (f.call(s, o, i, n) === false)
257
						return false;
258
 
259
					tinymce.walk(o, f, n, s);
260
				});
261
			}
262
		},
263
 
264
		createNS : function(n, o) {
265
			var i, v;
266
 
267
			o = o || win;
268
 
269
			n = n.split('.');
270
			for (i=0; i<n.length; i++) {
271
				v = n[i];
272
 
273
				if (!o[v])
274
					o[v] = {};
275
 
276
				o = o[v];
277
			}
278
 
279
			return o;
280
		},
281
 
282
		resolve : function(n, o) {
283
			var i, l;
284
 
285
			o = o || win;
286
 
287
			n = n.split('.');
288
			for (i = 0, l = n.length; i < l; i++) {
289
				o = o[n[i]];
290
 
291
				if (!o)
292
					break;
293
			}
294
 
295
			return o;
296
		},
297
 
298
		addUnload : function(f, s) {
299
			var t = this;
300
 
301
			f = {func : f, scope : s || this};
302
 
303
			if (!t.unloads) {
304
				function unload() {
305
					var li = t.unloads, o, n;
306
 
307
					if (li) {
308
						// Call unload handlers
309
						for (n in li) {
310
							o = li[n];
311
 
312
							if (o && o.func)
313
								o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
314
						}
315
 
316
						// Detach unload function
317
						if (win.detachEvent) {
318
							win.detachEvent('onbeforeunload', fakeUnload);
319
							win.detachEvent('onunload', unload);
320
						} else if (win.removeEventListener)
321
							win.removeEventListener('unload', unload, false);
322
 
323
						// Destroy references
324
						t.unloads = o = li = w = unload = 0;
325
 
326
						// Run garbarge collector on IE
327
						if (win.CollectGarbage)
328
							CollectGarbage();
329
					}
330
				};
331
 
332
				function fakeUnload() {
333
					var d = document;
334
 
335
					// Is there things still loading, then do some magic
336
					if (d.readyState == 'interactive') {
337
						function stop() {
338
							// Prevent memory leak
339
							d.detachEvent('onstop', stop);
340
 
341
							// Call unload handler
342
							if (unload)
343
								unload();
344
 
345
							d = 0;
346
						};
347
 
348
						// Fire unload when the currently loading page is stopped
349
						if (d)
350
							d.attachEvent('onstop', stop);
351
 
352
						// Remove onstop listener after a while to prevent the unload function
353
						// to execute if the user presses cancel in an onbeforeunload
354
						// confirm dialog and then presses the browser stop button
355
						win.setTimeout(function() {
356
							if (d)
357
								d.detachEvent('onstop', stop);
358
						}, 0);
359
					}
360
				};
361
 
362
				// Attach unload handler
363
				if (win.attachEvent) {
364
					win.attachEvent('onunload', unload);
365
					win.attachEvent('onbeforeunload', fakeUnload);
366
				} else if (win.addEventListener)
367
					win.addEventListener('unload', unload, false);
368
 
369
				// Setup initial unload handler array
370
				t.unloads = [f];
371
			} else
372
				t.unloads.push(f);
373
 
374
			return f;
375
		},
376
 
377
		removeUnload : function(f) {
378
			var u = this.unloads, r = null;
379
 
380
			tinymce.each(u, function(o, i) {
381
				if (o && o.func == f) {
382
					u.splice(i, 1);
383
					r = f;
384
					return false;
385
				}
386
			});
387
 
388
			return r;
389
		},
390
 
391
		explode : function(s, d) {
392
			return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
393
		},
394
 
395
		_addVer : function(u) {
396
			var v;
397
 
398
			if (!this.query)
399
				return u;
400
 
401
			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
402
 
403
			if (u.indexOf('#') == -1)
404
				return u + v;
405
 
406
			return u.replace('#', v + '#');
407
		},
408
 
409
		// Fix function for IE 9 where regexps isn't working correctly
410
		// Todo: remove me once MS fixes the bug
411
		_replace : function(find, replace, str) {
412
			// On IE9 we have to fake $x replacement
413
			if (isRegExpBroken) {
414
				return str.replace(find, function() {
415
					var val = replace, args = arguments, i;
416
 
417
					for (i = 0; i < args.length - 2; i++) {
418
						if (args[i] === undefined) {
419
							val = val.replace(new RegExp('\\$' + i, 'g'), '');
420
						} else {
421
							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
422
						}
423
					}
424
 
425
					return val;
426
				});
427
			}
428
 
429
			return str.replace(find, replace);
430
		}
431
 
432
		};
433
 
434
	// Initialize the API
435
	tinymce._init();
436
 
437
	// Expose tinymce namespace to the global namespace (window)
438
	win.tinymce = win.tinyMCE = tinymce;
439
 
440
	// Describe the different namespaces
441
 
442
	})(window);
443
 
444
 
445
(function($, tinymce) {
446
	var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undefined;
447
 
448
	// jQuery is undefined
449
	if (!$ && window.console) {
450
		return console.log("Load jQuery first!");
451
	}
452
 
453
	// Stick jQuery into the tinymce namespace
454
	tinymce.$ = $;
455
 
456
	// Setup adapter
457
	tinymce.adapter = {
458
		patchEditor : function(editor) {
459
			var fn = $.fn;
460
 
461
			// Adapt the css function to make sure that the data-mce-style
462
			// attribute gets updated with the new style information
463
			function css(name, value) {
464
				var self = this;
465
 
466
				// Remove data-mce-style when set operation occurs
467
				if (value)
468
					self.removeAttr('data-mce-style');
469
 
470
				return fn.css.apply(self, arguments);
471
			};
472
 
473
			// Apapt the attr function to make sure that it uses the data-mce- prefixed variants
474
			function attr(name, value) {
475
				var self = this;
476
 
477
				// Update/retrive data-mce- attribute variants
478
				if (attrRegExp.test(name)) {
479
					if (value !== undefined) {
480
						// Use TinyMCE behavior when setting the specifc attributes
481
						self.each(function(i, node) {
482
							editor.dom.setAttrib(node, name, value);
483
						});
484
 
485
						return self;
486
					} else
487
						return self.attr('data-mce-' + name);
488
				}
489
 
490
				// Default behavior
491
				return fn.attr.apply(self, arguments);
492
			};
493
 
494
			function htmlPatchFunc(func) {
495
				// Returns a modified function that processes
496
				// the HTML before executing the action this makes sure
497
				// that href/src etc gets moved into the data-mce- variants
498
				return function(content) {
499
					if (content)
500
						content = editor.dom.processHTML(content);
501
 
502
					return func.call(this, content);
503
				};
504
			};
505
 
506
			// Patch various jQuery functions to handle tinymce specific attribute and content behavior
507
			// we don't patch the jQuery.fn directly since it will most likely break compatibility
508
			// with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
509
			function patch(jq) {
510
				// Patch some functions, only patch the object once
511
				if (jq.css !== css) {
512
					// Patch css/attr to use the data-mce- prefixed attribute variants
513
					jq.css = css;
514
					jq.attr = attr;
515
 
516
					// Patch HTML functions to use the DOMUtils.processHTML filter logic
517
					jq.html = htmlPatchFunc(fn.html);
518
					jq.append = htmlPatchFunc(fn.append);
519
					jq.prepend = htmlPatchFunc(fn.prepend);
520
					jq.after = htmlPatchFunc(fn.after);
521
					jq.before = htmlPatchFunc(fn.before);
522
					jq.replaceWith = htmlPatchFunc(fn.replaceWith);
523
					jq.tinymce = editor;
524
 
525
					// Each pushed jQuery instance needs to be patched
526
					// as well for example when traversing the DOM
527
					jq.pushStack = function() {
528
						return patch(fn.pushStack.apply(this, arguments));
529
					};
530
				}
531
 
532
				return jq;
533
			};
534
 
535
			// Add a $ function on each editor instance this one is scoped for the editor document object
536
			// this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
537
			editor.$ = function(selector, scope) {
538
				var doc = editor.getDoc();
539
 
540
				return patch($(selector || doc, doc || scope));
541
			};
542
		}
543
	};
544
 
545
	// Patch in core NS functions
546
	tinymce.extend = $.extend;
547
	tinymce.extend(tinymce, {
548
		map : $.map,
549
		grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
550
		inArray : function(a, v) {return $.inArray(v, a || []);}
551
 
552
		/* Didn't iterate stylesheets
553
		each : function(o, cb, s) {
554
			if (!o)
555
				return 0;
556
 
557
			var r = 1;
558
 
559
			$.each(o, function(nr, el){
560
				if (cb.call(s, el, nr, o) === false) {
561
					r = 0;
562
					return false;
563
				}
564
			});
565
 
566
			return r;
567
		}*/
568
	});
569
 
570
	// Patch in functions in various clases
571
	// Add a "#ifndefjquery" statement around each core API function you add below
572
	var patches = {
573
		'tinymce.dom.DOMUtils' : {
574
			/*
575
			addClass : function(e, c) {
576
				if (is(e, 'array') && is(e[0], 'string'))
577
					e = e.join(',#');
578
				return (e && $(is(e, 'string') ? '#' + e : e)
579
					.addClass(c)
580
					.attr('class')) || false;
581
			},
582
 
583
			hasClass : function(n, c) {
584
				return $(is(n, 'string') ? '#' + n : n).hasClass(c);
585
			},
586
 
587
			removeClass : function(e, c) {
588
				if (!e)
589
					return false;
590
 
591
				var r = [];
592
 
593
				$(is(e, 'string') ? '#' + e : e)
594
					.removeClass(c)
595
					.each(function(){
596
						r.push(this.className);
597
					});
598
 
599
				return r.length == 1 ? r[0] : r;
600
			},
601
			*/
602
 
603
			select : function(pattern, scope) {
604
				var t = this;
605
 
606
				return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
607
			},
608
 
609
			is : function(n, patt) {
610
				return $(this.get(n)).is(patt);
611
			}
612
 
613
			/*
614
			show : function(e) {
615
				if (is(e, 'array') && is(e[0], 'string'))
616
					e = e.join(',#');
617
 
618
				$(is(e, 'string') ? '#' + e : e).css('display', 'block');
619
			},
620
 
621
			hide : function(e) {
622
				if (is(e, 'array') && is(e[0], 'string'))
623
					e = e.join(',#');
624
 
625
				$(is(e, 'string') ? '#' + e : e).css('display', 'none');
626
			},
627
 
628
			isHidden : function(e) {
629
				return $(is(e, 'string') ? '#' + e : e).is(':hidden');
630
			},
631
 
632
			insertAfter : function(n, e) {
633
				return $(is(e, 'string') ? '#' + e : e).after(n);
634
			},
635
 
636
			replace : function(o, n, k) {
637
				n = $(is(n, 'string') ? '#' + n : n);
638
 
639
				if (k)
640
					n.children().appendTo(o);
641
 
642
				n.replaceWith(o);
643
			},
644
 
645
			setStyle : function(n, na, v) {
646
				if (is(n, 'array') && is(n[0], 'string'))
647
					n = n.join(',#');
648
 
649
				$(is(n, 'string') ? '#' + n : n).css(na, v);
650
			},
651
 
652
			getStyle : function(n, na, c) {
653
				return $(is(n, 'string') ? '#' + n : n).css(na);
654
			},
655
 
656
			setStyles : function(e, o) {
657
				if (is(e, 'array') && is(e[0], 'string'))
658
					e = e.join(',#');
659
				$(is(e, 'string') ? '#' + e : e).css(o);
660
			},
661
 
662
			setAttrib : function(e, n, v) {
663
				var t = this, s = t.settings;
664
 
665
				if (is(e, 'array') && is(e[0], 'string'))
666
					e = e.join(',#');
667
 
668
				e = $(is(e, 'string') ? '#' + e : e);
669
 
670
				switch (n) {
671
					case "style":
672
						e.each(function(i, v){
673
							if (s.keep_values)
674
								$(v).attr('data-mce-style', v);
675
 
676
							v.style.cssText = v;
677
						});
678
						break;
679
 
680
					case "class":
681
						e.each(function(){
682
							this.className = v;
683
						});
684
						break;
685
 
686
					case "src":
687
					case "href":
688
						e.each(function(i, v){
689
							if (s.keep_values) {
690
								if (s.url_converter)
691
									v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
692
 
693
								t.setAttrib(v, 'data-mce-' + n, v);
694
							}
695
						});
696
 
697
						break;
698
				}
699
 
700
				if (v !== null && v.length !== 0)
701
					e.attr(n, '' + v);
702
				else
703
					e.removeAttr(n);
704
			},
705
 
706
			setAttribs : function(e, o) {
707
				var t = this;
708
 
709
				$.each(o, function(n, v){
710
					t.setAttrib(e,n,v);
711
				});
712
			}
713
			*/
714
		}
715
 
716
/*
717
		'tinymce.dom.Event' : {
718
			add : function (o, n, f, s) {
719
				var lo, cb;
720
 
721
				cb = function(e) {
722
					e.target = e.target || this;
723
					f.call(s || this, e);
724
				};
725
 
726
				if (is(o, 'array') && is(o[0], 'string'))
727
					o = o.join(',#');
728
				o = $(is(o, 'string') ? '#' + o : o);
729
				if (n == 'init') {
730
					o.ready(cb, s);
731
				} else {
732
					if (s) {
733
						o.bind(n, s, cb);
734
					} else {
735
						o.bind(n, cb);
736
					}
737
				}
738
 
739
				lo = this._jqLookup || (this._jqLookup = []);
740
				lo.push({func : f, cfunc : cb});
741
 
742
				return cb;
743
			},
744
 
745
			remove : function(o, n, f) {
746
				// Find cfunc
747
				$(this._jqLookup).each(function() {
748
					if (this.func === f)
749
						f = this.cfunc;
750
				});
751
 
752
				if (is(o, 'array') && is(o[0], 'string'))
753
					o = o.join(',#');
754
 
755
				$(is(o, 'string') ? '#' + o : o).unbind(n,f);
756
 
757
				return true;
758
			}
759
		}
760
*/
761
	};
762
 
763
	// Patch functions after a class is created
764
	tinymce.onCreate = function(ty, c, p) {
765
		tinymce.extend(p, patches[c]);
766
	};
767
})(window.jQuery, tinymce);
768
 
769
 
770
 
771
tinymce.create('tinymce.util.Dispatcher', {
772
	scope : null,
773
	listeners : null,
774
 
775
	Dispatcher : function(s) {
776
		this.scope = s || this;
777
		this.listeners = [];
778
	},
779
 
780
	add : function(cb, s) {
781
		this.listeners.push({cb : cb, scope : s || this.scope});
782
 
783
		return cb;
784
	},
785
 
786
	addToTop : function(cb, s) {
787
		this.listeners.unshift({cb : cb, scope : s || this.scope});
788
 
789
		return cb;
790
	},
791
 
792
	remove : function(cb) {
793
		var l = this.listeners, o = null;
794
 
795
		tinymce.each(l, function(c, i) {
796
			if (cb == c.cb) {
797
				o = cb;
798
				l.splice(i, 1);
799
				return false;
800
			}
801
		});
802
 
803
		return o;
804
	},
805
 
806
	dispatch : function() {
807
		var s, a = arguments, i, li = this.listeners, c;
808
 
809
		// Needs to be a real loop since the listener count might change while looping
810
		// And this is also more efficient
811
		for (i = 0; i<li.length; i++) {
812
			c = li[i];
813
			s = c.cb.apply(c.scope, a);
814
 
815
			if (s === false)
816
				break;
817
		}
818
 
819
		return s;
820
	}
821
 
822
	});
823
 
824
(function() {
825
	var each = tinymce.each;
826
 
827
	tinymce.create('tinymce.util.URI', {
828
		URI : function(u, s) {
829
			var t = this, o, a, b, base_url;
830
 
831
			// Trim whitespace
832
			u = tinymce.trim(u);
833
 
834
			// Default settings
835
			s = t.settings = s || {};
836
 
837
			// Strange app protocol that isn't http/https or local anchor
838
			// For example: mailto,skype,tel etc.
839
			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
840
				t.source = u;
841
				return;
842
			}
843
 
844
			// Absolute path with no host, fake host and protocol
845
			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
846
				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
847
 
848
			// Relative path http:// or protocol relative //path
849
			if (!/^[\w-]*:?\/\//.test(u)) {
850
				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
851
				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
852
			}
853
 
854
			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
855
			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
856
			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
857
			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
858
				var s = u[i];
859
 
860
				// Zope 3 workaround, they use @@something
861
				if (s)
862
					s = s.replace(/\(mce_at\)/g, '@@');
863
 
864
				t[v] = s;
865
			});
866
 
867
			if (b = s.base_uri) {
868
				if (!t.protocol)
869
					t.protocol = b.protocol;
870
 
871
				if (!t.userInfo)
872
					t.userInfo = b.userInfo;
873
 
874
				if (!t.port && t.host == 'mce_host')
875
					t.port = b.port;
876
 
877
				if (!t.host || t.host == 'mce_host')
878
					t.host = b.host;
879
 
880
				t.source = '';
881
			}
882
 
883
			//t.path = t.path || '/';
884
		},
885
 
886
		setPath : function(p) {
887
			var t = this;
888
 
889
			p = /^(.*?)\/?(\w+)?$/.exec(p);
890
 
891
			// Update path parts
892
			t.path = p[0];
893
			t.directory = p[1];
894
			t.file = p[2];
895
 
896
			// Rebuild source
897
			t.source = '';
898
			t.getURI();
899
		},
900
 
901
		toRelative : function(u) {
902
			var t = this, o;
903
 
904
			if (u === "./")
905
				return u;
906
 
907
			u = new tinymce.util.URI(u, {base_uri : t});
908
 
909
			// Not on same domain/port or protocol
910
			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
911
				return u.getURI();
912
 
913
			o = t.toRelPath(t.path, u.path);
914
 
915
			// Add query
916
			if (u.query)
917
				o += '?' + u.query;
918
 
919
			// Add anchor
920
			if (u.anchor)
921
				o += '#' + u.anchor;
922
 
923
			return o;
924
		},
925
 
926
		toAbsolute : function(u, nh) {
927
			var u = new tinymce.util.URI(u, {base_uri : this});
928
 
929
			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
930
		},
931
 
932
		toRelPath : function(base, path) {
933
			var items, bp = 0, out = '', i, l;
934
 
935
			// Split the paths
936
			base = base.substring(0, base.lastIndexOf('/'));
937
			base = base.split('/');
938
			items = path.split('/');
939
 
940
			if (base.length >= items.length) {
941
				for (i = 0, l = base.length; i < l; i++) {
942
					if (i >= items.length || base[i] != items[i]) {
943
						bp = i + 1;
944
						break;
945
					}
946
				}
947
			}
948
 
949
			if (base.length < items.length) {
950
				for (i = 0, l = items.length; i < l; i++) {
951
					if (i >= base.length || base[i] != items[i]) {
952
						bp = i + 1;
953
						break;
954
					}
955
				}
956
			}
957
 
958
			if (bp == 1)
959
				return path;
960
 
961
			for (i = 0, l = base.length - (bp - 1); i < l; i++)
962
				out += "../";
963
 
964
			for (i = bp - 1, l = items.length; i < l; i++) {
965
				if (i != bp - 1)
966
					out += "/" + items[i];
967
				else
968
					out += items[i];
969
			}
970
 
971
			return out;
972
		},
973
 
974
		toAbsPath : function(base, path) {
975
			var i, nb = 0, o = [], tr, outPath;
976
 
977
			// Split paths
978
			tr = /\/$/.test(path) ? '/' : '';
979
			base = base.split('/');
980
			path = path.split('/');
981
 
982
			// Remove empty chunks
983
			each(base, function(k) {
984
				if (k)
985
					o.push(k);
986
			});
987
 
988
			base = o;
989
 
990
			// Merge relURLParts chunks
991
			for (i = path.length - 1, o = []; i >= 0; i--) {
992
				// Ignore empty or .
993
				if (path[i].length == 0 || path[i] == ".")
994
					continue;
995
 
996
				// Is parent
997
				if (path[i] == '..') {
998
					nb++;
999
					continue;
1000
				}
1001
 
1002
				// Move up
1003
				if (nb > 0) {
1004
					nb--;
1005
					continue;
1006
				}
1007
 
1008
				o.push(path[i]);
1009
			}
1010
 
1011
			i = base.length - nb;
1012
 
1013
			// If /a/b/c or /
1014
			if (i <= 0)
1015
				outPath = o.reverse().join('/');
1016
			else
1017
				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1018
 
1019
			// Add front / if it's needed
1020
			if (outPath.indexOf('/') !== 0)
1021
				outPath = '/' + outPath;
1022
 
1023
			// Add traling / if it's needed
1024
			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1025
				outPath += tr;
1026
 
1027
			return outPath;
1028
		},
1029
 
1030
		getURI : function(nh) {
1031
			var s, t = this;
1032
 
1033
			// Rebuild source
1034
			if (!t.source || nh) {
1035
				s = '';
1036
 
1037
				if (!nh) {
1038
					if (t.protocol)
1039
						s += t.protocol + '://';
1040
 
1041
					if (t.userInfo)
1042
						s += t.userInfo + '@';
1043
 
1044
					if (t.host)
1045
						s += t.host;
1046
 
1047
					if (t.port)
1048
						s += ':' + t.port;
1049
				}
1050
 
1051
				if (t.path)
1052
					s += t.path;
1053
 
1054
				if (t.query)
1055
					s += '?' + t.query;
1056
 
1057
				if (t.anchor)
1058
					s += '#' + t.anchor;
1059
 
1060
				t.source = s;
1061
			}
1062
 
1063
			return t.source;
1064
		}
1065
	});
1066
})();
1067
 
1068
(function() {
1069
	var each = tinymce.each;
1070
 
1071
	tinymce.create('static tinymce.util.Cookie', {
1072
		getHash : function(n) {
1073
			var v = this.get(n), h;
1074
 
1075
			if (v) {
1076
				each(v.split('&'), function(v) {
1077
					v = v.split('=');
1078
					h = h || {};
1079
					h[unescape(v[0])] = unescape(v[1]);
1080
				});
1081
			}
1082
 
1083
			return h;
1084
		},
1085
 
1086
		setHash : function(n, v, e, p, d, s) {
1087
			var o = '';
1088
 
1089
			each(v, function(v, k) {
1090
				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1091
			});
1092
 
1093
			this.set(n, o, e, p, d, s);
1094
		},
1095
 
1096
		get : function(n) {
1097
			var c = document.cookie, e, p = n + "=", b;
1098
 
1099
			// Strict mode
1100
			if (!c)
1101
				return;
1102
 
1103
			b = c.indexOf("; " + p);
1104
 
1105
			if (b == -1) {
1106
				b = c.indexOf(p);
1107
 
1108
				if (b != 0)
1109
					return null;
1110
			} else
1111
				b += 2;
1112
 
1113
			e = c.indexOf(";", b);
1114
 
1115
			if (e == -1)
1116
				e = c.length;
1117
 
1118
			return unescape(c.substring(b + p.length, e));
1119
		},
1120
 
1121
		set : function(n, v, e, p, d, s) {
1122
			document.cookie = n + "=" + escape(v) +
1123
				((e) ? "; expires=" + e.toGMTString() : "") +
1124
				((p) ? "; path=" + escape(p) : "") +
1125
				((d) ? "; domain=" + d : "") +
1126
				((s) ? "; secure" : "");
1127
		},
1128
 
1129
		remove : function(n, p) {
1130
			var d = new Date();
1131
 
1132
			d.setTime(d.getTime() - 1000);
1133
 
1134
			this.set(n, '', d, p, d);
1135
		}
1136
	});
1137
})();
1138
 
1139
(function() {
1140
	function serialize(o, quote) {
1141
		var i, v, t;
1142
 
1143
		quote = quote || '"';
1144
 
1145
		if (o == null)
1146
			return 'null';
1147
 
1148
		t = typeof o;
1149
 
1150
		if (t == 'string') {
1151
			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1152
 
1153
			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1154
				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
1155
				if (quote === '"' && a === "'")
1156
					return a;
1157
 
1158
				i = v.indexOf(b);
1159
 
1160
				if (i + 1)
1161
					return '\\' + v.charAt(i + 1);
1162
 
1163
				a = b.charCodeAt().toString(16);
1164
 
1165
				return '\\u' + '0000'.substring(a.length) + a;
1166
			}) + quote;
1167
		}
1168
 
1169
		if (t == 'object') {
1170
			if (o.hasOwnProperty && o instanceof Array) {
1171
					for (i=0, v = '['; i<o.length; i++)
1172
						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1173
 
1174
					return v + ']';
1175
				}
1176
 
1177
				v = '{';
1178
 
1179
				for (i in o)
1180
					v += typeof o[i] != 'function' ? (v.length > 1 ? ',' + quote : quote) + i + quote +':' + serialize(o[i], quote) : '';
1181
 
1182
				return v + '}';
1183
		}
1184
 
1185
		return '' + o;
1186
	};
1187
 
1188
	tinymce.util.JSON = {
1189
		serialize: serialize,
1190
 
1191
		parse: function(s) {
1192
			try {
1193
				return eval('(' + s + ')');
1194
			} catch (ex) {
1195
				// Ignore
1196
			}
1197
		}
1198
 
1199
		};
1200
})();
1201
tinymce.create('static tinymce.util.XHR', {
1202
	send : function(o) {
1203
		var x, t, w = window, c = 0;
1204
 
1205
		// Default settings
1206
		o.scope = o.scope || this;
1207
		o.success_scope = o.success_scope || o.scope;
1208
		o.error_scope = o.error_scope || o.scope;
1209
		o.async = o.async === false ? false : true;
1210
		o.data = o.data || '';
1211
 
1212
		function get(s) {
1213
			x = 0;
1214
 
1215
			try {
1216
				x = new ActiveXObject(s);
1217
			} catch (ex) {
1218
			}
1219
 
1220
			return x;
1221
		};
1222
 
1223
		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1224
 
1225
		if (x) {
1226
			if (x.overrideMimeType)
1227
				x.overrideMimeType(o.content_type);
1228
 
1229
			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1230
 
1231
			if (o.content_type)
1232
				x.setRequestHeader('Content-Type', o.content_type);
1233
 
1234
			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1235
 
1236
			x.send(o.data);
1237
 
1238
			function ready() {
1239
				if (!o.async || x.readyState == 4 || c++ > 10000) {
1240
					if (o.success && c < 10000 && x.status == 200)
1241
						o.success.call(o.success_scope, '' + x.responseText, x, o);
1242
					else if (o.error)
1243
						o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1244
 
1245
					x = null;
1246
				} else
1247
					w.setTimeout(ready, 10);
1248
			};
1249
 
1250
			// Syncronous request
1251
			if (!o.async)
1252
				return ready();
1253
 
1254
			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1255
			t = w.setTimeout(ready, 10);
1256
		}
1257
	}
1258
});
1259
 
1260
(function() {
1261
	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1262
 
1263
	tinymce.create('tinymce.util.JSONRequest', {
1264
		JSONRequest : function(s) {
1265
			this.settings = extend({
1266
			}, s);
1267
			this.count = 0;
1268
		},
1269
 
1270
		send : function(o) {
1271
			var ecb = o.error, scb = o.success;
1272
 
1273
			o = extend(this.settings, o);
1274
 
1275
			o.success = function(c, x) {
1276
				c = JSON.parse(c);
1277
 
1278
				if (typeof(c) == 'undefined') {
1279
					c = {
1280
						error : 'JSON Parse error.'
1281
					};
1282
				}
1283
 
1284
				if (c.error)
1285
					ecb.call(o.error_scope || o.scope, c.error, x);
1286
				else
1287
					scb.call(o.success_scope || o.scope, c.result);
1288
			};
1289
 
1290
			o.error = function(ty, x) {
1291
				if (ecb)
1292
					ecb.call(o.error_scope || o.scope, ty, x);
1293
			};
1294
 
1295
			o.data = JSON.serialize({
1296
				id : o.id || 'c' + (this.count++),
1297
				method : o.method,
1298
				params : o.params
1299
			});
1300
 
1301
			// JSON content type for Ruby on rails. Bug: #1883287
1302
			o.content_type = 'application/json';
1303
 
1304
			XHR.send(o);
1305
		},
1306
 
1307
		'static' : {
1308
			sendRPC : function(o) {
1309
				return new tinymce.util.JSONRequest().send(o);
1310
			}
1311
		}
1312
	});
1313
}());
1314
(function(tinymce){
1315
	tinymce.VK = {
1316
		DELETE:46,
1317
		BACKSPACE:8
1318
 
1319
	}
1320
 
1321
})(tinymce);
1322
 
1323
(function(tinymce) {
1324
	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE;
1325
 
1326
	function cleanupStylesWhenDeleting(ed) {
1327
		var dom = ed.dom, selection = ed.selection;
1328
 
1329
		ed.onKeyDown.add(function(ed, e) {
1330
			var rng, blockElm, node, clonedSpan, isDelete;
1331
 
1332
			isDelete = e.keyCode == DELETE;
1333
			if (isDelete || e.keyCode == BACKSPACE) {
1334
				e.preventDefault();
1335
				rng = selection.getRng();
1336
 
1337
				// Find root block
1338
				blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1339
 
1340
				// On delete clone the root span of the next block element
1341
				if (isDelete)
1342
					blockElm = dom.getNext(blockElm, dom.isBlock);
1343
 
1344
				// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1345
				if (blockElm) {
1346
					node = blockElm.firstChild;
1347
 
1348
					// Ignore empty text nodes
1349
					while (node.nodeType == 3 && node.nodeValue.length == 0)
1350
						node = node.nextSibling;
1351
 
1352
					if (node && node.nodeName === 'SPAN') {
1353
						clonedSpan = node.cloneNode(false);
1354
					}
1355
				}
1356
 
1357
				// Do the backspace/delete actiopn
1358
				ed.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1359
 
1360
				// Find all odd apple-style-spans
1361
				blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1362
				tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1363
					var bm = selection.getBookmark();
1364
 
1365
					if (clonedSpan) {
1366
						dom.replace(clonedSpan.cloneNode(false), span, true);
1367
					} else {
1368
						dom.remove(span, true);
1369
					}
1370
 
1371
					// Restore the selection
1372
					selection.moveToBookmark(bm);
1373
				});
1374
			}
1375
		});
1376
	};
1377
 
1378
	function emptyEditorWhenDeleting(ed) {
1379
		ed.onKeyUp.add(function(ed, e) {
1380
			var keyCode = e.keyCode;
1381
 
1382
			if (keyCode == DELETE || keyCode == BACKSPACE) {
1383
				if (ed.dom.isEmpty(ed.getBody())) {
1384
					ed.setContent('', {format : 'raw'});
1385
					ed.nodeChanged();
1386
					return;
1387
				}
1388
			}
1389
		});
1390
	};
1391
 
1392
	function inputMethodFocus(ed) {
1393
		ed.dom.bind(ed.getDoc(), 'focusin', function() {
1394
			ed.selection.setRng(ed.selection.getRng());
1395
		});
1396
	};
1397
 
1398
	function focusBody(ed) {
1399
		// Fix for a focus bug in FF 3.x where the body element
1400
		// wouldn't get proper focus if the user clicked on the HTML element
1401
		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1402
			ed.onMouseDown.add(function(ed, e) {
1403
				if (e.target.nodeName === "HTML") {
1404
					var body = ed.getBody();
1405
 
1406
					// Blur the body it's focused but not correctly focused
1407
					body.blur();
1408
 
1409
					// Refocus the body after a little while
1410
					setTimeout(function() {
1411
						body.focus();
1412
					}, 0);
1413
				}
1414
			});
1415
		}
1416
	};
1417
 
1418
	function selectControlElements(ed) {
1419
		ed.onClick.add(function(ed, e) {
1420
			e = e.target;
1421
 
1422
			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1423
			// WebKit can't even do simple things like selecting an image
1424
			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1425
			if (/^(IMG|HR)$/.test(e.nodeName))
1426
				ed.selection.getSel().setBaseAndExtent(e, 0, e, 1);
1427
 
1428
			if (e.nodeName == 'A' && ed.dom.hasClass(e, 'mceItemAnchor'))
1429
				ed.selection.select(e);
1430
 
1431
			ed.nodeChanged();
1432
		});
1433
	};
1434
 
1435
	tinymce.create('tinymce.util.Quirks', {
1436
		Quirks: function(ed) {
1437
			// WebKit
1438
			if (tinymce.isWebKit) {
1439
				cleanupStylesWhenDeleting(ed);
1440
				emptyEditorWhenDeleting(ed);
1441
				inputMethodFocus(ed);
1442
				selectControlElements(ed);
1443
			}
1444
 
1445
			// IE
1446
			if (tinymce.isIE) {
1447
				emptyEditorWhenDeleting(ed);
1448
			}
1449
 
1450
			// Gecko
1451
			if (tinymce.isGecko) {
1452
				focusBody(ed);
1453
			}
1454
		}
1455
	});
1456
})(tinymce);
1457
(function(tinymce) {
1458
	var namedEntities, baseEntities, reverseEntities,
1459
		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1460
		textCharsRegExp = /[<>&\u007E-\uD7FF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
1461
		rawCharsRegExp = /[<>&\"\']/g,
1462
		entityRegExp = /&(#x|#)?([\w]+);/g,
1463
		asciiMap = {
1464
				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
1465
				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
1466
				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
1467
				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
1468
				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
1469
		};
1470
 
1471
	// Raw entities
1472
	baseEntities = {
1473
		'\"' : '&quot;', // Needs to be escaped since the YUI compressor would otherwise break the code
1474
		"'" : '&#39;',
1475
		'<' : '&lt;',
1476
		'>' : '&gt;',
1477
		'&' : '&amp;'
1478
	};
1479
 
1480
	// Reverse lookup table for raw entities
1481
	reverseEntities = {
1482
		'&lt;' : '<',
1483
		'&gt;' : '>',
1484
		'&amp;' : '&',
1485
		'&quot;' : '"',
1486
		'&apos;' : "'"
1487
	};
1488
 
1489
	// Decodes text by using the browser
1490
	function nativeDecode(text) {
1491
		var elm;
1492
 
1493
		elm = document.createElement("div");
1494
		elm.innerHTML = text;
1495
 
1496
		return elm.textContent || elm.innerText || text;
1497
	};
1498
 
1499
	// Build a two way lookup table for the entities
1500
	function buildEntitiesLookup(items, radix) {
1501
		var i, chr, entity, lookup = {};
1502
 
1503
		if (items) {
1504
			items = items.split(',');
1505
			radix = radix || 10;
1506
 
1507
			// Build entities lookup table
1508
			for (i = 0; i < items.length; i += 2) {
1509
				chr = String.fromCharCode(parseInt(items[i], radix));
1510
 
1511
				// Only add non base entities
1512
				if (!baseEntities[chr]) {
1513
					entity = '&' + items[i + 1] + ';';
1514
					lookup[chr] = entity;
1515
					lookup[entity] = chr;
1516
				}
1517
			}
1518
 
1519
			return lookup;
1520
		}
1521
	};
1522
 
1523
	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
1524
	namedEntities = buildEntitiesLookup(
1525
		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
1526
		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
1527
		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
1528
		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
1529
		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
1530
		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
1531
		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
1532
		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
1533
		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
1534
		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
1535
		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
1536
		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
1537
		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
1538
		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
1539
		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
1540
		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
1541
		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
1542
		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
1543
		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
1544
		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
1545
		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
1546
		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
1547
		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
1548
		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
1549
		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro'
1550
	, 32);
1551
 
1552
	tinymce.html = tinymce.html || {};
1553
 
1554
	tinymce.html.Entities = {
1555
		encodeRaw : function(text, attr) {
1556
			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1557
				return baseEntities[chr] || chr;
1558
			});
1559
		},
1560
 
1561
		encodeAllRaw : function(text) {
1562
			return ('' + text).replace(rawCharsRegExp, function(chr) {
1563
				return baseEntities[chr] || chr;
1564
			});
1565
		},
1566
 
1567
		encodeNumeric : function(text, attr) {
1568
			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1569
				// Multi byte sequence convert it to a single entity
1570
				if (chr.length > 1)
1571
					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
1572
 
1573
				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
1574
			});
1575
		},
1576
 
1577
		encodeNamed : function(text, attr, entities) {
1578
			entities = entities || namedEntities;
1579
 
1580
			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1581
				return baseEntities[chr] || entities[chr] || chr;
1582
			});
1583
		},
1584
 
1585
		getEncodeFunc : function(name, entities) {
1586
			var Entities = tinymce.html.Entities;
1587
 
1588
			entities = buildEntitiesLookup(entities) || namedEntities;
1589
 
1590
			function encodeNamedAndNumeric(text, attr) {
1591
				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
1592
					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
1593
				});
1594
			};
1595
 
1596
			function encodeCustomNamed(text, attr) {
1597
				return Entities.encodeNamed(text, attr, entities);
1598
			};
1599
 
1600
			// Replace + with , to be compatible with previous TinyMCE versions
1601
			name = tinymce.makeMap(name.replace(/\+/g, ','));
1602
 
1603
			// Named and numeric encoder
1604
			if (name.named && name.numeric)
1605
				return encodeNamedAndNumeric;
1606
 
1607
			// Named encoder
1608
			if (name.named) {
1609
				// Custom names
1610
				if (entities)
1611
					return encodeCustomNamed;
1612
 
1613
				return Entities.encodeNamed;
1614
			}
1615
 
1616
			// Numeric
1617
			if (name.numeric)
1618
				return Entities.encodeNumeric;
1619
 
1620
			// Raw encoder
1621
			return Entities.encodeRaw;
1622
		},
1623
 
1624
		decode : function(text) {
1625
			return text.replace(entityRegExp, function(all, numeric, value) {
1626
				if (numeric) {
1627
					value = parseInt(value, numeric.length === 2 ? 16 : 10);
1628
 
1629
					// Support upper UTF
1630
					if (value > 0xFFFF) {
1631
						value -= 0x10000;
1632
 
1633
						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
1634
					} else
1635
						return asciiMap[value] || String.fromCharCode(value);
1636
				}
1637
 
1638
				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
1639
			});
1640
		}
1641
	};
1642
})(tinymce);
1643
 
1644
tinymce.html.Styles = function(settings, schema) {
1645
	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
1646
		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
1647
		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
1648
		trimRightRegExp = /\s+$/,
1649
		urlColorRegExp = /rgb/,
1650
		undef, i, encodingLookup = {}, encodingItems;
1651
 
1652
	settings = settings || {};
1653
 
1654
	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
1655
	for (i = 0; i < encodingItems.length; i++) {
1656
		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
1657
		encodingLookup['\uFEFF' + i] = encodingItems[i];
1658
	}
1659
 
1660
	function toHex(match, r, g, b) {
1661
		function hex(val) {
1662
			val = parseInt(val).toString(16);
1663
 
1664
			return val.length > 1 ? val : '0' + val; // 0 -> 00
1665
		};
1666
 
1667
		return '#' + hex(r) + hex(g) + hex(b);
1668
	};
1669
 
1670
	return {
1671
		toHex : function(color) {
1672
			return color.replace(rgbRegExp, toHex);
1673
		},
1674
 
1675
		parse : function(css) {
1676
			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
1677
 
1678
			function compress(prefix, suffix) {
1679
				var top, right, bottom, left;
1680
 
1681
				// Get values and check it it needs compressing
1682
				top = styles[prefix + '-top' + suffix];
1683
				if (!top)
1684
					return;
1685
 
1686
				right = styles[prefix + '-right' + suffix];
1687
				if (top != right)
1688
					return;
1689
 
1690
				bottom = styles[prefix + '-bottom' + suffix];
1691
				if (right != bottom)
1692
					return;
1693
 
1694
				left = styles[prefix + '-left' + suffix];
1695
				if (bottom != left)
1696
					return;
1697
 
1698
				// Compress
1699
				styles[prefix + suffix] = left;
1700
				delete styles[prefix + '-top' + suffix];
1701
				delete styles[prefix + '-right' + suffix];
1702
				delete styles[prefix + '-bottom' + suffix];
1703
				delete styles[prefix + '-left' + suffix];
1704
			};
1705
 
1706
			function canCompress(key) {
1707
				var value = styles[key], i;
1708
 
1709
				if (!value || value.indexOf(' ') < 0)
1710
					return;
1711
 
1712
				value = value.split(' ');
1713
				i = value.length;
1714
				while (i--) {
1715
					if (value[i] !== value[0])
1716
						return false;
1717
				}
1718
 
1719
				styles[key] = value[0];
1720
 
1721
				return true;
1722
			};
1723
 
1724
			function compress2(target, a, b, c) {
1725
				if (!canCompress(a))
1726
					return;
1727
 
1728
				if (!canCompress(b))
1729
					return;
1730
 
1731
				if (!canCompress(c))
1732
					return;
1733
 
1734
				// Compress
1735
				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
1736
				delete styles[a];
1737
				delete styles[b];
1738
				delete styles[c];
1739
			};
1740
 
1741
			// Encodes the specified string by replacing all \" \' ; : with _<num>
1742
			function encode(str) {
1743
				isEncoded = true;
1744
 
1745
				return encodingLookup[str];
1746
			};
1747
 
1748
			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
1749
			// It will also decode the \" \' if keep_slashes is set to fale or omitted
1750
			function decode(str, keep_slashes) {
1751
				if (isEncoded) {
1752
					str = str.replace(/\uFEFF[0-9]/g, function(str) {
1753
						return encodingLookup[str];
1754
					});
1755
				}
1756
 
1757
				if (!keep_slashes)
1758
					str = str.replace(/\\([\'\";:])/g, "$1");
1759
 
1760
				return str;
1761
			}
1762
 
1763
			if (css) {
1764
				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
1765
				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
1766
					return str.replace(/[;:]/g, encode);
1767
				});
1768
 
1769
				// Parse styles
1770
				while (matches = styleRegExp.exec(css)) {
1771
					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
1772
					value = matches[2].replace(trimRightRegExp, '');
1773
 
1774
					if (name && value.length > 0) {
1775
						// Opera will produce 700 instead of bold in their style values
1776
						if (name === 'font-weight' && value === '700')
1777
							value = 'bold';
1778
						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
1779
							value = value.toLowerCase();
1780
 
1781
						// Convert RGB colors to HEX
1782
						value = value.replace(rgbRegExp, toHex);
1783
 
1784
						// Convert URLs and force them into url('value') format
1785
						value = value.replace(urlOrStrRegExp, function(match, url, url2, url3, str, str2) {
1786
							str = str || str2;
1787
 
1788
							if (str) {
1789
								str = decode(str);
1790
 
1791
								// Force strings into single quote format
1792
								return "'" + str.replace(/\'/g, "\\'") + "'";
1793
							}
1794
 
1795
							url = decode(url || url2 || url3);
1796
 
1797
							// Convert the URL to relative/absolute depending on config
1798
							if (urlConverter)
1799
								url = urlConverter.call(urlConverterScope, url, 'style');
1800
 
1801
							// Output new URL format
1802
							return "url('" + url.replace(/\'/g, "\\'") + "')";
1803
						});
1804
 
1805
						styles[name] = isEncoded ? decode(value, true) : value;
1806
					}
1807
 
1808
					styleRegExp.lastIndex = matches.index + matches[0].length;
1809
				}
1810
 
1811
				// Compress the styles to reduce it's size for example IE will expand styles
1812
				compress("border", "");
1813
				compress("border", "-width");
1814
				compress("border", "-color");
1815
				compress("border", "-style");
1816
				compress("padding", "");
1817
				compress("margin", "");
1818
				compress2('border', 'border-width', 'border-style', 'border-color');
1819
 
1820
				// Remove pointless border, IE produces these
1821
				if (styles.border === 'medium none')
1822
					delete styles.border;
1823
			}
1824
 
1825
			return styles;
1826
		},
1827
 
1828
		serialize : function(styles, element_name) {
1829
			var css = '', name, value;
1830
 
1831
			function serializeStyles(name) {
1832
				var styleList, i, l, value;
1833
 
1834
				styleList = schema.styles[name];
1835
				if (styleList) {
1836
					for (i = 0, l = styleList.length; i < l; i++) {
1837
						name = styleList[i];
1838
						value = styles[name];
1839
 
1840
						if (value !== undef && value.length > 0)
1841
							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1842
					}
1843
				}
1844
			};
1845
 
1846
			// Serialize styles according to schema
1847
			if (element_name && schema && schema.styles) {
1848
				// Serialize global styles and element specific styles
1849
				serializeStyles('*');
1850
				serializeStyles(element_name);
1851
			} else {
1852
				// Output the styles in the order they are inside the object
1853
				for (name in styles) {
1854
					value = styles[name];
1855
 
1856
					if (value !== undef && value.length > 0)
1857
						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
1858
				}
1859
			}
1860
 
1861
			return css;
1862
		}
1863
	};
1864
};
1865
 
1866
(function(tinymce) {
1867
	var transitional = {}, boolAttrMap, blockElementsMap, shortEndedElementsMap, nonEmptyElementsMap, customElementsMap = {},
1868
		defaultWhiteSpaceElementsMap, selfClosingElementsMap, makeMap = tinymce.makeMap, each = tinymce.each;
1869
 
1870
	function split(str, delim) {
1871
		return str.split(delim || ',');
1872
	};
1873
 
1874
	function unpack(lookup, data) {
1875
		var key, elements = {};
1876
 
1877
		function replace(value) {
1878
			return value.replace(/[A-Z]+/g, function(key) {
1879
				return replace(lookup[key]);
1880
			});
1881
		};
1882
 
1883
		// Unpack lookup
1884
		for (key in lookup) {
1885
			if (lookup.hasOwnProperty(key))
1886
				lookup[key] = replace(lookup[key]);
1887
		}
1888
 
1889
		// Unpack and parse data into object map
1890
		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
1891
			attributes = split(attributes, '|');
1892
 
1893
			elements[name] = {
1894
				attributes : makeMap(attributes),
1895
				attributesOrder : attributes,
1896
				children : makeMap(children, '|', {'#comment' : {}})
1897
			}
1898
		});
1899
 
1900
		return elements;
1901
	};
1902
 
1903
	// Build a lookup table for block elements both lowercase and uppercase
1904
	blockElementsMap = 'h1,h2,h3,h4,h5,h6,hr,p,div,address,pre,form,table,tbody,thead,tfoot,' +
1905
						'th,tr,td,li,ol,ul,caption,blockquote,center,dl,dt,dd,dir,fieldset,' +
1906
						'noscript,menu,isindex,samp,header,footer,article,section,hgroup';
1907
	blockElementsMap = makeMap(blockElementsMap, ',', makeMap(blockElementsMap.toUpperCase()));
1908
 
1909
	// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
1910
	transitional = unpack({
1911
		Z : 'H|K|N|O|P',
1912
		Y : 'X|form|R|Q',
1913
		ZG : 'E|span|width|align|char|charoff|valign',
1914
		X : 'p|T|div|U|W|isindex|fieldset|table',
1915
		ZF : 'E|align|char|charoff|valign',
1916
		W : 'pre|hr|blockquote|address|center|noframes',
1917
		ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
1918
		ZD : '[E][S]',
1919
		U : 'ul|ol|dl|menu|dir',
1920
		ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
1921
		T : 'h1|h2|h3|h4|h5|h6',
1922
		ZB : 'X|S|Q',
1923
		S : 'R|P',
1924
		ZA : 'a|G|J|M|O|P',
1925
		R : 'a|H|K|N|O',
1926
		Q : 'noscript|P',
1927
		P : 'ins|del|script',
1928
		O : 'input|select|textarea|label|button',
1929
		N : 'M|L',
1930
		M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
1931
		L : 'sub|sup',
1932
		K : 'J|I',
1933
		J : 'tt|i|b|u|s|strike',
1934
		I : 'big|small|font|basefont',
1935
		H : 'G|F',
1936
		G : 'br|span|bdo',
1937
		F : 'object|applet|img|map|iframe',
1938
		E : 'A|B|C',
1939
		D : 'accesskey|tabindex|onfocus|onblur',
1940
		C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
1941
		B : 'lang|xml:lang|dir',
1942
		A : 'id|class|style|title'
1943
	}, 'script[id|charset|type|language|src|defer|xml:space][]' +
1944
		'style[B|id|type|media|title|xml:space][]' +
1945
		'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' +
1946
		'param[id|name|value|valuetype|type][]' +
1947
		'p[E|align][#|S]' +
1948
		'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' +
1949
		'br[A|clear][]' +
1950
		'span[E][#|S]' +
1951
		'bdo[A|C|B][#|S]' +
1952
		'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' +
1953
		'h1[E|align][#|S]' +
1954
		'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' +
1955
		'map[B|C|A|name][X|form|Q|area]' +
1956
		'h2[E|align][#|S]' +
1957
		'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' +
1958
		'h3[E|align][#|S]' +
1959
		'tt[E][#|S]' +
1960
		'i[E][#|S]' +
1961
		'b[E][#|S]' +
1962
		'u[E][#|S]' +
1963
		's[E][#|S]' +
1964
		'strike[E][#|S]' +
1965
		'big[E][#|S]' +
1966
		'small[E][#|S]' +
1967
		'font[A|B|size|color|face][#|S]' +
1968
		'basefont[id|size|color|face][]' +
1969
		'em[E][#|S]' +
1970
		'strong[E][#|S]' +
1971
		'dfn[E][#|S]' +
1972
		'code[E][#|S]' +
1973
		'q[E|cite][#|S]' +
1974
		'samp[E][#|S]' +
1975
		'kbd[E][#|S]' +
1976
		'var[E][#|S]' +
1977
		'cite[E][#|S]' +
1978
		'abbr[E][#|S]' +
1979
		'acronym[E][#|S]' +
1980
		'sub[E][#|S]' +
1981
		'sup[E][#|S]' +
1982
		'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' +
1983
		'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' +
1984
		'optgroup[E|disabled|label][option]' +
1985
		'option[E|selected|disabled|label|value][]' +
1986
		'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' +
1987
		'label[E|for|accesskey|onfocus|onblur][#|S]' +
1988
		'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' +
1989
		'h4[E|align][#|S]' +
1990
		'ins[E|cite|datetime][#|Y]' +
1991
		'h5[E|align][#|S]' +
1992
		'del[E|cite|datetime][#|Y]' +
1993
		'h6[E|align][#|S]' +
1994
		'div[E|align][#|Y]' +
1995
		'ul[E|type|compact][li]' +
1996
		'li[E|type|value][#|Y]' +
1997
		'ol[E|type|compact|start][li]' +
1998
		'dl[E|compact][dt|dd]' +
1999
		'dt[E][#|S]' +
2000
		'dd[E][#|Y]' +
2001
		'menu[E|compact][li]' +
2002
		'dir[E|compact][li]' +
2003
		'pre[E|width|xml:space][#|ZA]' +
2004
		'hr[E|align|noshade|size|width][]' +
2005
		'blockquote[E|cite][#|Y]' +
2006
		'address[E][#|S|p]' +
2007
		'center[E][#|Y]' +
2008
		'noframes[E][#|Y]' +
2009
		'isindex[A|B|prompt][]' +
2010
		'fieldset[E][#|legend|Y]' +
2011
		'legend[E|accesskey|align][#|S]' +
2012
		'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' +
2013
		'caption[E|align][#|S]' +
2014
		'col[ZG][]' +
2015
		'colgroup[ZG][col]' +
2016
		'thead[ZF][tr]' +
2017
		'tr[ZF|bgcolor][th|td]' +
2018
		'th[E|ZE][#|Y]' +
2019
		'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' +
2020
		'noscript[E][#|Y]' +
2021
		'td[E|ZE][#|Y]' +
2022
		'tfoot[ZF][tr]' +
2023
		'tbody[ZF][tr]' +
2024
		'area[E|D|shape|coords|href|nohref|alt|target][]' +
2025
		'base[id|href|target][]' +
2026
		'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2027
	);
2028
 
2029
	boolAttrMap = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected,autoplay,loop,controls');
2030
	shortEndedElementsMap = makeMap('area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,source');
2031
	nonEmptyElementsMap = tinymce.extend(makeMap('td,th,iframe,video,audio,object'), shortEndedElementsMap);
2032
	defaultWhiteSpaceElementsMap = makeMap('pre,script,style,textarea');
2033
	selfClosingElementsMap = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr');
2034
 
2035
	tinymce.html.Schema = function(settings) {
2036
		var self = this, elements = {}, children = {}, patternElements = [], validStyles, whiteSpaceElementsMap;
2037
 
2038
		settings = settings || {};
2039
 
2040
		// Allow all elements and attributes if verify_html is set to false
2041
		if (settings.verify_html === false)
2042
			settings.valid_elements = '*[*]';
2043
 
2044
		// Build styles list
2045
		if (settings.valid_styles) {
2046
			validStyles = {};
2047
 
2048
			// Convert styles into a rule list
2049
			each(settings.valid_styles, function(value, key) {
2050
				validStyles[key] = tinymce.explode(value);
2051
			});
2052
		}
2053
 
2054
		whiteSpaceElementsMap = settings.whitespace_elements ? makeMap(settings.whitespace_elements) : defaultWhiteSpaceElementsMap;
2055
 
2056
		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2057
		function patternToRegExp(str) {
2058
			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2059
		};
2060
 
2061
		// Parses the specified valid_elements string and adds to the current rules
2062
		// This function is a bit hard to read since it's heavily optimized for speed
2063
		function addValidElements(valid_elements) {
2064
			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2065
				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2066
				elementRuleRegExp = /^([#+-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2067
				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2068
				hasPatternsRegExp = /[*?+]/;
2069
 
2070
			if (valid_elements) {
2071
				// Split valid elements into an array with rules
2072
				valid_elements = split(valid_elements);
2073
 
2074
				if (elements['@']) {
2075
					globalAttributes = elements['@'].attributes;
2076
					globalAttributesOrder = elements['@'].attributesOrder;
2077
				}
2078
 
2079
				// Loop all rules
2080
				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2081
					// Parse element rule
2082
					matches = elementRuleRegExp.exec(valid_elements[ei]);
2083
					if (matches) {
2084
						// Setup local names for matches
2085
						prefix = matches[1];
2086
						elementName = matches[2];
2087
						outputName = matches[3];
2088
						attrData = matches[4];
2089
 
2090
						// Create new attributes and attributesOrder
2091
						attributes = {};
2092
						attributesOrder = [];
2093
 
2094
						// Create the new element
2095
						element = {
2096
							attributes : attributes,
2097
							attributesOrder : attributesOrder
2098
						};
2099
 
2100
						// Padd empty elements prefix
2101
						if (prefix === '#')
2102
							element.paddEmpty = true;
2103
 
2104
						// Remove empty elements prefix
2105
						if (prefix === '-')
2106
							element.removeEmpty = true;
2107
 
2108
						// Copy attributes from global rule into current rule
2109
						if (globalAttributes) {
2110
							for (key in globalAttributes)
2111
								attributes[key] = globalAttributes[key];
2112
 
2113
							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2114
						}
2115
 
2116
						// Attributes defined
2117
						if (attrData) {
2118
							attrData = split(attrData, '|');
2119
							for (ai = 0, al = attrData.length; ai < al; ai++) {
2120
								matches = attrRuleRegExp.exec(attrData[ai]);
2121
								if (matches) {
2122
									attr = {};
2123
									attrType = matches[1];
2124
									attrName = matches[2].replace(/::/g, ':');
2125
									prefix = matches[3];
2126
									value = matches[4];
2127
 
2128
									// Required
2129
									if (attrType === '!') {
2130
										element.attributesRequired = element.attributesRequired || [];
2131
										element.attributesRequired.push(attrName);
2132
										attr.required = true;
2133
									}
2134
 
2135
									// Denied from global
2136
									if (attrType === '-') {
2137
										delete attributes[attrName];
2138
										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2139
										continue;
2140
									}
2141
 
2142
									// Default value
2143
									if (prefix) {
2144
										// Default value
2145
										if (prefix === '=') {
2146
											element.attributesDefault = element.attributesDefault || [];
2147
											element.attributesDefault.push({name: attrName, value: value});
2148
											attr.defaultValue = value;
2149
										}
2150
 
2151
										// Forced value
2152
										if (prefix === ':') {
2153
											element.attributesForced = element.attributesForced || [];
2154
											element.attributesForced.push({name: attrName, value: value});
2155
											attr.forcedValue = value;
2156
										}
2157
 
2158
										// Required values
2159
										if (prefix === '<')
2160
											attr.validValues = makeMap(value, '?');
2161
									}
2162
 
2163
									// Check for attribute patterns
2164
									if (hasPatternsRegExp.test(attrName)) {
2165
										element.attributePatterns = element.attributePatterns || [];
2166
										attr.pattern = patternToRegExp(attrName);
2167
										element.attributePatterns.push(attr);
2168
									} else {
2169
										// Add attribute to order list if it doesn't already exist
2170
										if (!attributes[attrName])
2171
											attributesOrder.push(attrName);
2172
 
2173
										attributes[attrName] = attr;
2174
									}
2175
								}
2176
							}
2177
						}
2178
 
2179
						// Global rule, store away these for later usage
2180
						if (!globalAttributes && elementName == '@') {
2181
							globalAttributes = attributes;
2182
							globalAttributesOrder = attributesOrder;
2183
						}
2184
 
2185
						// Handle substitute elements such as b/strong
2186
						if (outputName) {
2187
							element.outputName = elementName;
2188
							elements[outputName] = element;
2189
						}
2190
 
2191
						// Add pattern or exact element
2192
						if (hasPatternsRegExp.test(elementName)) {
2193
							element.pattern = patternToRegExp(elementName);
2194
							patternElements.push(element);
2195
						} else
2196
							elements[elementName] = element;
2197
					}
2198
				}
2199
			}
2200
		};
2201
 
2202
		function setValidElements(valid_elements) {
2203
			elements = {};
2204
			patternElements = [];
2205
 
2206
			addValidElements(valid_elements);
2207
 
2208
			each(transitional, function(element, name) {
2209
				children[name] = element.children;
2210
			});
2211
		};
2212
 
2213
		// Adds custom non HTML elements to the schema
2214
		function addCustomElements(custom_elements) {
2215
			var customElementRegExp = /^(~)?(.+)$/;
2216
 
2217
			if (custom_elements) {
2218
				each(split(custom_elements), function(rule) {
2219
					var matches = customElementRegExp.exec(rule),
2220
						inline = matches[1] === '~',
2221
						cloneName = inline ? 'span' : 'div',
2222
						name = matches[2];
2223
 
2224
					children[name] = children[cloneName];
2225
					customElementsMap[name] = cloneName;
2226
 
2227
					// If it's not marked as inline then add it to valid block elements
2228
					if (!inline)
2229
						blockElementsMap[name] = {};
2230
 
2231
					// Add custom elements at span/div positions
2232
					each(children, function(element, child) {
2233
						if (element[cloneName])
2234
							element[name] = element[cloneName];
2235
					});
2236
				});
2237
			}
2238
		};
2239
 
2240
		// Adds valid children to the schema object
2241
		function addValidChildren(valid_children) {
2242
			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2243
 
2244
			if (valid_children) {
2245
				each(split(valid_children), function(rule) {
2246
					var matches = childRuleRegExp.exec(rule), parent, prefix;
2247
 
2248
					if (matches) {
2249
						prefix = matches[1];
2250
 
2251
						// Add/remove items from default
2252
						if (prefix)
2253
							parent = children[matches[2]];
2254
						else
2255
							parent = children[matches[2]] = {'#comment' : {}};
2256
 
2257
						parent = children[matches[2]];
2258
 
2259
						each(split(matches[3], '|'), function(child) {
2260
							if (prefix === '-')
2261
								delete parent[child];
2262
							else
2263
								parent[child] = {};
2264
						});
2265
					}
2266
				});
2267
			}
2268
		};
2269
 
2270
		function getElementRule(name) {
2271
			var element = elements[name], i;
2272
 
2273
			// Exact match found
2274
			if (element)
2275
				return element;
2276
 
2277
			// No exact match then try the patterns
2278
			i = patternElements.length;
2279
			while (i--) {
2280
				element = patternElements[i];
2281
 
2282
				if (element.pattern.test(name))
2283
					return element;
2284
			}
2285
		};
2286
 
2287
		if (!settings.valid_elements) {
2288
			// No valid elements defined then clone the elements from the transitional spec
2289
			each(transitional, function(element, name) {
2290
				elements[name] = {
2291
					attributes : element.attributes,
2292
					attributesOrder : element.attributesOrder
2293
				};
2294
 
2295
				children[name] = element.children;
2296
			});
2297
 
2298
			// Switch these
2299
			each(split('strong/b,em/i'), function(item) {
2300
				item = split(item, '/');
2301
				elements[item[1]].outputName = item[0];
2302
			});
2303
 
2304
			// Add default alt attribute for images
2305
			elements.img.attributesDefault = [{name: 'alt', value: ''}];
2306
 
2307
			// Remove these if they are empty by default
2308
			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr'), function(name) {
2309
				elements[name].removeEmpty = true;
2310
			});
2311
 
2312
			// Padd these by default
2313
			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
2314
				elements[name].paddEmpty = true;
2315
			});
2316
		} else
2317
			setValidElements(settings.valid_elements);
2318
 
2319
		addCustomElements(settings.custom_elements);
2320
		addValidChildren(settings.valid_children);
2321
		addValidElements(settings.extended_valid_elements);
2322
 
2323
		// Todo: Remove this when we fix list handling to be valid
2324
		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
2325
 
2326
		// If the user didn't allow span only allow internal spans
2327
		if (!getElementRule('span'))
2328
			addValidElements('span[!data-mce-type|*]');
2329
 
2330
		// Delete invalid elements
2331
		if (settings.invalid_elements) {
2332
			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
2333
				if (elements[item])
2334
					delete elements[item];
2335
			});
2336
		}
2337
 
2338
		self.children = children;
2339
 
2340
		self.styles = validStyles;
2341
 
2342
		self.getBoolAttrs = function() {
2343
			return boolAttrMap;
2344
		};
2345
 
2346
		self.getBlockElements = function() {
2347
			return blockElementsMap;
2348
		};
2349
 
2350
		self.getShortEndedElements = function() {
2351
			return shortEndedElementsMap;
2352
		};
2353
 
2354
		self.getSelfClosingElements = function() {
2355
			return selfClosingElementsMap;
2356
		};
2357
 
2358
		self.getNonEmptyElements = function() {
2359
			return nonEmptyElementsMap;
2360
		};
2361
 
2362
		self.getWhiteSpaceElements = function() {
2363
			return whiteSpaceElementsMap;
2364
		};
2365
 
2366
		self.isValidChild = function(name, child) {
2367
			var parent = children[name];
2368
 
2369
			return !!(parent && parent[child]);
2370
		};
2371
 
2372
		self.getElementRule = getElementRule;
2373
 
2374
		self.getCustomElements = function() {
2375
			return customElementsMap;
2376
		};
2377
 
2378
		self.addValidElements = addValidElements;
2379
 
2380
		self.setValidElements = setValidElements;
2381
 
2382
		self.addCustomElements = addCustomElements;
2383
 
2384
		self.addValidChildren = addValidChildren;
2385
	};
2386
 
2387
	// Expose boolMap and blockElementMap as static properties for usage in DOMUtils
2388
	tinymce.html.Schema.boolAttrMap = boolAttrMap;
2389
	tinymce.html.Schema.blockElementsMap = blockElementsMap;
2390
})(tinymce);
2391
 
2392
(function(tinymce) {
2393
	tinymce.html.SaxParser = function(settings, schema) {
2394
		var self = this, noop = function() {};
2395
 
2396
		settings = settings || {};
2397
		self.schema = schema = schema || new tinymce.html.Schema();
2398
 
2399
		if (settings.fix_self_closing !== false)
2400
			settings.fix_self_closing = true;
2401
 
2402
		// Add handler functions from settings and setup default handlers
2403
		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
2404
			if (name)
2405
				self[name] = settings[name] || noop;
2406
		});
2407
 
2408
		self.parse = function(html) {
2409
			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
2410
				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
2411
				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
2412
				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
2413
 
2414
			function processEndTag(name) {
2415
				var pos, i;
2416
 
2417
				// Find position of parent of the same type
2418
				pos = stack.length;
2419
				while (pos--) {
2420
					if (stack[pos].name === name)
2421
						break;
2422
				}
2423
 
2424
				// Found parent
2425
				if (pos >= 0) {
2426
					// Close all the open elements
2427
					for (i = stack.length - 1; i >= pos; i--) {
2428
						name = stack[i];
2429
 
2430
						if (name.valid)
2431
							self.end(name.name);
2432
					}
2433
 
2434
					// Remove the open elements from the stack
2435
					stack.length = pos;
2436
				}
2437
			};
2438
 
2439
			// Precompile RegExps and map objects
2440
			tokenRegExp = new RegExp('<(?:' +
2441
				'(?:!--([\\w\\W]*?)-->)|' + // Comment
2442
				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
2443
				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
2444
				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
2445
				'(?:\\/([^>]+)>)|' + // End element
2446
				'(?:([^\\s\\/<>]+)\\s*((?:[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*)>)' + // Start element
2447
			')', 'g');
2448
 
2449
			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
2450
			specialElements = {
2451
				'script' : /<\/script[^>]*>/gi,
2452
				'style' : /<\/style[^>]*>/gi,
2453
				'noscript' : /<\/noscript[^>]*>/gi
2454
			};
2455
 
2456
			// Setup lookup tables for empty elements and boolean attributes
2457
			shortEndedElements = schema.getShortEndedElements();
2458
			selfClosing = schema.getSelfClosingElements();
2459
			fillAttrsMap = schema.getBoolAttrs();
2460
			validate = settings.validate;
2461
			removeInternalElements = settings.remove_internals;
2462
			fixSelfClosing = settings.fix_self_closing;
2463
			isIE = tinymce.isIE;
2464
			invalidPrefixRegExp = /^:/;
2465
 
2466
			while (matches = tokenRegExp.exec(html)) {
2467
				// Text
2468
				if (index < matches.index)
2469
					self.text(decode(html.substr(index, matches.index - index)));
2470
 
2471
				if (value = matches[6]) { // End element
2472
					value = value.toLowerCase();
2473
 
2474
					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2475
					if (isIE && invalidPrefixRegExp.test(value))
2476
						value = value.substr(1);
2477
 
2478
					processEndTag(value);
2479
				} else if (value = matches[7]) { // Start element
2480
					value = value.toLowerCase();
2481
 
2482
					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
2483
					if (isIE && invalidPrefixRegExp.test(value))
2484
						value = value.substr(1);
2485
 
2486
					isShortEnded = value in shortEndedElements;
2487
 
2488
					// Is self closing tag for example an <li> after an open <li>
2489
					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
2490
						processEndTag(value);
2491
 
2492
					// Validate element
2493
					if (!validate || (elementRule = schema.getElementRule(value))) {
2494
						isValidElement = true;
2495
 
2496
						// Grab attributes map and patters when validation is enabled
2497
						if (validate) {
2498
							validAttributesMap = elementRule.attributes;
2499
							validAttributePatterns = elementRule.attributePatterns;
2500
						}
2501
 
2502
						// Parse attributes
2503
						if (attribsValue = matches[8]) {
2504
							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
2505
 
2506
							// If the element has internal attributes then remove it if we are told to do so
2507
							if (isInternalElement && removeInternalElements)
2508
								isValidElement = false;
2509
 
2510
							attrList = [];
2511
							attrList.map = {};
2512
 
2513
							attribsValue.replace(attrRegExp, function(match, name, value, val2, val3) {
2514
								var attrRule, i;
2515
 
2516
								name = name.toLowerCase();
2517
								value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
2518
 
2519
								// Validate name and value
2520
								if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
2521
									attrRule = validAttributesMap[name];
2522
 
2523
									// Find rule by pattern matching
2524
									if (!attrRule && validAttributePatterns) {
2525
										i = validAttributePatterns.length;
2526
										while (i--) {
2527
											attrRule = validAttributePatterns[i];
2528
											if (attrRule.pattern.test(name))
2529
												break;
2530
										}
2531
 
2532
										// No rule matched
2533
										if (i === -1)
2534
											attrRule = null;
2535
									}
2536
 
2537
									// No attribute rule found
2538
									if (!attrRule)
2539
										return;
2540
 
2541
									// Validate value
2542
									if (attrRule.validValues && !(value in attrRule.validValues))
2543
										return;
2544
								}
2545
 
2546
								// Add attribute to list and map
2547
								attrList.map[name] = value;
2548
								attrList.push({
2549
									name: name,
2550
									value: value
2551
								});
2552
							});
2553
						} else {
2554
							attrList = [];
2555
							attrList.map = {};
2556
						}
2557
 
2558
						// Process attributes if validation is enabled
2559
						if (validate && !isInternalElement) {
2560
							attributesRequired = elementRule.attributesRequired;
2561
							attributesDefault = elementRule.attributesDefault;
2562
							attributesForced = elementRule.attributesForced;
2563
 
2564
							// Handle forced attributes
2565
							if (attributesForced) {
2566
								i = attributesForced.length;
2567
								while (i--) {
2568
									attr = attributesForced[i];
2569
									name = attr.name;
2570
									attrValue = attr.value;
2571
 
2572
									if (attrValue === '{$uid}')
2573
										attrValue = 'mce_' + idCount++;
2574
 
2575
									attrList.map[name] = attrValue;
2576
									attrList.push({name: name, value: attrValue});
2577
								}
2578
							}
2579
 
2580
							// Handle default attributes
2581
							if (attributesDefault) {
2582
								i = attributesDefault.length;
2583
								while (i--) {
2584
									attr = attributesDefault[i];
2585
									name = attr.name;
2586
 
2587
									if (!(name in attrList.map)) {
2588
										attrValue = attr.value;
2589
 
2590
										if (attrValue === '{$uid}')
2591
											attrValue = 'mce_' + idCount++;
2592
 
2593
										attrList.map[name] = attrValue;
2594
										attrList.push({name: name, value: attrValue});
2595
									}
2596
								}
2597
							}
2598
 
2599
							// Handle required attributes
2600
							if (attributesRequired) {
2601
								i = attributesRequired.length;
2602
								while (i--) {
2603
									if (attributesRequired[i] in attrList.map)
2604
										break;
2605
								}
2606
 
2607
								// None of the required attributes where found
2608
								if (i === -1)
2609
									isValidElement = false;
2610
							}
2611
 
2612
							// Invalidate element if it's marked as bogus
2613
							if (attrList.map['data-mce-bogus'])
2614
								isValidElement = false;
2615
						}
2616
 
2617
						if (isValidElement)
2618
							self.start(value, attrList, isShortEnded);
2619
					} else
2620
						isValidElement = false;
2621
 
2622
					// Treat script, noscript and style a bit different since they may include code that looks like elements
2623
					if (endRegExp = specialElements[value]) {
2624
						endRegExp.lastIndex = index = matches.index + matches[0].length;
2625
 
2626
						if (matches = endRegExp.exec(html)) {
2627
							if (isValidElement)
2628
								text = html.substr(index, matches.index - index);
2629
 
2630
							index = matches.index + matches[0].length;
2631
						} else {
2632
							text = html.substr(index);
2633
							index = html.length;
2634
						}
2635
 
2636
						if (isValidElement && text.length > 0)
2637
							self.text(text, true);
2638
 
2639
						if (isValidElement)
2640
							self.end(value);
2641
 
2642
						tokenRegExp.lastIndex = index;
2643
						continue;
2644
					}
2645
 
2646
					// Push value on to stack
2647
					if (!isShortEnded) {
2648
						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
2649
							stack.push({name: value, valid: isValidElement});
2650
						else if (isValidElement)
2651
							self.end(value);
2652
					}
2653
				} else if (value = matches[1]) { // Comment
2654
					self.comment(value);
2655
				} else if (value = matches[2]) { // CDATA
2656
					self.cdata(value);
2657
				} else if (value = matches[3]) { // DOCTYPE
2658
					self.doctype(value);
2659
				} else if (value = matches[4]) { // PI
2660
					self.pi(value, matches[5]);
2661
				}
2662
 
2663
				index = matches.index + matches[0].length;
2664
			}
2665
 
2666
			// Text
2667
			if (index < html.length)
2668
				self.text(decode(html.substr(index)));
2669
 
2670
			// Close any open elements
2671
			for (i = stack.length - 1; i >= 0; i--) {
2672
				value = stack[i];
2673
 
2674
				if (value.valid)
2675
					self.end(value.name);
2676
			}
2677
		};
2678
	}
2679
})(tinymce);
2680
 
2681
(function(tinymce) {
2682
	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
2683
		'#text' : 3,
2684
		'#comment' : 8,
2685
		'#cdata' : 4,
2686
		'#pi' : 7,
2687
		'#doctype' : 10,
2688
		'#document-fragment' : 11
2689
	};
2690
 
2691
	// Walks the tree left/right
2692
	function walk(node, root_node, prev) {
2693
		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
2694
 
2695
		// Walk into nodes if it has a start
2696
		if (node[startName])
2697
			return node[startName];
2698
 
2699
		// Return the sibling if it has one
2700
		if (node !== root_node) {
2701
			sibling = node[siblingName];
2702
 
2703
			if (sibling)
2704
				return sibling;
2705
 
2706
			// Walk up the parents to look for siblings
2707
			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
2708
				sibling = parent[siblingName];
2709
 
2710
				if (sibling)
2711
					return sibling;
2712
			}
2713
		}
2714
	};
2715
 
2716
	function Node(name, type) {
2717
		this.name = name;
2718
		this.type = type;
2719
 
2720
		if (type === 1) {
2721
			this.attributes = [];
2722
			this.attributes.map = {};
2723
		}
2724
	}
2725
 
2726
	tinymce.extend(Node.prototype, {
2727
		replace : function(node) {
2728
			var self = this;
2729
 
2730
			if (node.parent)
2731
				node.remove();
2732
 
2733
			self.insert(node, self);
2734
			self.remove();
2735
 
2736
			return self;
2737
		},
2738
 
2739
		attr : function(name, value) {
2740
			var self = this, attrs, i, undef;
2741
 
2742
			if (typeof name !== "string") {
2743
				for (i in name)
2744
					self.attr(i, name[i]);
2745
 
2746
				return self;
2747
			}
2748
 
2749
			if (attrs = self.attributes) {
2750
				if (value !== undef) {
2751
					// Remove attribute
2752
					if (value === null) {
2753
						if (name in attrs.map) {
2754
							delete attrs.map[name];
2755
 
2756
							i = attrs.length;
2757
							while (i--) {
2758
								if (attrs[i].name === name) {
2759
									attrs = attrs.splice(i, 1);
2760
									return self;
2761
								}
2762
							}
2763
						}
2764
 
2765
						return self;
2766
					}
2767
 
2768
					// Set attribute
2769
					if (name in attrs.map) {
2770
						// Set attribute
2771
						i = attrs.length;
2772
						while (i--) {
2773
							if (attrs[i].name === name) {
2774
								attrs[i].value = value;
2775
								break;
2776
							}
2777
						}
2778
					} else
2779
						attrs.push({name: name, value: value});
2780
 
2781
					attrs.map[name] = value;
2782
 
2783
					return self;
2784
				} else {
2785
					return attrs.map[name];
2786
				}
2787
			}
2788
		},
2789
 
2790
		clone : function() {
2791
			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
2792
 
2793
			// Clone element attributes
2794
			if (selfAttrs = self.attributes) {
2795
				cloneAttrs = [];
2796
				cloneAttrs.map = {};
2797
 
2798
				for (i = 0, l = selfAttrs.length; i < l; i++) {
2799
					selfAttr = selfAttrs[i];
2800
 
2801
					// Clone everything except id
2802
					if (selfAttr.name !== 'id') {
2803
						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
2804
						cloneAttrs.map[selfAttr.name] = selfAttr.value;
2805
					}
2806
				}
2807
 
2808
				clone.attributes = cloneAttrs;
2809
			}
2810
 
2811
			clone.value = self.value;
2812
			clone.shortEnded = self.shortEnded;
2813
 
2814
			return clone;
2815
		},
2816
 
2817
		wrap : function(wrapper) {
2818
			var self = this;
2819
 
2820
			self.parent.insert(wrapper, self);
2821
			wrapper.append(self);
2822
 
2823
			return self;
2824
		},
2825
 
2826
		unwrap : function() {
2827
			var self = this, node, next;
2828
 
2829
			for (node = self.firstChild; node; ) {
2830
				next = node.next;
2831
				self.insert(node, self, true);
2832
				node = next;
2833
			}
2834
 
2835
			self.remove();
2836
		},
2837
 
2838
		remove : function() {
2839
			var self = this, parent = self.parent, next = self.next, prev = self.prev;
2840
 
2841
			if (parent) {
2842
				if (parent.firstChild === self) {
2843
					parent.firstChild = next;
2844
 
2845
					if (next)
2846
						next.prev = null;
2847
				} else {
2848
					prev.next = next;
2849
				}
2850
 
2851
				if (parent.lastChild === self) {
2852
					parent.lastChild = prev;
2853
 
2854
					if (prev)
2855
						prev.next = null;
2856
				} else {
2857
					next.prev = prev;
2858
				}
2859
 
2860
				self.parent = self.next = self.prev = null;
2861
			}
2862
 
2863
			return self;
2864
		},
2865
 
2866
		append : function(node) {
2867
			var self = this, last;
2868
 
2869
			if (node.parent)
2870
				node.remove();
2871
 
2872
			last = self.lastChild;
2873
			if (last) {
2874
				last.next = node;
2875
				node.prev = last;
2876
				self.lastChild = node;
2877
			} else
2878
				self.lastChild = self.firstChild = node;
2879
 
2880
			node.parent = self;
2881
 
2882
			return node;
2883
		},
2884
 
2885
		insert : function(node, ref_node, before) {
2886
			var parent;
2887
 
2888
			if (node.parent)
2889
				node.remove();
2890
 
2891
			parent = ref_node.parent || this;
2892
 
2893
			if (before) {
2894
				if (ref_node === parent.firstChild)
2895
					parent.firstChild = node;
2896
				else
2897
					ref_node.prev.next = node;
2898
 
2899
				node.prev = ref_node.prev;
2900
				node.next = ref_node;
2901
				ref_node.prev = node;
2902
			} else {
2903
				if (ref_node === parent.lastChild)
2904
					parent.lastChild = node;
2905
				else
2906
					ref_node.next.prev = node;
2907
 
2908
				node.next = ref_node.next;
2909
				node.prev = ref_node;
2910
				ref_node.next = node;
2911
			}
2912
 
2913
			node.parent = parent;
2914
 
2915
			return node;
2916
		},
2917
 
2918
		getAll : function(name) {
2919
			var self = this, node, collection = [];
2920
 
2921
			for (node = self.firstChild; node; node = walk(node, self)) {
2922
				if (node.name === name)
2923
					collection.push(node);
2924
			}
2925
 
2926
			return collection;
2927
		},
2928
 
2929
		empty : function() {
2930
			var self = this, nodes, i, node;
2931
 
2932
			// Remove all children
2933
			if (self.firstChild) {
2934
				nodes = [];
2935
 
2936
				// Collect the children
2937
				for (node = self.firstChild; node; node = walk(node, self))
2938
					nodes.push(node);
2939
 
2940
				// Remove the children
2941
				i = nodes.length;
2942
				while (i--) {
2943
					node = nodes[i];
2944
					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
2945
				}
2946
			}
2947
 
2948
			self.firstChild = self.lastChild = null;
2949
 
2950
			return self;
2951
		},
2952
 
2953
		isEmpty : function(elements) {
2954
			var self = this, node = self.firstChild, i, name;
2955
 
2956
			if (node) {
2957
				do {
2958
					if (node.type === 1) {
2959
						// Ignore bogus elements
2960
						if (node.attributes.map['data-mce-bogus'])
2961
							continue;
2962
 
2963
						// Keep empty elements like <img />
2964
						if (elements[node.name])
2965
							return false;
2966
 
2967
						// Keep elements with data attributes or name attribute like <a name="1"></a>
2968
						i = node.attributes.length;
2969
						while (i--) {
2970
							name = node.attributes[i].name;
2971
							if (name === "name" || name.indexOf('data-') === 0)
2972
								return false;
2973
						}
2974
					}
2975
 
2976
					// Keep non whitespace text nodes
2977
					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
2978
						return false;
2979
				} while (node = walk(node, self));
2980
			}
2981
 
2982
			return true;
2983
		},
2984
 
2985
		walk : function(prev) {
2986
			return walk(this, null, prev);
2987
		}
2988
	});
2989
 
2990
	tinymce.extend(Node, {
2991
		create : function(name, attrs) {
2992
			var node, attrName;
2993
 
2994
			// Create node
2995
			node = new Node(name, typeLookup[name] || 1);
2996
 
2997
			// Add attributes if needed
2998
			if (attrs) {
2999
				for (attrName in attrs)
3000
					node.attr(attrName, attrs[attrName]);
3001
			}
3002
 
3003
			return node;
3004
		}
3005
	});
3006
 
3007
	tinymce.html.Node = Node;
3008
})(tinymce);
3009
 
3010
(function(tinymce) {
3011
	var Node = tinymce.html.Node;
3012
 
3013
	tinymce.html.DomParser = function(settings, schema) {
3014
		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3015
 
3016
		settings = settings || {};
3017
		settings.validate = "validate" in settings ? settings.validate : true;
3018
		settings.root_name = settings.root_name || 'body';
3019
		self.schema = schema = schema || new tinymce.html.Schema();
3020
 
3021
		function fixInvalidChildren(nodes) {
3022
			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3023
				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3024
 
3025
			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3026
			nonEmptyElements = schema.getNonEmptyElements();
3027
 
3028
			for (ni = 0; ni < nodes.length; ni++) {
3029
				node = nodes[ni];
3030
 
3031
				// Already removed
3032
				if (!node.parent)
3033
					continue;
3034
 
3035
				// Get list of all parent nodes until we find a valid parent to stick the child into
3036
				parents = [node];
3037
				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3038
					parents.push(parent);
3039
 
3040
				// Found a suitable parent
3041
				if (parent && parents.length > 1) {
3042
					// Reverse the array since it makes looping easier
3043
					parents.reverse();
3044
 
3045
					// Clone the related parent and insert that after the moved node
3046
					newParent = currentNode = self.filterNode(parents[0].clone());
3047
 
3048
					// Start cloning and moving children on the left side of the target node
3049
					for (i = 0; i < parents.length - 1; i++) {
3050
						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3051
							tempNode = self.filterNode(parents[i].clone());
3052
							currentNode.append(tempNode);
3053
						} else
3054
							tempNode = currentNode;
3055
 
3056
						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3057
							nextNode = childNode.next;
3058
							tempNode.append(childNode);
3059
							childNode = nextNode;
3060
						}
3061
 
3062
						currentNode = tempNode;
3063
					}
3064
 
3065
					if (!newParent.isEmpty(nonEmptyElements)) {
3066
						parent.insert(newParent, parents[0], true);
3067
						parent.insert(node, newParent);
3068
					} else {
3069
						parent.insert(node, parents[0], true);
3070
					}
3071
 
3072
					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3073
					parent = parents[0];
3074
					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3075
						parent.empty().remove();
3076
					}
3077
				} else if (node.parent) {
3078
					// If it's an LI try to find a UL/OL for it or wrap it
3079
					if (node.name === 'li') {
3080
						sibling = node.prev;
3081
						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3082
							sibling.append(node);
3083
							continue;
3084
						}
3085
 
3086
						sibling = node.next;
3087
						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3088
							sibling.insert(node, sibling.firstChild, true);
3089
							continue;
3090
						}
3091
 
3092
						node.wrap(self.filterNode(new Node('ul', 1)));
3093
						continue;
3094
					}
3095
 
3096
					// Try wrapping the element in a DIV
3097
					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3098
						node.wrap(self.filterNode(new Node('div', 1)));
3099
					} else {
3100
						// We failed wrapping it, then remove or unwrap it
3101
						if (node.name === 'style' || node.name === 'script')
3102
							node.empty().remove();
3103
						else
3104
							node.unwrap();
3105
					}
3106
				}
3107
			}
3108
		};
3109
 
3110
		self.filterNode = function(node) {
3111
			var i, name, list;
3112
 
3113
			// Run element filters
3114
			if (name in nodeFilters) {
3115
				list = matchedNodes[name];
3116
 
3117
				if (list)
3118
					list.push(node);
3119
				else
3120
					matchedNodes[name] = [node];
3121
			}
3122
 
3123
			// Run attribute filters
3124
			i = attributeFilters.length;
3125
			while (i--) {
3126
				name = attributeFilters[i].name;
3127
 
3128
				if (name in node.attributes.map) {
3129
					list = matchedAttributes[name];
3130
 
3131
					if (list)
3132
						list.push(node);
3133
					else
3134
						matchedAttributes[name] = [node];
3135
				}
3136
			}
3137
 
3138
			return node;
3139
		};
3140
 
3141
		self.addNodeFilter = function(name, callback) {
3142
			tinymce.each(tinymce.explode(name), function(name) {
3143
				var list = nodeFilters[name];
3144
 
3145
				if (!list)
3146
					nodeFilters[name] = list = [];
3147
 
3148
				list.push(callback);
3149
			});
3150
		};
3151
 
3152
		self.addAttributeFilter = function(name, callback) {
3153
			tinymce.each(tinymce.explode(name), function(name) {
3154
				var i;
3155
 
3156
				for (i = 0; i < attributeFilters.length; i++) {
3157
					if (attributeFilters[i].name === name) {
3158
						attributeFilters[i].callbacks.push(callback);
3159
						return;
3160
					}
3161
				}
3162
 
3163
				attributeFilters.push({name: name, callbacks: [callback]});
3164
			});
3165
		};
3166
 
3167
		self.parse = function(html, args) {
3168
			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3169
				blockElements, startWhiteSpaceRegExp, invalidChildren = [],
3170
				endWhiteSpaceRegExp, allWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3171
 
3172
			args = args || {};
3173
			matchedNodes = {};
3174
			matchedAttributes = {};
3175
			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3176
			nonEmptyElements = schema.getNonEmptyElements();
3177
			children = schema.children;
3178
			validate = settings.validate;
3179
			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3180
 
3181
			whiteSpaceElements = schema.getWhiteSpaceElements();
3182
			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3183
			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3184
			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3185
 
3186
			function addRootBlocks() {
3187
				var node = rootNode.firstChild, next, rootBlockNode;
3188
 
3189
				while (node) {
3190
					next = node.next;
3191
 
3192
					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3193
						if (!rootBlockNode) {
3194
							// Create a new root block element
3195
							rootBlockNode = createNode(rootBlockName, 1);
3196
							rootNode.insert(rootBlockNode, node);
3197
							rootBlockNode.append(node);
3198
						} else
3199
							rootBlockNode.append(node);
3200
					} else {
3201
						rootBlockNode = null;
3202
					}
3203
 
3204
					node = next;
3205
				};
3206
			};
3207
 
3208
			function createNode(name, type) {
3209
				var node = new Node(name, type), list;
3210
 
3211
				if (name in nodeFilters) {
3212
					list = matchedNodes[name];
3213
 
3214
					if (list)
3215
						list.push(node);
3216
					else
3217
						matchedNodes[name] = [node];
3218
				}
3219
 
3220
				return node;
3221
			};
3222
 
3223
			function removeWhitespaceBefore(node) {
3224
				var textNode, textVal, sibling;
3225
 
3226
				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3227
					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3228
 
3229
					if (textVal.length > 0) {
3230
						textNode.value = textVal;
3231
						textNode = textNode.prev;
3232
					} else {
3233
						sibling = textNode.prev;
3234
						textNode.remove();
3235
						textNode = sibling;
3236
					}
3237
				}
3238
			};
3239
 
3240
			parser = new tinymce.html.SaxParser({
3241
				validate : validate,
3242
				fix_self_closing : !validate, // Let the DOM parser handle <li> in <li> or <p> in <p> for better results
3243
 
3244
				cdata: function(text) {
3245
					node.append(createNode('#cdata', 4)).value = text;
3246
				},
3247
 
3248
				text: function(text, raw) {
3249
					var textNode;
3250
 
3251
					// Trim all redundant whitespace on non white space elements
3252
					if (!whiteSpaceElements[node.name]) {
3253
						text = text.replace(allWhiteSpaceRegExp, ' ');
3254
 
3255
						if (node.lastChild && blockElements[node.lastChild.name])
3256
							text = text.replace(startWhiteSpaceRegExp, '');
3257
					}
3258
 
3259
					// Do we need to create the node
3260
					if (text.length !== 0) {
3261
						textNode = createNode('#text', 3);
3262
						textNode.raw = !!raw;
3263
						node.append(textNode).value = text;
3264
					}
3265
				},
3266
 
3267
				comment: function(text) {
3268
					node.append(createNode('#comment', 8)).value = text;
3269
				},
3270
 
3271
				pi: function(name, text) {
3272
					node.append(createNode(name, 7)).value = text;
3273
					removeWhitespaceBefore(node);
3274
				},
3275
 
3276
				doctype: function(text) {
3277
					var newNode;
3278
 
3279
					newNode = node.append(createNode('#doctype', 10));
3280
					newNode.value = text;
3281
					removeWhitespaceBefore(node);
3282
				},
3283
 
3284
				start: function(name, attrs, empty) {
3285
					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
3286
 
3287
					elementRule = validate ? schema.getElementRule(name) : {};
3288
					if (elementRule) {
3289
						newNode = createNode(elementRule.outputName || name, 1);
3290
						newNode.attributes = attrs;
3291
						newNode.shortEnded = empty;
3292
 
3293
						node.append(newNode);
3294
 
3295
						// Check if node is valid child of the parent node is the child is
3296
						// unknown we don't collect it since it's probably a custom element
3297
						parent = children[node.name];
3298
						if (parent && children[newNode.name] && !parent[newNode.name])
3299
							invalidChildren.push(newNode);
3300
 
3301
						attrFiltersLen = attributeFilters.length;
3302
						while (attrFiltersLen--) {
3303
							attrName = attributeFilters[attrFiltersLen].name;
3304
 
3305
							if (attrName in attrs.map) {
3306
								list = matchedAttributes[attrName];
3307
 
3308
								if (list)
3309
									list.push(newNode);
3310
								else
3311
									matchedAttributes[attrName] = [newNode];
3312
							}
3313
						}
3314
 
3315
						// Trim whitespace before block
3316
						if (blockElements[name])
3317
							removeWhitespaceBefore(newNode);
3318
 
3319
						// Change current node if the element wasn't empty i.e not <br /> or <img />
3320
						if (!empty)
3321
							node = newNode;
3322
					}
3323
				},
3324
 
3325
				end: function(name) {
3326
					var textNode, elementRule, text, sibling, tempNode;
3327
 
3328
					elementRule = validate ? schema.getElementRule(name) : {};
3329
					if (elementRule) {
3330
						if (blockElements[name]) {
3331
							if (!whiteSpaceElements[node.name]) {
3332
								// Trim whitespace at beginning of block
3333
								for (textNode = node.firstChild; textNode && textNode.type === 3; ) {
3334
									text = textNode.value.replace(startWhiteSpaceRegExp, '');
3335
 
3336
									if (text.length > 0) {
3337
										textNode.value = text;
3338
										textNode = textNode.next;
3339
									} else {
3340
										sibling = textNode.next;
3341
										textNode.remove();
3342
										textNode = sibling;
3343
									}
3344
								}
3345
 
3346
								// Trim whitespace at end of block
3347
								for (textNode = node.lastChild; textNode && textNode.type === 3; ) {
3348
									text = textNode.value.replace(endWhiteSpaceRegExp, '');
3349
 
3350
									if (text.length > 0) {
3351
										textNode.value = text;
3352
										textNode = textNode.prev;
3353
									} else {
3354
										sibling = textNode.prev;
3355
										textNode.remove();
3356
										textNode = sibling;
3357
									}
3358
								}
3359
							}
3360
 
3361
							// Trim start white space
3362
							textNode = node.prev;
3363
							if (textNode && textNode.type === 3) {
3364
								text = textNode.value.replace(startWhiteSpaceRegExp, '');
3365
 
3366
								if (text.length > 0)
3367
									textNode.value = text;
3368
								else
3369
									textNode.remove();
3370
							}
3371
						}
3372
 
3373
						// Handle empty nodes
3374
						if (elementRule.removeEmpty || elementRule.paddEmpty) {
3375
							if (node.isEmpty(nonEmptyElements)) {
3376
								if (elementRule.paddEmpty)
3377
									node.empty().append(new Node('#text', '3')).value = '\u00a0';
3378
								else {
3379
									// Leave nodes that have a name like <a name="name">
3380
									if (!node.attributes.map.name) {
3381
										tempNode = node.parent;
3382
										node.empty().remove();
3383
										node = tempNode;
3384
										return;
3385
									}
3386
								}
3387
							}
3388
						}
3389
 
3390
						node = node.parent;
3391
					}
3392
				}
3393
			}, schema);
3394
 
3395
			rootNode = node = new Node(args.context || settings.root_name, 11);
3396
 
3397
			parser.parse(html);
3398
 
3399
			// Fix invalid children or report invalid children in a contextual parsing
3400
			if (validate && invalidChildren.length) {
3401
				if (!args.context)
3402
					fixInvalidChildren(invalidChildren);
3403
				else
3404
					args.invalid = true;
3405
			}
3406
 
3407
			// Wrap nodes in the root into block elements if the root is body
3408
			if (rootBlockName && rootNode.name == 'body')
3409
				addRootBlocks();
3410
 
3411
			// Run filters only when the contents is valid
3412
			if (!args.invalid) {
3413
				// Run node filters
3414
				for (name in matchedNodes) {
3415
					list = nodeFilters[name];
3416
					nodes = matchedNodes[name];
3417
 
3418
					// Remove already removed children
3419
					fi = nodes.length;
3420
					while (fi--) {
3421
						if (!nodes[fi].parent)
3422
							nodes.splice(fi, 1);
3423
					}
3424
 
3425
					for (i = 0, l = list.length; i < l; i++)
3426
						list[i](nodes, name, args);
3427
				}
3428
 
3429
				// Run attribute filters
3430
				for (i = 0, l = attributeFilters.length; i < l; i++) {
3431
					list = attributeFilters[i];
3432
 
3433
					if (list.name in matchedAttributes) {
3434
						nodes = matchedAttributes[list.name];
3435
 
3436
						// Remove already removed children
3437
						fi = nodes.length;
3438
						while (fi--) {
3439
							if (!nodes[fi].parent)
3440
								nodes.splice(fi, 1);
3441
						}
3442
 
3443
						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
3444
							list.callbacks[fi](nodes, list.name, args);
3445
					}
3446
				}
3447
			}
3448
 
3449
			return rootNode;
3450
		};
3451
 
3452
		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
3453
		// make it possible to place the caret inside empty blocks. This logic tries to remove
3454
		// these elements and keep br elements that where intended to be there intact
3455
		if (settings.remove_trailing_brs) {
3456
			self.addNodeFilter('br', function(nodes, name) {
3457
				var i, l = nodes.length, node, blockElements = schema.getBlockElements(),
3458
					nonEmptyElements = schema.getNonEmptyElements(), parent, prev, prevName;
3459
 
3460
				// Remove brs from body element as well
3461
				blockElements.body = 1;
3462
 
3463
				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
3464
				for (i = 0; i < l; i++) {
3465
					node = nodes[i];
3466
					parent = node.parent;
3467
 
3468
					if (blockElements[node.parent.name] && node === parent.lastChild) {
3469
						// Loop all nodes to the right of the current node and check for other BR elements
3470
						// excluding bookmarks since they are invisible
3471
						prev = node.prev;
3472
						while (prev) {
3473
							prevName = prev.name;
3474
 
3475
							// Ignore bookmarks
3476
							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
3477
								// Found a non BR element
3478
								if (prevName !== "br")
3479
									break;
3480
 
3481
								// Found another br it's a <br><br> structure then don't remove anything
3482
								if (prevName === 'br') {
3483
									node = null;
3484
									break;
3485
								}
3486
							}
3487
 
3488
							prev = prev.prev;
3489
						}
3490
 
3491
						if (node) {
3492
							node.remove();
3493
 
3494
							// Is the parent to be considered empty after we removed the BR
3495
							if (parent.isEmpty(nonEmptyElements)) {
3496
								elementRule = schema.getElementRule(parent.name);
3497
 
3498
								// Remove or padd the element depending on schema rule
3499
								if (elementRule) {
3500
								  if (elementRule.removeEmpty)
3501
									  parent.remove();
3502
								  else if (elementRule.paddEmpty)
3503
									  parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
3504
							  }
3505
              }
3506
						}
3507
					}
3508
				}
3509
			});
3510
		}
3511
	}
3512
})(tinymce);
3513
 
3514
tinymce.html.Writer = function(settings) {
3515
	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
3516
 
3517
	settings = settings || {};
3518
	indent = settings.indent;
3519
	indentBefore = tinymce.makeMap(settings.indent_before || '');
3520
	indentAfter = tinymce.makeMap(settings.indent_after || '');
3521
	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
3522
	htmlOutput = settings.element_format == "html";
3523
 
3524
	return {
3525
		start: function(name, attrs, empty) {
3526
			var i, l, attr, value;
3527
 
3528
			if (indent && indentBefore[name] && html.length > 0) {
3529
				value = html[html.length - 1];
3530
 
3531
				if (value.length > 0 && value !== '\n')
3532
					html.push('\n');
3533
			}
3534
 
3535
			html.push('<', name);
3536
 
3537
			if (attrs) {
3538
				for (i = 0, l = attrs.length; i < l; i++) {
3539
					attr = attrs[i];
3540
					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
3541
				}
3542
			}
3543
 
3544
			if (!empty || htmlOutput)
3545
				html[html.length] = '>';
3546
			else
3547
				html[html.length] = ' />';
3548
 
3549
			if (empty && indent && indentAfter[name] && html.length > 0) {
3550
				value = html[html.length - 1];
3551
 
3552
				if (value.length > 0 && value !== '\n')
3553
					html.push('\n');
3554
			}
3555
		},
3556
 
3557
		end: function(name) {
3558
			var value;
3559
 
3560
			/*if (indent && indentBefore[name] && html.length > 0) {
3561
				value = html[html.length - 1];
3562
 
3563
				if (value.length > 0 && value !== '\n')
3564
					html.push('\n');
3565
			}*/
3566
 
3567
			html.push('</', name, '>');
3568
 
3569
			if (indent && indentAfter[name] && html.length > 0) {
3570
				value = html[html.length - 1];
3571
 
3572
				if (value.length > 0 && value !== '\n')
3573
					html.push('\n');
3574
			}
3575
		},
3576
 
3577
		text: function(text, raw) {
3578
			if (text.length > 0)
3579
				html[html.length] = raw ? text : encode(text);
3580
		},
3581
 
3582
		cdata: function(text) {
3583
			html.push('<![CDATA[', text, ']]>');
3584
		},
3585
 
3586
		comment: function(text) {
3587
			html.push('<!--', text, '-->');
3588
		},
3589
 
3590
		pi: function(name, text) {
3591
			if (text)
3592
				html.push('<?', name, ' ', text, '?>');
3593
			else
3594
				html.push('<?', name, '?>');
3595
 
3596
			if (indent)
3597
				html.push('\n');
3598
		},
3599
 
3600
		doctype: function(text) {
3601
			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
3602
		},
3603
 
3604
		reset: function() {
3605
			html.length = 0;
3606
		},
3607
 
3608
		getContent: function() {
3609
			return html.join('').replace(/\n$/, '');
3610
		}
3611
	};
3612
};
3613
 
3614
(function(tinymce) {
3615
	tinymce.html.Serializer = function(settings, schema) {
3616
		var self = this, writer = new tinymce.html.Writer(settings);
3617
 
3618
		settings = settings || {};
3619
		settings.validate = "validate" in settings ? settings.validate : true;
3620
 
3621
		self.schema = schema = schema || new tinymce.html.Schema();
3622
		self.writer = writer;
3623
 
3624
		self.serialize = function(node) {
3625
			var handlers, validate;
3626
 
3627
			validate = settings.validate;
3628
 
3629
			handlers = {
3630
				// #text
3631
				3: function(node, raw) {
3632
					writer.text(node.value, node.raw);
3633
				},
3634
 
3635
				// #comment
3636
				8: function(node) {
3637
					writer.comment(node.value);
3638
				},
3639
 
3640
				// Processing instruction
3641
				7: function(node) {
3642
					writer.pi(node.name, node.value);
3643
				},
3644
 
3645
				// Doctype
3646
				10: function(node) {
3647
					writer.doctype(node.value);
3648
				},
3649
 
3650
				// CDATA
3651
				4: function(node) {
3652
					writer.cdata(node.value);
3653
				},
3654
 
3655
 				// Document fragment
3656
				11: function(node) {
3657
					if ((node = node.firstChild)) {
3658
						do {
3659
							walk(node);
3660
						} while (node = node.next);
3661
					}
3662
				}
3663
			};
3664
 
3665
			writer.reset();
3666
 
3667
			function walk(node) {
3668
				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
3669
 
3670
				if (!handler) {
3671
					name = node.name;
3672
					isEmpty = node.shortEnded;
3673
					attrs = node.attributes;
3674
 
3675
					// Sort attributes
3676
					if (validate && attrs && attrs.length > 1) {
3677
						sortedAttrs = [];
3678
						sortedAttrs.map = {};
3679
 
3680
						elementRule = schema.getElementRule(node.name);
3681
						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
3682
							attrName = elementRule.attributesOrder[i];
3683
 
3684
							if (attrName in attrs.map) {
3685
								attrValue = attrs.map[attrName];
3686
								sortedAttrs.map[attrName] = attrValue;
3687
								sortedAttrs.push({name: attrName, value: attrValue});
3688
							}
3689
						}
3690
 
3691
						for (i = 0, l = attrs.length; i < l; i++) {
3692
							attrName = attrs[i].name;
3693
 
3694
							if (!(attrName in sortedAttrs.map)) {
3695
								attrValue = attrs.map[attrName];
3696
								sortedAttrs.map[attrName] = attrValue;
3697
								sortedAttrs.push({name: attrName, value: attrValue});
3698
							}
3699
						}
3700
 
3701
						attrs = sortedAttrs;
3702
					}
3703
 
3704
					writer.start(node.name, attrs, isEmpty);
3705
 
3706
					if (!isEmpty) {
3707
						if ((node = node.firstChild)) {
3708
							do {
3709
								walk(node);
3710
							} while (node = node.next);
3711
						}
3712
 
3713
						writer.end(name);
3714
					}
3715
				} else
3716
					handler(node);
3717
			}
3718
 
3719
			// Serialize element and treat all non elements as fragments
3720
			if (node.type == 1 && !settings.inner)
3721
				walk(node);
3722
			else
3723
				handlers[11](node);
3724
 
3725
			return writer.getContent();
3726
		};
3727
	}
3728
})(tinymce);
3729
 
3730
(function(tinymce) {
3731
	// Shorten names
3732
	var each = tinymce.each,
3733
		is = tinymce.is,
3734
		isWebKit = tinymce.isWebKit,
3735
		isIE = tinymce.isIE,
3736
		Entities = tinymce.html.Entities,
3737
		simpleSelectorRe = /^([a-z0-9],?)+$/i,
3738
		blockElementsMap = tinymce.html.Schema.blockElementsMap,
3739
		whiteSpaceRegExp = /^[ \t\r\n]*$/;
3740
 
3741
	tinymce.create('tinymce.dom.DOMUtils', {
3742
		doc : null,
3743
		root : null,
3744
		files : null,
3745
		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
3746
		props : {
3747
			"for" : "htmlFor",
3748
			"class" : "className",
3749
			className : "className",
3750
			checked : "checked",
3751
			disabled : "disabled",
3752
			maxlength : "maxLength",
3753
			readonly : "readOnly",
3754
			selected : "selected",
3755
			value : "value",
3756
			id : "id",
3757
			name : "name",
3758
			type : "type"
3759
		},
3760
 
3761
		DOMUtils : function(d, s) {
3762
			var t = this, globalStyle, name;
3763
 
3764
			t.doc = d;
3765
			t.win = window;
3766
			t.files = {};
3767
			t.cssFlicker = false;
3768
			t.counter = 0;
3769
			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
3770
			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
3771
			t.hasOuterHTML = "outerHTML" in d.createElement("a");
3772
 
3773
			t.settings = s = tinymce.extend({
3774
				keep_values : false,
3775
				hex_colors : 1
3776
			}, s);
3777
 
3778
			t.schema = s.schema;
3779
			t.styles = new tinymce.html.Styles({
3780
				url_converter : s.url_converter,
3781
				url_converter_scope : s.url_converter_scope
3782
			}, s.schema);
3783
 
3784
			// Fix IE6SP2 flicker and check it failed for pre SP2
3785
			if (tinymce.isIE6) {
3786
				try {
3787
					d.execCommand('BackgroundImageCache', false, true);
3788
				} catch (e) {
3789
					t.cssFlicker = true;
3790
				}
3791
			}
3792
 
3793
			if (isIE && s.schema) {
3794
				// Add missing HTML 4/5 elements to IE
3795
				('abbr article aside audio canvas ' +
3796
				'details figcaption figure footer ' +
3797
				'header hgroup mark menu meter nav ' +
3798
				'output progress section summary ' +
3799
				'time video').replace(/\w+/g, function(name) {
3800
					d.createElement(name);
3801
				});
3802
 
3803
				// Create all custom elements
3804
				for (name in s.schema.getCustomElements()) {
3805
					d.createElement(name);
3806
				}
3807
			}
3808
 
3809
			tinymce.addUnload(t.destroy, t);
3810
		},
3811
 
3812
		getRoot : function() {
3813
			var t = this, s = t.settings;
3814
 
3815
			return (s && t.get(s.root_element)) || t.doc.body;
3816
		},
3817
 
3818
		getViewPort : function(w) {
3819
			var d, b;
3820
 
3821
			w = !w ? this.win : w;
3822
			d = w.document;
3823
			b = this.boxModel ? d.documentElement : d.body;
3824
 
3825
			// Returns viewport size excluding scrollbars
3826
			return {
3827
				x : w.pageXOffset || b.scrollLeft,
3828
				y : w.pageYOffset || b.scrollTop,
3829
				w : w.innerWidth || b.clientWidth,
3830
				h : w.innerHeight || b.clientHeight
3831
			};
3832
		},
3833
 
3834
		getRect : function(e) {
3835
			var p, t = this, sr;
3836
 
3837
			e = t.get(e);
3838
			p = t.getPos(e);
3839
			sr = t.getSize(e);
3840
 
3841
			return {
3842
				x : p.x,
3843
				y : p.y,
3844
				w : sr.w,
3845
				h : sr.h
3846
			};
3847
		},
3848
 
3849
		getSize : function(e) {
3850
			var t = this, w, h;
3851
 
3852
			e = t.get(e);
3853
			w = t.getStyle(e, 'width');
3854
			h = t.getStyle(e, 'height');
3855
 
3856
			// Non pixel value, then force offset/clientWidth
3857
			if (w.indexOf('px') === -1)
3858
				w = 0;
3859
 
3860
			// Non pixel value, then force offset/clientWidth
3861
			if (h.indexOf('px') === -1)
3862
				h = 0;
3863
 
3864
			return {
3865
				w : parseInt(w) || e.offsetWidth || e.clientWidth,
3866
				h : parseInt(h) || e.offsetHeight || e.clientHeight
3867
			};
3868
		},
3869
 
3870
		getParent : function(n, f, r) {
3871
			return this.getParents(n, f, r, false);
3872
		},
3873
 
3874
		getParents : function(n, f, r, c) {
3875
			var t = this, na, se = t.settings, o = [];
3876
 
3877
			n = t.get(n);
3878
			c = c === undefined;
3879
 
3880
			if (se.strict_root)
3881
				r = r || t.getRoot();
3882
 
3883
			// Wrap node name as func
3884
			if (is(f, 'string')) {
3885
				na = f;
3886
 
3887
				if (f === '*') {
3888
					f = function(n) {return n.nodeType == 1;};
3889
				} else {
3890
					f = function(n) {
3891
						return t.is(n, na);
3892
					};
3893
				}
3894
			}
3895
 
3896
			while (n) {
3897
				if (n == r || !n.nodeType || n.nodeType === 9)
3898
					break;
3899
 
3900
				if (!f || f(n)) {
3901
					if (c)
3902
						o.push(n);
3903
					else
3904
						return n;
3905
				}
3906
 
3907
				n = n.parentNode;
3908
			}
3909
 
3910
			return c ? o : null;
3911
		},
3912
 
3913
		get : function(e) {
3914
			var n;
3915
 
3916
			if (e && this.doc && typeof(e) == 'string') {
3917
				n = e;
3918
				e = this.doc.getElementById(e);
3919
 
3920
				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
3921
				if (e && e.id !== n)
3922
					return this.doc.getElementsByName(n)[1];
3923
			}
3924
 
3925
			return e;
3926
		},
3927
 
3928
		getNext : function(node, selector) {
3929
			return this._findSib(node, selector, 'nextSibling');
3930
		},
3931
 
3932
		getPrev : function(node, selector) {
3933
			return this._findSib(node, selector, 'previousSibling');
3934
		},
3935
 
3936
 
3937
		add : function(p, n, a, h, c) {
3938
			var t = this;
3939
 
3940
			return this.run(p, function(p) {
3941
				var e, k;
3942
 
3943
				e = is(n, 'string') ? t.doc.createElement(n) : n;
3944
				t.setAttribs(e, a);
3945
 
3946
				if (h) {
3947
					if (h.nodeType)
3948
						e.appendChild(h);
3949
					else
3950
						t.setHTML(e, h);
3951
				}
3952
 
3953
				return !c ? p.appendChild(e) : e;
3954
			});
3955
		},
3956
 
3957
		create : function(n, a, h) {
3958
			return this.add(this.doc.createElement(n), n, a, h, 1);
3959
		},
3960
 
3961
		createHTML : function(n, a, h) {
3962
			var o = '', t = this, k;
3963
 
3964
			o += '<' + n;
3965
 
3966
			for (k in a) {
3967
				if (a.hasOwnProperty(k))
3968
					o += ' ' + k + '="' + t.encode(a[k]) + '"';
3969
			}
3970
 
3971
			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
3972
			if (typeof(h) != "undefined")
3973
				return o + '>' + h + '</' + n + '>';
3974
 
3975
			return o + ' />';
3976
		},
3977
 
3978
		remove : function(node, keep_children) {
3979
			return this.run(node, function(node) {
3980
				var child, parent = node.parentNode;
3981
 
3982
				if (!parent)
3983
					return null;
3984
 
3985
				if (keep_children) {
3986
					while (child = node.firstChild) {
3987
						// IE 8 will crash if you don't remove completely empty text nodes
3988
						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
3989
							parent.insertBefore(child, node);
3990
						else
3991
							node.removeChild(child);
3992
					}
3993
				}
3994
 
3995
				return parent.removeChild(node);
3996
			});
3997
		},
3998
 
3999
		setStyle : function(n, na, v) {
4000
			var t = this;
4001
 
4002
			return t.run(n, function(e) {
4003
				var s, i;
4004
 
4005
				s = e.style;
4006
 
4007
				// Camelcase it, if needed
4008
				na = na.replace(/-(\D)/g, function(a, b){
4009
					return b.toUpperCase();
4010
				});
4011
 
4012
				// Default px suffix on these
4013
				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
4014
					v += 'px';
4015
 
4016
				switch (na) {
4017
					case 'opacity':
4018
						// IE specific opacity
4019
						if (isIE) {
4020
							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
4021
 
4022
							if (!n.currentStyle || !n.currentStyle.hasLayout)
4023
								s.display = 'inline-block';
4024
						}
4025
 
4026
						// Fix for older browsers
4027
						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
4028
						break;
4029
 
4030
					case 'float':
4031
						isIE ? s.styleFloat = v : s.cssFloat = v;
4032
						break;
4033
 
4034
					default:
4035
						s[na] = v || '';
4036
				}
4037
 
4038
				// Force update of the style data
4039
				if (t.settings.update_styles)
4040
					t.setAttrib(e, 'data-mce-style');
4041
			});
4042
		},
4043
 
4044
		getStyle : function(n, na, c) {
4045
			n = this.get(n);
4046
 
4047
			if (!n)
4048
				return;
4049
 
4050
			// Gecko
4051
			if (this.doc.defaultView && c) {
4052
				// Remove camelcase
4053
				na = na.replace(/[A-Z]/g, function(a){
4054
					return '-' + a;
4055
				});
4056
 
4057
				try {
4058
					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
4059
				} catch (ex) {
4060
					// Old safari might fail
4061
					return null;
4062
				}
4063
			}
4064
 
4065
			// Camelcase it, if needed
4066
			na = na.replace(/-(\D)/g, function(a, b){
4067
				return b.toUpperCase();
4068
			});
4069
 
4070
			if (na == 'float')
4071
				na = isIE ? 'styleFloat' : 'cssFloat';
4072
 
4073
			// IE & Opera
4074
			if (n.currentStyle && c)
4075
				return n.currentStyle[na];
4076
 
4077
			return n.style ? n.style[na] : undefined;
4078
		},
4079
 
4080
		setStyles : function(e, o) {
4081
			var t = this, s = t.settings, ol;
4082
 
4083
			ol = s.update_styles;
4084
			s.update_styles = 0;
4085
 
4086
			each(o, function(v, n) {
4087
				t.setStyle(e, n, v);
4088
			});
4089
 
4090
			// Update style info
4091
			s.update_styles = ol;
4092
			if (s.update_styles)
4093
				t.setAttrib(e, s.cssText);
4094
		},
4095
 
4096
		removeAllAttribs: function(e) {
4097
			return this.run(e, function(e) {
4098
				var i, attrs = e.attributes;
4099
				for (i = attrs.length - 1; i >= 0; i--) {
4100
					e.removeAttributeNode(attrs.item(i));
4101
				}
4102
			});
4103
		},
4104
 
4105
		setAttrib : function(e, n, v) {
4106
			var t = this;
4107
 
4108
			// Whats the point
4109
			if (!e || !n)
4110
				return;
4111
 
4112
			// Strict XML mode
4113
			if (t.settings.strict)
4114
				n = n.toLowerCase();
4115
 
4116
			return this.run(e, function(e) {
4117
				var s = t.settings;
4118
 
4119
				switch (n) {
4120
					case "style":
4121
						if (!is(v, 'string')) {
4122
							each(v, function(v, n) {
4123
								t.setStyle(e, n, v);
4124
							});
4125
 
4126
							return;
4127
						}
4128
 
4129
						// No mce_style for elements with these since they might get resized by the user
4130
						if (s.keep_values) {
4131
							if (v && !t._isRes(v))
4132
								e.setAttribute('data-mce-style', v, 2);
4133
							else
4134
								e.removeAttribute('data-mce-style', 2);
4135
						}
4136
 
4137
						e.style.cssText = v;
4138
						break;
4139
 
4140
					case "class":
4141
						e.className = v || ''; // Fix IE null bug
4142
						break;
4143
 
4144
					case "src":
4145
					case "href":
4146
						if (s.keep_values) {
4147
							if (s.url_converter)
4148
								v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
4149
 
4150
							t.setAttrib(e, 'data-mce-' + n, v, 2);
4151
						}
4152
 
4153
						break;
4154
 
4155
					case "shape":
4156
						e.setAttribute('data-mce-style', v);
4157
						break;
4158
				}
4159
 
4160
				if (is(v) && v !== null && v.length !== 0)
4161
					e.setAttribute(n, '' + v, 2);
4162
				else
4163
					e.removeAttribute(n, 2);
4164
			});
4165
		},
4166
 
4167
		setAttribs : function(e, o) {
4168
			var t = this;
4169
 
4170
			return this.run(e, function(e) {
4171
				each(o, function(v, n) {
4172
					t.setAttrib(e, n, v);
4173
				});
4174
			});
4175
		},
4176
 
4177
		getAttrib : function(e, n, dv) {
4178
			var v, t = this, undef;
4179
 
4180
			e = t.get(e);
4181
 
4182
			if (!e || e.nodeType !== 1)
4183
				return dv === undef ? false : dv;
4184
 
4185
			if (!is(dv))
4186
				dv = '';
4187
 
4188
			// Try the mce variant for these
4189
			if (/^(src|href|style|coords|shape)$/.test(n)) {
4190
				v = e.getAttribute("data-mce-" + n);
4191
 
4192
				if (v)
4193
					return v;
4194
			}
4195
 
4196
			if (isIE && t.props[n]) {
4197
				v = e[t.props[n]];
4198
				v = v && v.nodeValue ? v.nodeValue : v;
4199
			}
4200
 
4201
			if (!v)
4202
				v = e.getAttribute(n, 2);
4203
 
4204
			// Check boolean attribs
4205
			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
4206
				if (e[t.props[n]] === true && v === '')
4207
					return n;
4208
 
4209
				return v ? n : '';
4210
			}
4211
 
4212
			// Inner input elements will override attributes on form elements
4213
			if (e.nodeName === "FORM" && e.getAttributeNode(n))
4214
				return e.getAttributeNode(n).nodeValue;
4215
 
4216
			if (n === 'style') {
4217
				v = v || e.style.cssText;
4218
 
4219
				if (v) {
4220
					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
4221
 
4222
					if (t.settings.keep_values && !t._isRes(v))
4223
						e.setAttribute('data-mce-style', v);
4224
				}
4225
			}
4226
 
4227
			// Remove Apple and WebKit stuff
4228
			if (isWebKit && n === "class" && v)
4229
				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
4230
 
4231
			// Handle IE issues
4232
			if (isIE) {
4233
				switch (n) {
4234
					case 'rowspan':
4235
					case 'colspan':
4236
						// IE returns 1 as default value
4237
						if (v === 1)
4238
							v = '';
4239
 
4240
						break;
4241
 
4242
					case 'size':
4243
						// IE returns +0 as default value for size
4244
						if (v === '+0' || v === 20 || v === 0)
4245
							v = '';
4246
 
4247
						break;
4248
 
4249
					case 'width':
4250
					case 'height':
4251
					case 'vspace':
4252
					case 'checked':
4253
					case 'disabled':
4254
					case 'readonly':
4255
						if (v === 0)
4256
							v = '';
4257
 
4258
						break;
4259
 
4260
					case 'hspace':
4261
						// IE returns -1 as default value
4262
						if (v === -1)
4263
							v = '';
4264
 
4265
						break;
4266
 
4267
					case 'maxlength':
4268
					case 'tabindex':
4269
						// IE returns default value
4270
						if (v === 32768 || v === 2147483647 || v === '32768')
4271
							v = '';
4272
 
4273
						break;
4274
 
4275
					case 'multiple':
4276
					case 'compact':
4277
					case 'noshade':
4278
					case 'nowrap':
4279
						if (v === 65535)
4280
							return n;
4281
 
4282
						return dv;
4283
 
4284
					case 'shape':
4285
						v = v.toLowerCase();
4286
						break;
4287
 
4288
					default:
4289
						// IE has odd anonymous function for event attributes
4290
						if (n.indexOf('on') === 0 && v)
4291
							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
4292
				}
4293
			}
4294
 
4295
			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
4296
		},
4297
 
4298
		getPos : function(n, ro) {
4299
			var t = this, x = 0, y = 0, e, d = t.doc, r;
4300
 
4301
			n = t.get(n);
4302
			ro = ro || d.body;
4303
 
4304
			if (n) {
4305
				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
4306
				if (n.getBoundingClientRect) {
4307
					n = n.getBoundingClientRect();
4308
					e = t.boxModel ? d.documentElement : d.body;
4309
 
4310
					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
4311
					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
4312
					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
4313
					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
4314
 
4315
					return {x : x, y : y};
4316
				}
4317
 
4318
				r = n;
4319
				while (r && r != ro && r.nodeType) {
4320
					x += r.offsetLeft || 0;
4321
					y += r.offsetTop || 0;
4322
					r = r.offsetParent;
4323
				}
4324
 
4325
				r = n.parentNode;
4326
				while (r && r != ro && r.nodeType) {
4327
					x -= r.scrollLeft || 0;
4328
					y -= r.scrollTop || 0;
4329
					r = r.parentNode;
4330
				}
4331
			}
4332
 
4333
			return {x : x, y : y};
4334
		},
4335
 
4336
		parseStyle : function(st) {
4337
			return this.styles.parse(st);
4338
		},
4339
 
4340
		serializeStyle : function(o, name) {
4341
			return this.styles.serialize(o, name);
4342
		},
4343
 
4344
		loadCSS : function(u) {
4345
			var t = this, d = t.doc, head;
4346
 
4347
			if (!u)
4348
				u = '';
4349
 
4350
			head = t.select('head')[0];
4351
 
4352
			each(u.split(','), function(u) {
4353
				var link;
4354
 
4355
				if (t.files[u])
4356
					return;
4357
 
4358
				t.files[u] = true;
4359
				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
4360
 
4361
				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
4362
				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
4363
				// It's ugly but it seems to work fine.
4364
				if (isIE && d.documentMode && d.recalc) {
4365
					link.onload = function() {
4366
						if (d.recalc)
4367
							d.recalc();
4368
 
4369
						link.onload = null;
4370
					};
4371
				}
4372
 
4373
				head.appendChild(link);
4374
			});
4375
		},
4376
 
4377
		addClass : function(e, c) {
4378
			return this.run(e, function(e) {
4379
				var o;
4380
 
4381
				if (!c)
4382
					return 0;
4383
 
4384
				if (this.hasClass(e, c))
4385
					return e.className;
4386
 
4387
				o = this.removeClass(e, c);
4388
 
4389
				return e.className = (o != '' ? (o + ' ') : '') + c;
4390
			});
4391
		},
4392
 
4393
		removeClass : function(e, c) {
4394
			var t = this, re;
4395
 
4396
			return t.run(e, function(e) {
4397
				var v;
4398
 
4399
				if (t.hasClass(e, c)) {
4400
					if (!re)
4401
						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
4402
 
4403
					v = e.className.replace(re, ' ');
4404
					v = tinymce.trim(v != ' ' ? v : '');
4405
 
4406
					e.className = v;
4407
 
4408
					// Empty class attr
4409
					if (!v) {
4410
						e.removeAttribute('class');
4411
						e.removeAttribute('className');
4412
					}
4413
 
4414
					return v;
4415
				}
4416
 
4417
				return e.className;
4418
			});
4419
		},
4420
 
4421
		hasClass : function(n, c) {
4422
			n = this.get(n);
4423
 
4424
			if (!n || !c)
4425
				return false;
4426
 
4427
			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
4428
		},
4429
 
4430
		show : function(e) {
4431
			return this.setStyle(e, 'display', 'block');
4432
		},
4433
 
4434
		hide : function(e) {
4435
			return this.setStyle(e, 'display', 'none');
4436
		},
4437
 
4438
		isHidden : function(e) {
4439
			e = this.get(e);
4440
 
4441
			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
4442
		},
4443
 
4444
		uniqueId : function(p) {
4445
			return (!p ? 'mce_' : p) + (this.counter++);
4446
		},
4447
 
4448
		setHTML : function(element, html) {
4449
			var self = this;
4450
 
4451
			return self.run(element, function(element) {
4452
				if (isIE) {
4453
					// Remove all child nodes, IE keeps empty text nodes in DOM
4454
					while (element.firstChild)
4455
						element.removeChild(element.firstChild);
4456
 
4457
					try {
4458
						// IE will remove comments from the beginning
4459
						// unless you padd the contents with something
4460
						element.innerHTML = '<br />' + html;
4461
						element.removeChild(element.firstChild);
4462
					} catch (ex) {
4463
						// 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
4464
						// This seems to fix this problem
4465
 
4466
						// Create new div with HTML contents and a BR infront to keep comments
4467
						element = self.create('div');
4468
						element.innerHTML = '<br />' + html;
4469
 
4470
						// Add all children from div to target
4471
						each (element.childNodes, function(node, i) {
4472
							// Skip br element
4473
							if (i)
4474
								element.appendChild(node);
4475
						});
4476
					}
4477
				} else
4478
					element.innerHTML = html;
4479
 
4480
				return html;
4481
			});
4482
		},
4483
 
4484
		getOuterHTML : function(elm) {
4485
			var doc, self = this;
4486
 
4487
			elm = self.get(elm);
4488
 
4489
			if (!elm)
4490
				return null;
4491
 
4492
			if (elm.nodeType === 1 && self.hasOuterHTML)
4493
				return elm.outerHTML;
4494
 
4495
			doc = (elm.ownerDocument || self.doc).createElement("body");
4496
			doc.appendChild(elm.cloneNode(true));
4497
 
4498
			return doc.innerHTML;
4499
		},
4500
 
4501
		setOuterHTML : function(e, h, d) {
4502
			var t = this;
4503
 
4504
			function setHTML(e, h, d) {
4505
				var n, tp;
4506
 
4507
				tp = d.createElement("body");
4508
				tp.innerHTML = h;
4509
 
4510
				n = tp.lastChild;
4511
				while (n) {
4512
					t.insertAfter(n.cloneNode(true), e);
4513
					n = n.previousSibling;
4514
				}
4515
 
4516
				t.remove(e);
4517
			};
4518
 
4519
			return this.run(e, function(e) {
4520
				e = t.get(e);
4521
 
4522
				// Only set HTML on elements
4523
				if (e.nodeType == 1) {
4524
					d = d || e.ownerDocument || t.doc;
4525
 
4526
					if (isIE) {
4527
						try {
4528
							// Try outerHTML for IE it sometimes produces an unknown runtime error
4529
							if (isIE && e.nodeType == 1)
4530
								e.outerHTML = h;
4531
							else
4532
								setHTML(e, h, d);
4533
						} catch (ex) {
4534
							// Fix for unknown runtime error
4535
							setHTML(e, h, d);
4536
						}
4537
					} else
4538
						setHTML(e, h, d);
4539
				}
4540
			});
4541
		},
4542
 
4543
		decode : Entities.decode,
4544
 
4545
		encode : Entities.encodeAllRaw,
4546
 
4547
		insertAfter : function(node, reference_node) {
4548
			reference_node = this.get(reference_node);
4549
 
4550
			return this.run(node, function(node) {
4551
				var parent, nextSibling;
4552
 
4553
				parent = reference_node.parentNode;
4554
				nextSibling = reference_node.nextSibling;
4555
 
4556
				if (nextSibling)
4557
					parent.insertBefore(node, nextSibling);
4558
				else
4559
					parent.appendChild(node);
4560
 
4561
				return node;
4562
			});
4563
		},
4564
 
4565
		isBlock : function(node) {
4566
			var type = node.nodeType;
4567
 
4568
			// If it's a node then check the type and use the nodeName
4569
			if (type)
4570
				return !!(type === 1 && blockElementsMap[node.nodeName]);
4571
 
4572
			return !!blockElementsMap[node];
4573
		},
4574
 
4575
		replace : function(n, o, k) {
4576
			var t = this;
4577
 
4578
			if (is(o, 'array'))
4579
				n = n.cloneNode(true);
4580
 
4581
			return t.run(o, function(o) {
4582
				if (k) {
4583
					each(tinymce.grep(o.childNodes), function(c) {
4584
						n.appendChild(c);
4585
					});
4586
				}
4587
 
4588
				return o.parentNode.replaceChild(n, o);
4589
			});
4590
		},
4591
 
4592
		rename : function(elm, name) {
4593
			var t = this, newElm;
4594
 
4595
			if (elm.nodeName != name.toUpperCase()) {
4596
				// Rename block element
4597
				newElm = t.create(name);
4598
 
4599
				// Copy attribs to new block
4600
				each(t.getAttribs(elm), function(attr_node) {
4601
					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
4602
				});
4603
 
4604
				// Replace block
4605
				t.replace(newElm, elm, 1);
4606
			}
4607
 
4608
			return newElm || elm;
4609
		},
4610
 
4611
		findCommonAncestor : function(a, b) {
4612
			var ps = a, pe;
4613
 
4614
			while (ps) {
4615
				pe = b;
4616
 
4617
				while (pe && ps != pe)
4618
					pe = pe.parentNode;
4619
 
4620
				if (ps == pe)
4621
					break;
4622
 
4623
				ps = ps.parentNode;
4624
			}
4625
 
4626
			if (!ps && a.ownerDocument)
4627
				return a.ownerDocument.documentElement;
4628
 
4629
			return ps;
4630
		},
4631
 
4632
		toHex : function(s) {
4633
			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
4634
 
4635
			function hex(s) {
4636
				s = parseInt(s).toString(16);
4637
 
4638
				return s.length > 1 ? s : '0' + s; // 0 -> 00
4639
			};
4640
 
4641
			if (c) {
4642
				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
4643
 
4644
				return s;
4645
			}
4646
 
4647
			return s;
4648
		},
4649
 
4650
		getClasses : function() {
4651
			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
4652
 
4653
			if (t.classes)
4654
				return t.classes;
4655
 
4656
			function addClasses(s) {
4657
				// IE style imports
4658
				each(s.imports, function(r) {
4659
					addClasses(r);
4660
				});
4661
 
4662
				each(s.cssRules || s.rules, function(r) {
4663
					// Real type or fake it on IE
4664
					switch (r.type || 1) {
4665
						// Rule
4666
						case 1:
4667
							if (r.selectorText) {
4668
								each(r.selectorText.split(','), function(v) {
4669
									v = v.replace(/^\s*|\s*$|^\s\./g, "");
4670
 
4671
									// Is internal or it doesn't contain a class
4672
									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
4673
										return;
4674
 
4675
									// Remove everything but class name
4676
									ov = v;
4677
									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
4678
 
4679
									// Filter classes
4680
									if (f && !(v = f(v, ov)))
4681
										return;
4682
 
4683
									if (!lo[v]) {
4684
										cl.push({'class' : v});
4685
										lo[v] = 1;
4686
									}
4687
								});
4688
							}
4689
							break;
4690
 
4691
						// Import
4692
						case 3:
4693
							addClasses(r.styleSheet);
4694
							break;
4695
					}
4696
				});
4697
			};
4698
 
4699
			try {
4700
				each(t.doc.styleSheets, addClasses);
4701
			} catch (ex) {
4702
				// Ignore
4703
			}
4704
 
4705
			if (cl.length > 0)
4706
				t.classes = cl;
4707
 
4708
			return cl;
4709
		},
4710
 
4711
		run : function(e, f, s) {
4712
			var t = this, o;
4713
 
4714
			if (t.doc && typeof(e) === 'string')
4715
				e = t.get(e);
4716
 
4717
			if (!e)
4718
				return false;
4719
 
4720
			s = s || this;
4721
			if (!e.nodeType && (e.length || e.length === 0)) {
4722
				o = [];
4723
 
4724
				each(e, function(e, i) {
4725
					if (e) {
4726
						if (typeof(e) == 'string')
4727
							e = t.doc.getElementById(e);
4728
 
4729
						o.push(f.call(s, e, i));
4730
					}
4731
				});
4732
 
4733
				return o;
4734
			}
4735
 
4736
			return f.call(s, e);
4737
		},
4738
 
4739
		getAttribs : function(n) {
4740
			var o;
4741
 
4742
			n = this.get(n);
4743
 
4744
			if (!n)
4745
				return [];
4746
 
4747
			if (isIE) {
4748
				o = [];
4749
 
4750
				// Object will throw exception in IE
4751
				if (n.nodeName == 'OBJECT')
4752
					return n.attributes;
4753
 
4754
				// IE doesn't keep the selected attribute if you clone option elements
4755
				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
4756
					o.push({specified : 1, nodeName : 'selected'});
4757
 
4758
				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
4759
				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
4760
					o.push({specified : 1, nodeName : a});
4761
				});
4762
 
4763
				return o;
4764
			}
4765
 
4766
			return n.attributes;
4767
		},
4768
 
4769
		isEmpty : function(node, elements) {
4770
			var self = this, i, attributes, type, walker, name, parentNode;
4771
 
4772
			node = node.firstChild;
4773
			if (node) {
4774
				walker = new tinymce.dom.TreeWalker(node);
4775
				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
4776
 
4777
				do {
4778
					type = node.nodeType;
4779
 
4780
					if (type === 1) {
4781
						// Ignore bogus elements
4782
						if (node.getAttribute('data-mce-bogus'))
4783
							continue;
4784
 
4785
						// Keep empty elements like <img />
4786
						name = node.nodeName.toLowerCase();
4787
						if (elements && elements[name]) {
4788
							// Ignore single BR elements in blocks like <p><br /></p>
4789
							parentNode = node.parentNode;
4790
							if (name === 'br' && self.isBlock(parentNode) && parentNode.firstChild === node && parentNode.lastChild === node) {
4791
								continue;
4792
							}
4793
 
4794
							return false;
4795
						}
4796
 
4797
						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
4798
						attributes = self.getAttribs(node);
4799
						i = node.attributes.length;
4800
						while (i--) {
4801
							name = node.attributes[i].nodeName;
4802
							if (name === "name" || name === 'data-mce-bookmark')
4803
								return false;
4804
						}
4805
					}
4806
 
4807
					// Keep non whitespace text nodes
4808
					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
4809
						return false;
4810
				} while (node = walker.next());
4811
			}
4812
 
4813
			return true;
4814
		},
4815
 
4816
		destroy : function(s) {
4817
			var t = this;
4818
 
4819
			if (t.events)
4820
				t.events.destroy();
4821
 
4822
			t.win = t.doc = t.root = t.events = null;
4823
 
4824
			// Manual destroy then remove unload handler
4825
			if (!s)
4826
				tinymce.removeUnload(t.destroy);
4827
		},
4828
 
4829
		createRng : function() {
4830
			var d = this.doc;
4831
 
4832
			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
4833
		},
4834
 
4835
		nodeIndex : function(node, normalized) {
4836
			var idx = 0, lastNodeType, lastNode, nodeType;
4837
 
4838
			if (node) {
4839
				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
4840
					nodeType = node.nodeType;
4841
 
4842
					// Normalize text nodes
4843
					if (normalized && nodeType == 3) {
4844
						if (nodeType == lastNodeType || !node.nodeValue.length)
4845
							continue;
4846
					}
4847
					idx++;
4848
					lastNodeType = nodeType;
4849
				}
4850
			}
4851
 
4852
			return idx;
4853
		},
4854
 
4855
		split : function(pe, e, re) {
4856
			var t = this, r = t.createRng(), bef, aft, pa;
4857
 
4858
			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
4859
			// but we don't want that in our code since it serves no purpose for the end user
4860
			// For example if this is chopped:
4861
			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
4862
			// would produce:
4863
			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
4864
			// this function will then trim of empty edges and produce:
4865
			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
4866
			function trim(node) {
4867
				var i, children = node.childNodes, type = node.nodeType;
4868
 
4869
				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
4870
					return;
4871
 
4872
				for (i = children.length - 1; i >= 0; i--)
4873
					trim(children[i]);
4874
 
4875
				if (type != 9) {
4876
					// Keep non whitespace text nodes
4877
					if (type == 3 && node.nodeValue.length > 0) {
4878
						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
4879
						if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
4880
							return;
4881
					} else if (type == 1) {
4882
						// If the only child is a bookmark then move it up
4883
						children = node.childNodes;
4884
						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
4885
							node.parentNode.insertBefore(children[0], node);
4886
 
4887
						// Keep non empty elements or img, hr etc
4888
						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
4889
							return;
4890
					}
4891
 
4892
					t.remove(node);
4893
				}
4894
 
4895
				return node;
4896
			};
4897
 
4898
			if (pe && e) {
4899
				// Get before chunk
4900
				r.setStart(pe.parentNode, t.nodeIndex(pe));
4901
				r.setEnd(e.parentNode, t.nodeIndex(e));
4902
				bef = r.extractContents();
4903
 
4904
				// Get after chunk
4905
				r = t.createRng();
4906
				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
4907
				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
4908
				aft = r.extractContents();
4909
 
4910
				// Insert before chunk
4911
				pa = pe.parentNode;
4912
				pa.insertBefore(trim(bef), pe);
4913
 
4914
				// Insert middle chunk
4915
				if (re)
4916
					pa.replaceChild(re, e);
4917
				else
4918
					pa.insertBefore(e, pe);
4919
 
4920
				// Insert after chunk
4921
				pa.insertBefore(trim(aft), pe);
4922
				t.remove(pe);
4923
 
4924
				return re || e;
4925
			}
4926
		},
4927
 
4928
		bind : function(target, name, func, scope) {
4929
			var t = this;
4930
 
4931
			if (!t.events)
4932
				t.events = new tinymce.dom.EventUtils();
4933
 
4934
			return t.events.add(target, name, func, scope || this);
4935
		},
4936
 
4937
		unbind : function(target, name, func) {
4938
			var t = this;
4939
 
4940
			if (!t.events)
4941
				t.events = new tinymce.dom.EventUtils();
4942
 
4943
			return t.events.remove(target, name, func);
4944
		},
4945
 
4946
 
4947
		_findSib : function(node, selector, name) {
4948
			var t = this, f = selector;
4949
 
4950
			if (node) {
4951
				// If expression make a function of it using is
4952
				if (is(f, 'string')) {
4953
					f = function(node) {
4954
						return t.is(node, selector);
4955
					};
4956
				}
4957
 
4958
				// Loop all siblings
4959
				for (node = node[name]; node; node = node[name]) {
4960
					if (f(node))
4961
						return node;
4962
				}
4963
			}
4964
 
4965
			return null;
4966
		},
4967
 
4968
		_isRes : function(c) {
4969
			// Is live resizble element
4970
			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
4971
		}
4972
 
4973
		/*
4974
		walk : function(n, f, s) {
4975
			var d = this.doc, w;
4976
 
4977
			if (d.createTreeWalker) {
4978
				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
4979
 
4980
				while ((n = w.nextNode()) != null)
4981
					f.call(s || this, n);
4982
			} else
4983
				tinymce.walk(n, f, 'childNodes', s);
4984
		}
4985
		*/
4986
 
4987
		/*
4988
		toRGB : function(s) {
4989
			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
4990
 
4991
			if (c) {
4992
				// #FFF -> #FFFFFF
4993
				if (!is(c[3]))
4994
					c[3] = c[2] = c[1];
4995
 
4996
				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
4997
			}
4998
 
4999
			return s;
5000
		}
5001
		*/
5002
	});
5003
 
5004
	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
5005
})(tinymce);
5006
 
5007
(function(ns) {
5008
	// Range constructor
5009
	function Range(dom) {
5010
		var t = this,
5011
			doc = dom.doc,
5012
			EXTRACT = 0,
5013
			CLONE = 1,
5014
			DELETE = 2,
5015
			TRUE = true,
5016
			FALSE = false,
5017
			START_OFFSET = 'startOffset',
5018
			START_CONTAINER = 'startContainer',
5019
			END_CONTAINER = 'endContainer',
5020
			END_OFFSET = 'endOffset',
5021
			extend = tinymce.extend,
5022
			nodeIndex = dom.nodeIndex;
5023
 
5024
		extend(t, {
5025
			// Inital states
5026
			startContainer : doc,
5027
			startOffset : 0,
5028
			endContainer : doc,
5029
			endOffset : 0,
5030
			collapsed : TRUE,
5031
			commonAncestorContainer : doc,
5032
 
5033
			// Range constants
5034
			START_TO_START : 0,
5035
			START_TO_END : 1,
5036
			END_TO_END : 2,
5037
			END_TO_START : 3,
5038
 
5039
			// Public methods
5040
			setStart : setStart,
5041
			setEnd : setEnd,
5042
			setStartBefore : setStartBefore,
5043
			setStartAfter : setStartAfter,
5044
			setEndBefore : setEndBefore,
5045
			setEndAfter : setEndAfter,
5046
			collapse : collapse,
5047
			selectNode : selectNode,
5048
			selectNodeContents : selectNodeContents,
5049
			compareBoundaryPoints : compareBoundaryPoints,
5050
			deleteContents : deleteContents,
5051
			extractContents : extractContents,
5052
			cloneContents : cloneContents,
5053
			insertNode : insertNode,
5054
			surroundContents : surroundContents,
5055
			cloneRange : cloneRange
5056
		});
5057
 
5058
		function setStart(n, o) {
5059
			_setEndPoint(TRUE, n, o);
5060
		};
5061
 
5062
		function setEnd(n, o) {
5063
			_setEndPoint(FALSE, n, o);
5064
		};
5065
 
5066
		function setStartBefore(n) {
5067
			setStart(n.parentNode, nodeIndex(n));
5068
		};
5069
 
5070
		function setStartAfter(n) {
5071
			setStart(n.parentNode, nodeIndex(n) + 1);
5072
		};
5073
 
5074
		function setEndBefore(n) {
5075
			setEnd(n.parentNode, nodeIndex(n));
5076
		};
5077
 
5078
		function setEndAfter(n) {
5079
			setEnd(n.parentNode, nodeIndex(n) + 1);
5080
		};
5081
 
5082
		function collapse(ts) {
5083
			if (ts) {
5084
				t[END_CONTAINER] = t[START_CONTAINER];
5085
				t[END_OFFSET] = t[START_OFFSET];
5086
			} else {
5087
				t[START_CONTAINER] = t[END_CONTAINER];
5088
				t[START_OFFSET] = t[END_OFFSET];
5089
			}
5090
 
5091
			t.collapsed = TRUE;
5092
		};
5093
 
5094
		function selectNode(n) {
5095
			setStartBefore(n);
5096
			setEndAfter(n);
5097
		};
5098
 
5099
		function selectNodeContents(n) {
5100
			setStart(n, 0);
5101
			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
5102
		};
5103
 
5104
		function compareBoundaryPoints(h, r) {
5105
			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
5106
			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
5107
 
5108
			// Check START_TO_START
5109
			if (h === 0)
5110
				return _compareBoundaryPoints(sc, so, rsc, rso);
5111
 
5112
			// Check START_TO_END
5113
			if (h === 1)
5114
				return _compareBoundaryPoints(ec, eo, rsc, rso);
5115
 
5116
			// Check END_TO_END
5117
			if (h === 2)
5118
				return _compareBoundaryPoints(ec, eo, rec, reo);
5119
 
5120
			// Check END_TO_START
5121
			if (h === 3)
5122
				return _compareBoundaryPoints(sc, so, rec, reo);
5123
		};
5124
 
5125
		function deleteContents() {
5126
			_traverse(DELETE);
5127
		};
5128
 
5129
		function extractContents() {
5130
			return _traverse(EXTRACT);
5131
		};
5132
 
5133
		function cloneContents() {
5134
			return _traverse(CLONE);
5135
		};
5136
 
5137
		function insertNode(n) {
5138
			var startContainer = this[START_CONTAINER],
5139
				startOffset = this[START_OFFSET], nn, o;
5140
 
5141
			// Node is TEXT_NODE or CDATA
5142
			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
5143
				if (!startOffset) {
5144
					// At the start of text
5145
					startContainer.parentNode.insertBefore(n, startContainer);
5146
				} else if (startOffset >= startContainer.nodeValue.length) {
5147
					// At the end of text
5148
					dom.insertAfter(n, startContainer);
5149
				} else {
5150
					// Middle, need to split
5151
					nn = startContainer.splitText(startOffset);
5152
					startContainer.parentNode.insertBefore(n, nn);
5153
				}
5154
			} else {
5155
				// Insert element node
5156
				if (startContainer.childNodes.length > 0)
5157
					o = startContainer.childNodes[startOffset];
5158
 
5159
				if (o)
5160
					startContainer.insertBefore(n, o);
5161
				else
5162
					startContainer.appendChild(n);
5163
			}
5164
		};
5165
 
5166
		function surroundContents(n) {
5167
			var f = t.extractContents();
5168
 
5169
			t.insertNode(n);
5170
			n.appendChild(f);
5171
			t.selectNode(n);
5172
		};
5173
 
5174
		function cloneRange() {
5175
			return extend(new Range(dom), {
5176
				startContainer : t[START_CONTAINER],
5177
				startOffset : t[START_OFFSET],
5178
				endContainer : t[END_CONTAINER],
5179
				endOffset : t[END_OFFSET],
5180
				collapsed : t.collapsed,
5181
				commonAncestorContainer : t.commonAncestorContainer
5182
			});
5183
		};
5184
 
5185
		// Private methods
5186
 
5187
		function _getSelectedNode(container, offset) {
5188
			var child;
5189
 
5190
			if (container.nodeType == 3 /* TEXT_NODE */)
5191
				return container;
5192
 
5193
			if (offset < 0)
5194
				return container;
5195
 
5196
			child = container.firstChild;
5197
			while (child && offset > 0) {
5198
				--offset;
5199
				child = child.nextSibling;
5200
			}
5201
 
5202
			if (child)
5203
				return child;
5204
 
5205
			return container;
5206
		};
5207
 
5208
		function _isCollapsed() {
5209
			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
5210
		};
5211
 
5212
		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
5213
			var c, offsetC, n, cmnRoot, childA, childB;
5214
 
5215
			// In the first case the boundary-points have the same container. A is before B
5216
			// if its offset is less than the offset of B, A is equal to B if its offset is
5217
			// equal to the offset of B, and A is after B if its offset is greater than the
5218
			// offset of B.
5219
			if (containerA == containerB) {
5220
				if (offsetA == offsetB)
5221
					return 0; // equal
5222
 
5223
				if (offsetA < offsetB)
5224
					return -1; // before
5225
 
5226
				return 1; // after
5227
			}
5228
 
5229
			// In the second case a child node C of the container of A is an ancestor
5230
			// container of B. In this case, A is before B if the offset of A is less than or
5231
			// equal to the index of the child node C and A is after B otherwise.
5232
			c = containerB;
5233
			while (c && c.parentNode != containerA)
5234
				c = c.parentNode;
5235
 
5236
			if (c) {
5237
				offsetC = 0;
5238
				n = containerA.firstChild;
5239
 
5240
				while (n != c && offsetC < offsetA) {
5241
					offsetC++;
5242
					n = n.nextSibling;
5243
				}
5244
 
5245
				if (offsetA <= offsetC)
5246
					return -1; // before
5247
 
5248
				return 1; // after
5249
			}
5250
 
5251
			// In the third case a child node C of the container of B is an ancestor container
5252
			// of A. In this case, A is before B if the index of the child node C is less than
5253
			// the offset of B and A is after B otherwise.
5254
			c = containerA;
5255
			while (c && c.parentNode != containerB) {
5256
				c = c.parentNode;
5257
			}
5258
 
5259
			if (c) {
5260
				offsetC = 0;
5261
				n = containerB.firstChild;
5262
 
5263
				while (n != c && offsetC < offsetB) {
5264
					offsetC++;
5265
					n = n.nextSibling;
5266
				}
5267
 
5268
				if (offsetC < offsetB)
5269
					return -1; // before
5270
 
5271
				return 1; // after
5272
			}
5273
 
5274
			// In the fourth case, none of three other cases hold: the containers of A and B
5275
			// are siblings or descendants of sibling nodes. In this case, A is before B if
5276
			// the container of A is before the container of B in a pre-order traversal of the
5277
			// Ranges' context tree and A is after B otherwise.
5278
			cmnRoot = dom.findCommonAncestor(containerA, containerB);
5279
			childA = containerA;
5280
 
5281
			while (childA && childA.parentNode != cmnRoot)
5282
				childA = childA.parentNode;
5283
 
5284
			if (!childA)
5285
				childA = cmnRoot;
5286
 
5287
			childB = containerB;
5288
			while (childB && childB.parentNode != cmnRoot)
5289
				childB = childB.parentNode;
5290
 
5291
			if (!childB)
5292
				childB = cmnRoot;
5293
 
5294
			if (childA == childB)
5295
				return 0; // equal
5296
 
5297
			n = cmnRoot.firstChild;
5298
			while (n) {
5299
				if (n == childA)
5300
					return -1; // before
5301
 
5302
				if (n == childB)
5303
					return 1; // after
5304
 
5305
				n = n.nextSibling;
5306
			}
5307
		};
5308
 
5309
		function _setEndPoint(st, n, o) {
5310
			var ec, sc;
5311
 
5312
			if (st) {
5313
				t[START_CONTAINER] = n;
5314
				t[START_OFFSET] = o;
5315
			} else {
5316
				t[END_CONTAINER] = n;
5317
				t[END_OFFSET] = o;
5318
			}
5319
 
5320
			// If one boundary-point of a Range is set to have a root container
5321
			// other than the current one for the Range, the Range is collapsed to
5322
			// the new position. This enforces the restriction that both boundary-
5323
			// points of a Range must have the same root container.
5324
			ec = t[END_CONTAINER];
5325
			while (ec.parentNode)
5326
				ec = ec.parentNode;
5327
 
5328
			sc = t[START_CONTAINER];
5329
			while (sc.parentNode)
5330
				sc = sc.parentNode;
5331
 
5332
			if (sc == ec) {
5333
				// The start position of a Range is guaranteed to never be after the
5334
				// end position. To enforce this restriction, if the start is set to
5335
				// be at a position after the end, the Range is collapsed to that
5336
				// position.
5337
				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
5338
					t.collapse(st);
5339
			} else
5340
				t.collapse(st);
5341
 
5342
			t.collapsed = _isCollapsed();
5343
			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
5344
		};
5345
 
5346
		function _traverse(how) {
5347
			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
5348
 
5349
			if (t[START_CONTAINER] == t[END_CONTAINER])
5350
				return _traverseSameContainer(how);
5351
 
5352
			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5353
				if (p == t[START_CONTAINER])
5354
					return _traverseCommonStartContainer(c, how);
5355
 
5356
				++endContainerDepth;
5357
			}
5358
 
5359
			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
5360
				if (p == t[END_CONTAINER])
5361
					return _traverseCommonEndContainer(c, how);
5362
 
5363
				++startContainerDepth;
5364
			}
5365
 
5366
			depthDiff = startContainerDepth - endContainerDepth;
5367
 
5368
			startNode = t[START_CONTAINER];
5369
			while (depthDiff > 0) {
5370
				startNode = startNode.parentNode;
5371
				depthDiff--;
5372
			}
5373
 
5374
			endNode = t[END_CONTAINER];
5375
			while (depthDiff < 0) {
5376
				endNode = endNode.parentNode;
5377
				depthDiff++;
5378
			}
5379
 
5380
			// ascend the ancestor hierarchy until we have a common parent.
5381
			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
5382
				startNode = sp;
5383
				endNode = ep;
5384
			}
5385
 
5386
			return _traverseCommonAncestors(startNode, endNode, how);
5387
		};
5388
 
5389
		 function _traverseSameContainer(how) {
5390
			var frag, s, sub, n, cnt, sibling, xferNode;
5391
 
5392
			if (how != DELETE)
5393
				frag = doc.createDocumentFragment();
5394
 
5395
			// If selection is empty, just return the fragment
5396
			if (t[START_OFFSET] == t[END_OFFSET])
5397
				return frag;
5398
 
5399
			// Text node needs special case handling
5400
			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
5401
				// get the substring
5402
				s = t[START_CONTAINER].nodeValue;
5403
				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
5404
 
5405
				// set the original text node to its new value
5406
				if (how != CLONE) {
5407
					t[START_CONTAINER].deleteData(t[START_OFFSET], t[END_OFFSET] - t[START_OFFSET]);
5408
 
5409
					// Nothing is partially selected, so collapse to start point
5410
					t.collapse(TRUE);
5411
				}
5412
 
5413
				if (how == DELETE)
5414
					return;
5415
 
5416
				frag.appendChild(doc.createTextNode(sub));
5417
				return frag;
5418
			}
5419
 
5420
			// Copy nodes between the start/end offsets.
5421
			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
5422
			cnt = t[END_OFFSET] - t[START_OFFSET];
5423
 
5424
			while (cnt > 0) {
5425
				sibling = n.nextSibling;
5426
				xferNode = _traverseFullySelected(n, how);
5427
 
5428
				if (frag)
5429
					frag.appendChild( xferNode );
5430
 
5431
				--cnt;
5432
				n = sibling;
5433
			}
5434
 
5435
			// Nothing is partially selected, so collapse to start point
5436
			if (how != CLONE)
5437
				t.collapse(TRUE);
5438
 
5439
			return frag;
5440
		};
5441
 
5442
		function _traverseCommonStartContainer(endAncestor, how) {
5443
			var frag, n, endIdx, cnt, sibling, xferNode;
5444
 
5445
			if (how != DELETE)
5446
				frag = doc.createDocumentFragment();
5447
 
5448
			n = _traverseRightBoundary(endAncestor, how);
5449
 
5450
			if (frag)
5451
				frag.appendChild(n);
5452
 
5453
			endIdx = nodeIndex(endAncestor);
5454
			cnt = endIdx - t[START_OFFSET];
5455
 
5456
			if (cnt <= 0) {
5457
				// Collapse to just before the endAncestor, which
5458
				// is partially selected.
5459
				if (how != CLONE) {
5460
					t.setEndBefore(endAncestor);
5461
					t.collapse(FALSE);
5462
				}
5463
 
5464
				return frag;
5465
			}
5466
 
5467
			n = endAncestor.previousSibling;
5468
			while (cnt > 0) {
5469
				sibling = n.previousSibling;
5470
				xferNode = _traverseFullySelected(n, how);
5471
 
5472
				if (frag)
5473
					frag.insertBefore(xferNode, frag.firstChild);
5474
 
5475
				--cnt;
5476
				n = sibling;
5477
			}
5478
 
5479
			// Collapse to just before the endAncestor, which
5480
			// is partially selected.
5481
			if (how != CLONE) {
5482
				t.setEndBefore(endAncestor);
5483
				t.collapse(FALSE);
5484
			}
5485
 
5486
			return frag;
5487
		};
5488
 
5489
		function _traverseCommonEndContainer(startAncestor, how) {
5490
			var frag, startIdx, n, cnt, sibling, xferNode;
5491
 
5492
			if (how != DELETE)
5493
				frag = doc.createDocumentFragment();
5494
 
5495
			n = _traverseLeftBoundary(startAncestor, how);
5496
			if (frag)
5497
				frag.appendChild(n);
5498
 
5499
			startIdx = nodeIndex(startAncestor);
5500
			++startIdx; // Because we already traversed it
5501
 
5502
			cnt = t[END_OFFSET] - startIdx;
5503
			n = startAncestor.nextSibling;
5504
			while (cnt > 0) {
5505
				sibling = n.nextSibling;
5506
				xferNode = _traverseFullySelected(n, how);
5507
 
5508
				if (frag)
5509
					frag.appendChild(xferNode);
5510
 
5511
				--cnt;
5512
				n = sibling;
5513
			}
5514
 
5515
			if (how != CLONE) {
5516
				t.setStartAfter(startAncestor);
5517
				t.collapse(TRUE);
5518
			}
5519
 
5520
			return frag;
5521
		};
5522
 
5523
		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
5524
			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
5525
 
5526
			if (how != DELETE)
5527
				frag = doc.createDocumentFragment();
5528
 
5529
			n = _traverseLeftBoundary(startAncestor, how);
5530
			if (frag)
5531
				frag.appendChild(n);
5532
 
5533
			commonParent = startAncestor.parentNode;
5534
			startOffset = nodeIndex(startAncestor);
5535
			endOffset = nodeIndex(endAncestor);
5536
			++startOffset;
5537
 
5538
			cnt = endOffset - startOffset;
5539
			sibling = startAncestor.nextSibling;
5540
 
5541
			while (cnt > 0) {
5542
				nextSibling = sibling.nextSibling;
5543
				n = _traverseFullySelected(sibling, how);
5544
 
5545
				if (frag)
5546
					frag.appendChild(n);
5547
 
5548
				sibling = nextSibling;
5549
				--cnt;
5550
			}
5551
 
5552
			n = _traverseRightBoundary(endAncestor, how);
5553
 
5554
			if (frag)
5555
				frag.appendChild(n);
5556
 
5557
			if (how != CLONE) {
5558
				t.setStartAfter(startAncestor);
5559
				t.collapse(TRUE);
5560
			}
5561
 
5562
			return frag;
5563
		};
5564
 
5565
		function _traverseRightBoundary(root, how) {
5566
			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
5567
 
5568
			if (next == root)
5569
				return _traverseNode(next, isFullySelected, FALSE, how);
5570
 
5571
			parent = next.parentNode;
5572
			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
5573
 
5574
			while (parent) {
5575
				while (next) {
5576
					prevSibling = next.previousSibling;
5577
					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
5578
 
5579
					if (how != DELETE)
5580
						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
5581
 
5582
					isFullySelected = TRUE;
5583
					next = prevSibling;
5584
				}
5585
 
5586
				if (parent == root)
5587
					return clonedParent;
5588
 
5589
				next = parent.previousSibling;
5590
				parent = parent.parentNode;
5591
 
5592
				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
5593
 
5594
				if (how != DELETE)
5595
					clonedGrandParent.appendChild(clonedParent);
5596
 
5597
				clonedParent = clonedGrandParent;
5598
			}
5599
		};
5600
 
5601
		function _traverseLeftBoundary(root, how) {
5602
			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
5603
 
5604
			if (next == root)
5605
				return _traverseNode(next, isFullySelected, TRUE, how);
5606
 
5607
			parent = next.parentNode;
5608
			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
5609
 
5610
			while (parent) {
5611
				while (next) {
5612
					nextSibling = next.nextSibling;
5613
					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
5614
 
5615
					if (how != DELETE)
5616
						clonedParent.appendChild(clonedChild);
5617
 
5618
					isFullySelected = TRUE;
5619
					next = nextSibling;
5620
				}
5621
 
5622
				if (parent == root)
5623
					return clonedParent;
5624
 
5625
				next = parent.nextSibling;
5626
				parent = parent.parentNode;
5627
 
5628
				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
5629
 
5630
				if (how != DELETE)
5631
					clonedGrandParent.appendChild(clonedParent);
5632
 
5633
				clonedParent = clonedGrandParent;
5634
			}
5635
		};
5636
 
5637
		function _traverseNode(n, isFullySelected, isLeft, how) {
5638
			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
5639
 
5640
			if (isFullySelected)
5641
				return _traverseFullySelected(n, how);
5642
 
5643
			if (n.nodeType == 3 /* TEXT_NODE */) {
5644
				txtValue = n.nodeValue;
5645
 
5646
				if (isLeft) {
5647
					offset = t[START_OFFSET];
5648
					newNodeValue = txtValue.substring(offset);
5649
					oldNodeValue = txtValue.substring(0, offset);
5650
				} else {
5651
					offset = t[END_OFFSET];
5652
					newNodeValue = txtValue.substring(0, offset);
5653
					oldNodeValue = txtValue.substring(offset);
5654
				}
5655
 
5656
				if (how != CLONE)
5657
					n.nodeValue = oldNodeValue;
5658
 
5659
				if (how == DELETE)
5660
					return;
5661
 
5662
				newNode = n.cloneNode(FALSE);
5663
				newNode.nodeValue = newNodeValue;
5664
 
5665
				return newNode;
5666
			}
5667
 
5668
			if (how == DELETE)
5669
				return;
5670
 
5671
			return n.cloneNode(FALSE);
5672
		};
5673
 
5674
		function _traverseFullySelected(n, how) {
5675
			if (how != DELETE)
5676
				return how == CLONE ? n.cloneNode(TRUE) : n;
5677
 
5678
			n.parentNode.removeChild(n);
5679
		};
5680
	};
5681
 
5682
	ns.Range = Range;
5683
})(tinymce.dom);
5684
 
5685
(function() {
5686
	function Selection(selection) {
5687
		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
5688
 
5689
		function getPosition(rng, start) {
5690
			var checkRng, startIndex = 0, endIndex, inside,
5691
				children, child, offset, index, position = -1, parent;
5692
 
5693
			// Setup test range, collapse it and get the parent
5694
			checkRng = rng.duplicate();
5695
			checkRng.collapse(start);
5696
			parent = checkRng.parentElement();
5697
 
5698
			// Check if the selection is within the right document
5699
			if (parent.ownerDocument !== selection.dom.doc)
5700
				return;
5701
 
5702
			// IE will report non editable elements as it's parent so look for an editable one
5703
			while (parent.contentEditable === "false") {
5704
				parent = parent.parentNode;
5705
			}
5706
 
5707
			// If parent doesn't have any children then return that we are inside the element
5708
			if (!parent.hasChildNodes()) {
5709
				return {node : parent, inside : 1};
5710
			}
5711
 
5712
			// Setup node list and endIndex
5713
			children = parent.children;
5714
			endIndex = children.length - 1;
5715
 
5716
			// Perform a binary search for the position
5717
			while (startIndex <= endIndex) {
5718
				index = Math.floor((startIndex + endIndex) / 2);
5719
 
5720
				// Move selection to node and compare the ranges
5721
				child = children[index];
5722
				checkRng.moveToElementText(child);
5723
				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
5724
 
5725
				// Before/after or an exact match
5726
				if (position > 0) {
5727
					endIndex = index - 1;
5728
				} else if (position < 0) {
5729
					startIndex = index + 1;
5730
				} else {
5731
					return {node : child};
5732
				}
5733
			}
5734
 
5735
			// Check if child position is before or we didn't find a position
5736
			if (position < 0) {
5737
				// No element child was found use the parent element and the offset inside that
5738
				if (!child) {
5739
					checkRng.moveToElementText(parent);
5740
					checkRng.collapse(true);
5741
					child = parent;
5742
					inside = true;
5743
				} else
5744
					checkRng.collapse(false);
5745
 
5746
				checkRng.setEndPoint(start ? 'EndToStart' : 'EndToEnd', rng);
5747
 
5748
				// Fix for edge case: <div style="width: 100px; height:100px;"><table>..</table>ab|c</div>
5749
				if (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) > 0) {
5750
					checkRng = rng.duplicate();
5751
					checkRng.collapse(start);
5752
 
5753
					offset = -1;
5754
					while (parent == checkRng.parentElement()) {
5755
						if (checkRng.move('character', -1) == 0)
5756
							break;
5757
 
5758
						offset++;
5759
					}
5760
				}
5761
 
5762
				offset = offset || checkRng.text.replace('\r\n', ' ').length;
5763
			} else {
5764
				// Child position is after the selection endpoint
5765
				checkRng.collapse(true);
5766
				checkRng.setEndPoint(start ? 'StartToStart' : 'StartToEnd', rng);
5767
 
5768
				// Get the length of the text to find where the endpoint is relative to it's container
5769
				offset = checkRng.text.replace('\r\n', ' ').length;
5770
			}
5771
 
5772
			return {node : child, position : position, offset : offset, inside : inside};
5773
		};
5774
 
5775
		// Returns a W3C DOM compatible range object by using the IE Range API
5776
		function getRange() {
5777
			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
5778
 
5779
			// If selection is outside the current document just return an empty range
5780
			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
5781
			if (element.ownerDocument != dom.doc)
5782
				return domRange;
5783
 
5784
			collapsed = selection.isCollapsed();
5785
 
5786
			// Handle control selection
5787
			if (ieRange.item) {
5788
				domRange.setStart(element.parentNode, dom.nodeIndex(element));
5789
				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
5790
 
5791
				return domRange;
5792
			}
5793
 
5794
			function findEndPoint(start) {
5795
				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
5796
 
5797
				container = endPoint.node;
5798
				offset = endPoint.offset;
5799
 
5800
				if (endPoint.inside && !container.hasChildNodes()) {
5801
					domRange[start ? 'setStart' : 'setEnd'](container, 0);
5802
					return;
5803
				}
5804
 
5805
				if (offset === undef) {
5806
					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
5807
					return;
5808
				}
5809
 
5810
				if (endPoint.position < 0) {
5811
					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
5812
 
5813
					if (!sibling) {
5814
						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
5815
						return;
5816
					}
5817
 
5818
					if (!offset) {
5819
						if (sibling.nodeType == 3)
5820
							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
5821
						else
5822
							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
5823
 
5824
						return;
5825
					}
5826
 
5827
					// Find the text node and offset
5828
					while (sibling) {
5829
						nodeValue = sibling.nodeValue;
5830
						textNodeOffset += nodeValue.length;
5831
 
5832
						// We are at or passed the position we where looking for
5833
						if (textNodeOffset >= offset) {
5834
							container = sibling;
5835
							textNodeOffset -= offset;
5836
							textNodeOffset = nodeValue.length - textNodeOffset;
5837
							break;
5838
						}
5839
 
5840
						sibling = sibling.nextSibling;
5841
					}
5842
				} else {
5843
					// Find the text node and offset
5844
					sibling = container.previousSibling;
5845
 
5846
					if (!sibling)
5847
						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
5848
 
5849
					// If there isn't any text to loop then use the first position
5850
					if (!offset) {
5851
						if (container.nodeType == 3)
5852
							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
5853
						else
5854
							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
5855
 
5856
						return;
5857
					}
5858
 
5859
					while (sibling) {
5860
						textNodeOffset += sibling.nodeValue.length;
5861
 
5862
						// We are at or passed the position we where looking for
5863
						if (textNodeOffset >= offset) {
5864
							container = sibling;
5865
							textNodeOffset -= offset;
5866
							break;
5867
						}
5868
 
5869
						sibling = sibling.previousSibling;
5870
					}
5871
				}
5872
 
5873
				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
5874
			};
5875
 
5876
			try {
5877
				// Find start point
5878
				findEndPoint(true);
5879
 
5880
				// Find end point if needed
5881
				if (!collapsed)
5882
					findEndPoint();
5883
			} catch (ex) {
5884
				// IE has a nasty bug where text nodes might throw "invalid argument" when you
5885
				// access the nodeValue or other properties of text nodes. This seems to happend when
5886
				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
5887
				if (ex.number == -2147024809) {
5888
					// Get the current selection
5889
					bookmark = self.getBookmark(2);
5890
 
5891
					// Get start element
5892
					tmpRange = ieRange.duplicate();
5893
					tmpRange.collapse(true);
5894
					element = tmpRange.parentElement();
5895
 
5896
					// Get end element
5897
					if (!collapsed) {
5898
						tmpRange = ieRange.duplicate();
5899
						tmpRange.collapse(false);
5900
						element2 = tmpRange.parentElement();
5901
						element2.innerHTML = element2.innerHTML;
5902
					}
5903
 
5904
					// Remove the broken elements
5905
					element.innerHTML = element.innerHTML;
5906
 
5907
					// Restore the selection
5908
					self.moveToBookmark(bookmark);
5909
 
5910
					// Since the range has moved we need to re-get it
5911
					ieRange = selection.getRng();
5912
 
5913
					// Find start point
5914
					findEndPoint(true);
5915
 
5916
					// Find end point if needed
5917
					if (!collapsed)
5918
						findEndPoint();
5919
				} else
5920
					throw ex; // Throw other errors
5921
			}
5922
 
5923
			return domRange;
5924
		};
5925
 
5926
		this.getBookmark = function(type) {
5927
			var rng = selection.getRng(), start, end, bookmark = {};
5928
 
5929
			function getIndexes(node) {
5930
				var node, parent, root, children, i, indexes = [];
5931
 
5932
				parent = node.parentNode;
5933
				root = dom.getRoot().parentNode;
5934
 
5935
				while (parent != root) {
5936
					children = parent.children;
5937
 
5938
					i = children.length;
5939
					while (i--) {
5940
						if (node === children[i]) {
5941
							indexes.push(i);
5942
							break;
5943
						}
5944
					}
5945
 
5946
					node = parent;
5947
					parent = parent.parentNode;
5948
				}
5949
 
5950
				return indexes;
5951
			};
5952
 
5953
			function getBookmarkEndPoint(start) {
5954
				var position;
5955
 
5956
				position = getPosition(rng, start);
5957
				if (position) {
5958
					return {
5959
						position : position.position,
5960
						offset : position.offset,
5961
						indexes : getIndexes(position.node),
5962
						inside : position.inside
5963
					};
5964
				}
5965
			};
5966
 
5967
			// Non ubstructive bookmark
5968
			if (type === 2) {
5969
				// Handle text selection
5970
				if (!rng.item) {
5971
					bookmark.start = getBookmarkEndPoint(true);
5972
 
5973
					if (!selection.isCollapsed())
5974
						bookmark.end = getBookmarkEndPoint();
5975
				} else
5976
					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
5977
			}
5978
 
5979
			return bookmark;
5980
		};
5981
 
5982
		this.moveToBookmark = function(bookmark) {
5983
			var rng, body = dom.doc.body;
5984
 
5985
			function resolveIndexes(indexes) {
5986
				var node, i, idx, children;
5987
 
5988
				node = dom.getRoot();
5989
				for (i = indexes.length - 1; i >= 0; i--) {
5990
					children = node.children;
5991
					idx = indexes[i];
5992
 
5993
					if (idx <= children.length - 1) {
5994
						node = children[idx];
5995
					}
5996
				}
5997
 
5998
				return node;
5999
			};
6000
 
6001
			function setBookmarkEndPoint(start) {
6002
				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
6003
 
6004
				if (endPoint) {
6005
					moveLeft = endPoint.position > 0;
6006
 
6007
					moveRng = body.createTextRange();
6008
					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
6009
 
6010
					offset = endPoint.offset;
6011
					if (offset !== undef) {
6012
						moveRng.collapse(endPoint.inside || moveLeft);
6013
						moveRng.moveStart('character', moveLeft ? -offset : offset);
6014
					} else
6015
						moveRng.collapse(start);
6016
 
6017
					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
6018
 
6019
					if (start)
6020
						rng.collapse(true);
6021
				}
6022
			};
6023
 
6024
			if (bookmark.start) {
6025
				if (bookmark.start.ctrl) {
6026
					rng = body.createControlRange();
6027
					rng.addElement(resolveIndexes(bookmark.start.indexes));
6028
					rng.select();
6029
				} else {
6030
					rng = body.createTextRange();
6031
					setBookmarkEndPoint(true);
6032
					setBookmarkEndPoint();
6033
					rng.select();
6034
				}
6035
			}
6036
		};
6037
 
6038
		this.addRange = function(rng) {
6039
			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, doc = selection.dom.doc, body = doc.body;
6040
 
6041
			function setEndPoint(start) {
6042
				var container, offset, marker, tmpRng, nodes;
6043
 
6044
				marker = dom.create('a');
6045
				container = start ? startContainer : endContainer;
6046
				offset = start ? startOffset : endOffset;
6047
				tmpRng = ieRng.duplicate();
6048
 
6049
				if (container == doc || container == doc.documentElement) {
6050
					container = body;
6051
					offset = 0;
6052
				}
6053
 
6054
				if (container.nodeType == 3) {
6055
					container.parentNode.insertBefore(marker, container);
6056
					tmpRng.moveToElementText(marker);
6057
					tmpRng.moveStart('character', offset);
6058
					dom.remove(marker);
6059
					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
6060
				} else {
6061
					nodes = container.childNodes;
6062
 
6063
					if (nodes.length) {
6064
						if (offset >= nodes.length) {
6065
							dom.insertAfter(marker, nodes[nodes.length - 1]);
6066
						} else {
6067
							container.insertBefore(marker, nodes[offset]);
6068
						}
6069
 
6070
						tmpRng.moveToElementText(marker);
6071
					} else {
6072
						// Empty node selection for example <div>|</div>
6073
						marker = doc.createTextNode('\uFEFF');
6074
						container.appendChild(marker);
6075
						tmpRng.moveToElementText(marker.parentNode);
6076
						tmpRng.collapse(TRUE);
6077
					}
6078
 
6079
					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
6080
					dom.remove(marker);
6081
				}
6082
			}
6083
 
6084
			// Setup some shorter versions
6085
			startContainer = rng.startContainer;
6086
			startOffset = rng.startOffset;
6087
			endContainer = rng.endContainer;
6088
			endOffset = rng.endOffset;
6089
			ieRng = body.createTextRange();
6090
 
6091
			// If single element selection then try making a control selection out of it
6092
			if (startContainer == endContainer && startContainer.nodeType == 1 && startOffset == endOffset - 1) {
6093
				if (startOffset == endOffset - 1) {
6094
					try {
6095
						ctrlRng = body.createControlRange();
6096
						ctrlRng.addElement(startContainer.childNodes[startOffset]);
6097
						ctrlRng.select();
6098
						return;
6099
					} catch (ex) {
6100
						// Ignore
6101
					}
6102
				}
6103
			}
6104
 
6105
			// Set start/end point of selection
6106
			setEndPoint(true);
6107
			setEndPoint();
6108
 
6109
			// Select the new range and scroll it into view
6110
			ieRng.select();
6111
		};
6112
 
6113
		// Expose range method
6114
		this.getRangeAt = getRange;
6115
	};
6116
 
6117
	// Expose the selection object
6118
	tinymce.dom.TridentSelection = Selection;
6119
})();
6120
 
6121
 
6122
(function(tinymce) {
6123
	// Shorten names
6124
	var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
6125
 
6126
	tinymce.create('tinymce.dom.EventUtils', {
6127
		EventUtils : function() {
6128
			this.inits = [];
6129
			this.events = [];
6130
		},
6131
 
6132
		add : function(o, n, f, s) {
6133
			var cb, t = this, el = t.events, r;
6134
 
6135
			if (n instanceof Array) {
6136
				r = [];
6137
 
6138
				each(n, function(n) {
6139
					r.push(t.add(o, n, f, s));
6140
				});
6141
 
6142
				return r;
6143
			}
6144
 
6145
			// Handle array
6146
			if (o && o.hasOwnProperty && o instanceof Array) {
6147
				r = [];
6148
 
6149
				each(o, function(o) {
6150
					o = DOM.get(o);
6151
					r.push(t.add(o, n, f, s));
6152
				});
6153
 
6154
				return r;
6155
			}
6156
 
6157
			o = DOM.get(o);
6158
 
6159
			if (!o)
6160
				return;
6161
 
6162
			// Setup event callback
6163
			cb = function(e) {
6164
				// Is all events disabled
6165
				if (t.disabled)
6166
					return;
6167
 
6168
				e = e || window.event;
6169
 
6170
				// Patch in target, preventDefault and stopPropagation in IE it's W3C valid
6171
				if (e && isIE) {
6172
					if (!e.target)
6173
						e.target = e.srcElement;
6174
 
6175
					// Patch in preventDefault, stopPropagation methods for W3C compatibility
6176
					tinymce.extend(e, t._stoppers);
6177
				}
6178
 
6179
				if (!s)
6180
					return f(e);
6181
 
6182
				return f.call(s, e);
6183
			};
6184
 
6185
			if (n == 'unload') {
6186
				tinymce.unloads.unshift({func : cb});
6187
				return cb;
6188
			}
6189
 
6190
			if (n == 'init') {
6191
				if (t.domLoaded)
6192
					cb();
6193
				else
6194
					t.inits.push(cb);
6195
 
6196
				return cb;
6197
			}
6198
 
6199
			// Store away listener reference
6200
			el.push({
6201
				obj : o,
6202
				name : n,
6203
				func : f,
6204
				cfunc : cb,
6205
				scope : s
6206
			});
6207
 
6208
			t._add(o, n, cb);
6209
 
6210
			return f;
6211
		},
6212
 
6213
		remove : function(o, n, f) {
6214
			var t = this, a = t.events, s = false, r;
6215
 
6216
			// Handle array
6217
			if (o && o.hasOwnProperty && o instanceof Array) {
6218
				r = [];
6219
 
6220
				each(o, function(o) {
6221
					o = DOM.get(o);
6222
					r.push(t.remove(o, n, f));
6223
				});
6224
 
6225
				return r;
6226
			}
6227
 
6228
			o = DOM.get(o);
6229
 
6230
			each(a, function(e, i) {
6231
				if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
6232
					a.splice(i, 1);
6233
					t._remove(o, n, e.cfunc);
6234
					s = true;
6235
					return false;
6236
				}
6237
			});
6238
 
6239
			return s;
6240
		},
6241
 
6242
		clear : function(o) {
6243
			var t = this, a = t.events, i, e;
6244
 
6245
			if (o) {
6246
				o = DOM.get(o);
6247
 
6248
				for (i = a.length - 1; i >= 0; i--) {
6249
					e = a[i];
6250
 
6251
					if (e.obj === o) {
6252
						t._remove(e.obj, e.name, e.cfunc);
6253
						e.obj = e.cfunc = null;
6254
						a.splice(i, 1);
6255
					}
6256
				}
6257
			}
6258
		},
6259
 
6260
		cancel : function(e) {
6261
			if (!e)
6262
				return false;
6263
 
6264
			this.stop(e);
6265
 
6266
			return this.prevent(e);
6267
		},
6268
 
6269
		stop : function(e) {
6270
			if (e.stopPropagation)
6271
				e.stopPropagation();
6272
			else
6273
				e.cancelBubble = true;
6274
 
6275
			return false;
6276
		},
6277
 
6278
		prevent : function(e) {
6279
			if (e.preventDefault)
6280
				e.preventDefault();
6281
			else
6282
				e.returnValue = false;
6283
 
6284
			return false;
6285
		},
6286
 
6287
		destroy : function() {
6288
			var t = this;
6289
 
6290
			each(t.events, function(e, i) {
6291
				t._remove(e.obj, e.name, e.cfunc);
6292
				e.obj = e.cfunc = null;
6293
			});
6294
 
6295
			t.events = [];
6296
			t = null;
6297
		},
6298
 
6299
		_add : function(o, n, f) {
6300
			if (o.attachEvent)
6301
				o.attachEvent('on' + n, f);
6302
			else if (o.addEventListener)
6303
				o.addEventListener(n, f, false);
6304
			else
6305
				o['on' + n] = f;
6306
		},
6307
 
6308
		_remove : function(o, n, f) {
6309
			if (o) {
6310
				try {
6311
					if (o.detachEvent)
6312
						o.detachEvent('on' + n, f);
6313
					else if (o.removeEventListener)
6314
						o.removeEventListener(n, f, false);
6315
					else
6316
						o['on' + n] = null;
6317
				} catch (ex) {
6318
					// Might fail with permission denined on IE so we just ignore that
6319
				}
6320
			}
6321
		},
6322
 
6323
		_pageInit : function(win) {
6324
			var t = this;
6325
 
6326
			// Keep it from running more than once
6327
			if (t.domLoaded)
6328
				return;
6329
 
6330
			t.domLoaded = true;
6331
 
6332
			each(t.inits, function(c) {
6333
				c();
6334
			});
6335
 
6336
			t.inits = [];
6337
		},
6338
 
6339
		_wait : function(win) {
6340
			var t = this, doc = win.document;
6341
 
6342
			// No need since the document is already loaded
6343
			if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
6344
				t.domLoaded = 1;
6345
				return;
6346
			}
6347
 
6348
			// Use IE method
6349
			if (doc.attachEvent) {
6350
				doc.attachEvent("onreadystatechange", function() {
6351
					if (doc.readyState === "complete") {
6352
						doc.detachEvent("onreadystatechange", arguments.callee);
6353
						t._pageInit(win);
6354
					}
6355
				});
6356
 
6357
				if (doc.documentElement.doScroll && win == win.top) {
6358
					(function() {
6359
						if (t.domLoaded)
6360
							return;
6361
 
6362
						try {
6363
							// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
6364
							// http://javascript.nwbox.com/IEContentLoaded/
6365
							doc.documentElement.doScroll("left");
6366
						} catch (ex) {
6367
							setTimeout(arguments.callee, 0);
6368
							return;
6369
						}
6370
 
6371
						t._pageInit(win);
6372
					})();
6373
				}
6374
			} else if (doc.addEventListener) {
6375
				t._add(win, 'DOMContentLoaded', function() {
6376
					t._pageInit(win);
6377
				});
6378
			}
6379
 
6380
			t._add(win, 'load', function() {
6381
				t._pageInit(win);
6382
			});
6383
		},
6384
 
6385
		_stoppers : {
6386
			preventDefault : function() {
6387
				this.returnValue = false;
6388
			},
6389
 
6390
			stopPropagation : function() {
6391
				this.cancelBubble = true;
6392
			}
6393
		}
6394
	});
6395
 
6396
	Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
6397
 
6398
	// Dispatch DOM content loaded event for IE and Safari
6399
	Event._wait(window);
6400
 
6401
	tinymce.addUnload(function() {
6402
		Event.destroy();
6403
	});
6404
})(tinymce);
6405
 
6406
(function(tinymce) {
6407
	tinymce.dom.Element = function(id, settings) {
6408
		var t = this, dom, el;
6409
 
6410
		t.settings = settings = settings || {};
6411
		t.id = id;
6412
		t.dom = dom = settings.dom || tinymce.DOM;
6413
 
6414
		// Only IE leaks DOM references, this is a lot faster
6415
		if (!tinymce.isIE)
6416
			el = dom.get(t.id);
6417
 
6418
		tinymce.each(
6419
				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' +
6420
				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' +
6421
				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' +
6422
				'isHidden,setHTML,get').split(/,/)
6423
			, function(k) {
6424
				t[k] = function() {
6425
					var a = [id], i;
6426
 
6427
					for (i = 0; i < arguments.length; i++)
6428
						a.push(arguments[i]);
6429
 
6430
					a = dom[k].apply(dom, a);
6431
					t.update(k);
6432
 
6433
					return a;
6434
				};
6435
		});
6436
 
6437
		tinymce.extend(t, {
6438
			on : function(n, f, s) {
6439
				return tinymce.dom.Event.add(t.id, n, f, s);
6440
			},
6441
 
6442
			getXY : function() {
6443
				return {
6444
					x : parseInt(t.getStyle('left')),
6445
					y : parseInt(t.getStyle('top'))
6446
				};
6447
			},
6448
 
6449
			getSize : function() {
6450
				var n = dom.get(t.id);
6451
 
6452
				return {
6453
					w : parseInt(t.getStyle('width') || n.clientWidth),
6454
					h : parseInt(t.getStyle('height') || n.clientHeight)
6455
				};
6456
			},
6457
 
6458
			moveTo : function(x, y) {
6459
				t.setStyles({left : x, top : y});
6460
			},
6461
 
6462
			moveBy : function(x, y) {
6463
				var p = t.getXY();
6464
 
6465
				t.moveTo(p.x + x, p.y + y);
6466
			},
6467
 
6468
			resizeTo : function(w, h) {
6469
				t.setStyles({width : w, height : h});
6470
			},
6471
 
6472
			resizeBy : function(w, h) {
6473
				var s = t.getSize();
6474
 
6475
				t.resizeTo(s.w + w, s.h + h);
6476
			},
6477
 
6478
			update : function(k) {
6479
				var b;
6480
 
6481
				if (tinymce.isIE6 && settings.blocker) {
6482
					k = k || '';
6483
 
6484
					// Ignore getters
6485
					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
6486
						return;
6487
 
6488
					// Remove blocker on remove
6489
					if (k == 'remove') {
6490
						dom.remove(t.blocker);
6491
						return;
6492
					}
6493
 
6494
					if (!t.blocker) {
6495
						t.blocker = dom.uniqueId();
6496
						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
6497
						dom.setStyle(b, 'opacity', 0);
6498
					} else
6499
						b = dom.get(t.blocker);
6500
 
6501
					dom.setStyles(b, {
6502
						left : t.getStyle('left', 1),
6503
						top : t.getStyle('top', 1),
6504
						width : t.getStyle('width', 1),
6505
						height : t.getStyle('height', 1),
6506
						display : t.getStyle('display', 1),
6507
						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
6508
					});
6509
				}
6510
			}
6511
		});
6512
	};
6513
})(tinymce);
6514
 
6515
(function(tinymce) {
6516
	function trimNl(s) {
6517
		return s.replace(/[\n\r]+/g, '');
6518
	};
6519
 
6520
	// Shorten names
6521
	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
6522
 
6523
	tinymce.create('tinymce.dom.Selection', {
6524
		Selection : function(dom, win, serializer) {
6525
			var t = this;
6526
 
6527
			t.dom = dom;
6528
			t.win = win;
6529
			t.serializer = serializer;
6530
 
6531
			// Add events
6532
			each([
6533
				'onBeforeSetContent',
6534
 
6535
				'onBeforeGetContent',
6536
 
6537
				'onSetContent',
6538
 
6539
				'onGetContent'
6540
			], function(e) {
6541
				t[e] = new tinymce.util.Dispatcher(t);
6542
			});
6543
 
6544
			// No W3C Range support
6545
			if (!t.win.getSelection)
6546
				t.tridentSel = new tinymce.dom.TridentSelection(t);
6547
 
6548
			if (tinymce.isIE && dom.boxModel)
6549
				this._fixIESelection();
6550
 
6551
			// Prevent leaks
6552
			tinymce.addUnload(t.destroy, t);
6553
		},
6554
 
6555
		setCursorLocation: function(node, offset) {
6556
			var t = this; var r = t.dom.createRng();
6557
			r.setStart(node, offset);
6558
			r.setEnd(node, offset);
6559
			t.setRng(r);
6560
			t.collapse(false);
6561
		},
6562
		getContent : function(s) {
6563
			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
6564
 
6565
			s = s || {};
6566
			wb = wa = '';
6567
			s.get = true;
6568
			s.format = s.format || 'html';
6569
			s.forced_root_block = '';
6570
			t.onBeforeGetContent.dispatch(t, s);
6571
 
6572
			if (s.format == 'text')
6573
				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
6574
 
6575
			if (r.cloneContents) {
6576
				n = r.cloneContents();
6577
 
6578
				if (n)
6579
					e.appendChild(n);
6580
			} else if (is(r.item) || is(r.htmlText)) {
6581
				// IE will produce invalid markup if elements are present that
6582
				// it doesn't understand like custom elements or HTML5 elements.
6583
				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
6584
				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
6585
				e.removeChild(e.firstChild);
6586
			} else
6587
				e.innerHTML = r.toString();
6588
 
6589
			// Keep whitespace before and after
6590
			if (/^\s/.test(e.innerHTML))
6591
				wb = ' ';
6592
 
6593
			if (/\s+$/.test(e.innerHTML))
6594
				wa = ' ';
6595
 
6596
			s.getInner = true;
6597
 
6598
			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
6599
			t.onGetContent.dispatch(t, s);
6600
 
6601
			return s.content;
6602
		},
6603
 
6604
		setContent : function(content, args) {
6605
			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
6606
 
6607
			args = args || {format : 'html'};
6608
			args.set = true;
6609
			content = args.content = content;
6610
 
6611
			// Dispatch before set content event
6612
			if (!args.no_events)
6613
				self.onBeforeSetContent.dispatch(self, args);
6614
 
6615
			content = args.content;
6616
 
6617
			if (rng.insertNode) {
6618
				// Make caret marker since insertNode places the caret in the beginning of text after insert
6619
				content += '<span id="__caret">_</span>';
6620
 
6621
				// Delete and insert new node
6622
				if (rng.startContainer == doc && rng.endContainer == doc) {
6623
					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
6624
					doc.body.innerHTML = content;
6625
				} else {
6626
					rng.deleteContents();
6627
 
6628
					if (doc.body.childNodes.length == 0) {
6629
						doc.body.innerHTML = content;
6630
					} else {
6631
						// createContextualFragment doesn't exists in IE 9 DOMRanges
6632
						if (rng.createContextualFragment) {
6633
							rng.insertNode(rng.createContextualFragment(content));
6634
						} else {
6635
							// Fake createContextualFragment call in IE 9
6636
							frag = doc.createDocumentFragment();
6637
							temp = doc.createElement('div');
6638
 
6639
							frag.appendChild(temp);
6640
							temp.outerHTML = content;
6641
 
6642
							rng.insertNode(frag);
6643
						}
6644
					}
6645
				}
6646
 
6647
				// Move to caret marker
6648
				caretNode = self.dom.get('__caret');
6649
 
6650
				// Make sure we wrap it compleatly, Opera fails with a simple select call
6651
				rng = doc.createRange();
6652
				rng.setStartBefore(caretNode);
6653
				rng.setEndBefore(caretNode);
6654
				self.setRng(rng);
6655
 
6656
				// Remove the caret position
6657
				self.dom.remove('__caret');
6658
 
6659
				try {
6660
					self.setRng(rng);
6661
				} catch (ex) {
6662
					// Might fail on Opera for some odd reason
6663
				}
6664
			} else {
6665
				if (rng.item) {
6666
					// Delete content and get caret text selection
6667
					doc.execCommand('Delete', false, null);
6668
					rng = self.getRng();
6669
				}
6670
 
6671
				// Explorer removes spaces from the beginning of pasted contents
6672
				if (/^\s+/.test(content)) {
6673
					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
6674
					self.dom.remove('__mce_tmp');
6675
				} else
6676
					rng.pasteHTML(content);
6677
			}
6678
 
6679
			// Dispatch set content event
6680
			if (!args.no_events)
6681
				self.onSetContent.dispatch(self, args);
6682
		},
6683
 
6684
		getStart : function() {
6685
			var rng = this.getRng(), startElement, parentElement, checkRng, node;
6686
 
6687
			if (rng.duplicate || rng.item) {
6688
				// Control selection, return first item
6689
				if (rng.item)
6690
					return rng.item(0);
6691
 
6692
				// Get start element
6693
				checkRng = rng.duplicate();
6694
				checkRng.collapse(1);
6695
				startElement = checkRng.parentElement();
6696
 
6697
				// Check if range parent is inside the start element, then return the inner parent element
6698
				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
6699
				parentElement = node = rng.parentElement();
6700
				while (node = node.parentNode) {
6701
					if (node == startElement) {
6702
						startElement = parentElement;
6703
						break;
6704
					}
6705
				}
6706
 
6707
				return startElement;
6708
			} else {
6709
				startElement = rng.startContainer;
6710
 
6711
				if (startElement.nodeType == 1 && startElement.hasChildNodes())
6712
					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
6713
 
6714
				if (startElement && startElement.nodeType == 3)
6715
					return startElement.parentNode;
6716
 
6717
				return startElement;
6718
			}
6719
		},
6720
 
6721
		getEnd : function() {
6722
			var t = this, r = t.getRng(), e, eo;
6723
 
6724
			if (r.duplicate || r.item) {
6725
				if (r.item)
6726
					return r.item(0);
6727
 
6728
				r = r.duplicate();
6729
				r.collapse(0);
6730
				e = r.parentElement();
6731
 
6732
				if (e && e.nodeName == 'BODY')
6733
					return e.lastChild || e;
6734
 
6735
				return e;
6736
			} else {
6737
				e = r.endContainer;
6738
				eo = r.endOffset;
6739
 
6740
				if (e.nodeType == 1 && e.hasChildNodes())
6741
					e = e.childNodes[eo > 0 ? eo - 1 : eo];
6742
 
6743
				if (e && e.nodeType == 3)
6744
					return e.parentNode;
6745
 
6746
				return e;
6747
			}
6748
		},
6749
 
6750
		getBookmark : function(type, normalized) {
6751
			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
6752
 
6753
			function findIndex(name, element) {
6754
				var index = 0;
6755
 
6756
				each(dom.select(name), function(node, i) {
6757
					if (node == element)
6758
						index = i;
6759
				});
6760
 
6761
				return index;
6762
			};
6763
 
6764
			if (type == 2) {
6765
				function getLocation() {
6766
					var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
6767
 
6768
					function getPoint(rng, start) {
6769
						var container = rng[start ? 'startContainer' : 'endContainer'],
6770
							offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
6771
 
6772
						if (container.nodeType == 3) {
6773
							if (normalized) {
6774
								for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
6775
									offset += node.nodeValue.length;
6776
							}
6777
 
6778
							point.push(offset);
6779
						} else {
6780
							childNodes = container.childNodes;
6781
 
6782
							if (offset >= childNodes.length && childNodes.length) {
6783
								after = 1;
6784
								offset = Math.max(0, childNodes.length - 1);
6785
							}
6786
 
6787
							point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
6788
						}
6789
 
6790
						for (; container && container != root; container = container.parentNode)
6791
							point.push(t.dom.nodeIndex(container, normalized));
6792
 
6793
						return point;
6794
					};
6795
 
6796
					bookmark.start = getPoint(rng, true);
6797
 
6798
					if (!t.isCollapsed())
6799
						bookmark.end = getPoint(rng);
6800
 
6801
					return bookmark;
6802
				};
6803
 
6804
				if (t.tridentSel)
6805
					return t.tridentSel.getBookmark(type);
6806
 
6807
				return getLocation();
6808
			}
6809
 
6810
			// Handle simple range
6811
			if (type)
6812
				return {rng : t.getRng()};
6813
 
6814
			rng = t.getRng();
6815
			id = dom.uniqueId();
6816
			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
6817
			styles = 'overflow:hidden;line-height:0px';
6818
 
6819
			// Explorer method
6820
			if (rng.duplicate || rng.item) {
6821
				// Text selection
6822
				if (!rng.item) {
6823
					rng2 = rng.duplicate();
6824
 
6825
					try {
6826
						// Insert start marker
6827
						rng.collapse();
6828
						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
6829
 
6830
						// Insert end marker
6831
						if (!collapsed) {
6832
							rng2.collapse(false);
6833
 
6834
							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
6835
							rng.moveToElementText(rng2.parentElement());
6836
							if (rng.compareEndPoints('StartToEnd', rng2) == 0)
6837
								rng2.move('character', -1);
6838
 
6839
							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
6840
						}
6841
					} catch (ex) {
6842
						// IE might throw unspecified error so lets ignore it
6843
						return null;
6844
					}
6845
				} else {
6846
					// Control selection
6847
					element = rng.item(0);
6848
					name = element.nodeName;
6849
 
6850
					return {name : name, index : findIndex(name, element)};
6851
				}
6852
			} else {
6853
				element = t.getNode();
6854
				name = element.nodeName;
6855
				if (name == 'IMG')
6856
					return {name : name, index : findIndex(name, element)};
6857
 
6858
				// W3C method
6859
				rng2 = rng.cloneRange();
6860
 
6861
				// Insert end marker
6862
				if (!collapsed) {
6863
					rng2.collapse(false);
6864
					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
6865
				}
6866
 
6867
				rng.collapse(true);
6868
				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
6869
			}
6870
 
6871
			t.moveToBookmark({id : id, keep : 1});
6872
 
6873
			return {id : id};
6874
		},
6875
 
6876
		moveToBookmark : function(bookmark) {
6877
			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
6878
 
6879
			if (bookmark) {
6880
				if (bookmark.start) {
6881
					rng = dom.createRng();
6882
					root = dom.getRoot();
6883
 
6884
					function setEndPoint(start) {
6885
						var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
6886
 
6887
						if (point) {
6888
							offset = point[0];
6889
 
6890
							// Find container node
6891
							for (node = root, i = point.length - 1; i >= 1; i--) {
6892
								children = node.childNodes;
6893
 
6894
								if (point[i] > children.length - 1)
6895
									return;
6896
 
6897
								node = children[point[i]];
6898
							}
6899
 
6900
							// Move text offset to best suitable location
6901
							if (node.nodeType === 3)
6902
								offset = Math.min(point[0], node.nodeValue.length);
6903
 
6904
							// Move element offset to best suitable location
6905
							if (node.nodeType === 1)
6906
								offset = Math.min(point[0], node.childNodes.length);
6907
 
6908
							// Set offset within container node
6909
							if (start)
6910
								rng.setStart(node, offset);
6911
							else
6912
								rng.setEnd(node, offset);
6913
						}
6914
 
6915
						return true;
6916
					};
6917
 
6918
					if (t.tridentSel)
6919
						return t.tridentSel.moveToBookmark(bookmark);
6920
 
6921
					if (setEndPoint(true) && setEndPoint()) {
6922
						t.setRng(rng);
6923
					}
6924
				} else if (bookmark.id) {
6925
					function restoreEndPoint(suffix) {
6926
						var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
6927
 
6928
						if (marker) {
6929
							node = marker.parentNode;
6930
 
6931
							if (suffix == 'start') {
6932
								if (!keep) {
6933
									idx = dom.nodeIndex(marker);
6934
								} else {
6935
									node = marker.firstChild;
6936
									idx = 1;
6937
								}
6938
 
6939
								startContainer = endContainer = node;
6940
								startOffset = endOffset = idx;
6941
							} else {
6942
								if (!keep) {
6943
									idx = dom.nodeIndex(marker);
6944
								} else {
6945
									node = marker.firstChild;
6946
									idx = 1;
6947
								}
6948
 
6949
								endContainer = node;
6950
								endOffset = idx;
6951
							}
6952
 
6953
							if (!keep) {
6954
								prev = marker.previousSibling;
6955
								next = marker.nextSibling;
6956
 
6957
								// Remove all marker text nodes
6958
								each(tinymce.grep(marker.childNodes), function(node) {
6959
									if (node.nodeType == 3)
6960
										node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
6961
								});
6962
 
6963
								// Remove marker but keep children if for example contents where inserted into the marker
6964
								// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
6965
								while (marker = dom.get(bookmark.id + '_' + suffix))
6966
									dom.remove(marker, 1);
6967
 
6968
								// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
6969
								// 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
6970
								if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
6971
									idx = prev.nodeValue.length;
6972
									prev.appendData(next.nodeValue);
6973
									dom.remove(next);
6974
 
6975
									if (suffix == 'start') {
6976
										startContainer = endContainer = prev;
6977
										startOffset = endOffset = idx;
6978
									} else {
6979
										endContainer = prev;
6980
										endOffset = idx;
6981
									}
6982
								}
6983
							}
6984
						}
6985
					};
6986
 
6987
					function addBogus(node) {
6988
						// Adds a bogus BR element for empty block elements or just a space on IE since it renders BR elements incorrectly
6989
						if (dom.isBlock(node) && !node.innerHTML)
6990
							node.innerHTML = !isIE ? '<br data-mce-bogus="1" />' : ' ';
6991
 
6992
						return node;
6993
					};
6994
 
6995
					// Restore start/end points
6996
					restoreEndPoint('start');
6997
					restoreEndPoint('end');
6998
 
6999
					if (startContainer) {
7000
						rng = dom.createRng();
7001
						rng.setStart(addBogus(startContainer), startOffset);
7002
						rng.setEnd(addBogus(endContainer), endOffset);
7003
						t.setRng(rng);
7004
					}
7005
				} else if (bookmark.name) {
7006
					t.select(dom.select(bookmark.name)[bookmark.index]);
7007
				} else if (bookmark.rng)
7008
					t.setRng(bookmark.rng);
7009
			}
7010
		},
7011
 
7012
		select : function(node, content) {
7013
			var t = this, dom = t.dom, rng = dom.createRng(), idx;
7014
 
7015
			if (node) {
7016
				idx = dom.nodeIndex(node);
7017
				rng.setStart(node.parentNode, idx);
7018
				rng.setEnd(node.parentNode, idx + 1);
7019
 
7020
				// Find first/last text node or BR element
7021
				if (content) {
7022
					function setPoint(node, start) {
7023
						var walker = new tinymce.dom.TreeWalker(node, node);
7024
 
7025
						do {
7026
							// Text node
7027
							if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length != 0) {
7028
								if (start)
7029
									rng.setStart(node, 0);
7030
								else
7031
									rng.setEnd(node, node.nodeValue.length);
7032
 
7033
								return;
7034
							}
7035
 
7036
							// BR element
7037
							if (node.nodeName == 'BR') {
7038
								if (start)
7039
									rng.setStartBefore(node);
7040
								else
7041
									rng.setEndBefore(node);
7042
 
7043
								return;
7044
							}
7045
						} while (node = (start ? walker.next() : walker.prev()));
7046
					};
7047
 
7048
					setPoint(node, 1);
7049
					setPoint(node);
7050
				}
7051
 
7052
				t.setRng(rng);
7053
			}
7054
 
7055
			return node;
7056
		},
7057
 
7058
		isCollapsed : function() {
7059
			var t = this, r = t.getRng(), s = t.getSel();
7060
 
7061
			if (!r || r.item)
7062
				return false;
7063
 
7064
			if (r.compareEndPoints)
7065
				return r.compareEndPoints('StartToEnd', r) === 0;
7066
 
7067
			return !s || r.collapsed;
7068
		},
7069
 
7070
		collapse : function(to_start) {
7071
			var self = this, rng = self.getRng(), node;
7072
 
7073
			// Control range on IE
7074
			if (rng.item) {
7075
				node = rng.item(0);
7076
				rng = self.win.document.body.createTextRange();
7077
				rng.moveToElementText(node);
7078
			}
7079
 
7080
			rng.collapse(!!to_start);
7081
			self.setRng(rng);
7082
		},
7083
 
7084
		getSel : function() {
7085
			var t = this, w = this.win;
7086
 
7087
			return w.getSelection ? w.getSelection() : w.document.selection;
7088
		},
7089
 
7090
		getRng : function(w3c) {
7091
			var t = this, s, r, elm, doc = t.win.document;
7092
 
7093
			// Found tridentSel object then we need to use that one
7094
			if (w3c && t.tridentSel)
7095
				return t.tridentSel.getRangeAt(0);
7096
 
7097
			try {
7098
				if (s = t.getSel())
7099
					r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : doc.createRange());
7100
			} catch (ex) {
7101
				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
7102
			}
7103
 
7104
			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
7105
			if (tinymce.isIE && r && r.setStart && doc.selection.createRange().item) {
7106
				elm = doc.selection.createRange().item(0);
7107
				r = doc.createRange();
7108
				r.setStartBefore(elm);
7109
				r.setEndAfter(elm);
7110
			}
7111
 
7112
			// No range found then create an empty one
7113
			// This can occur when the editor is placed in a hidden container element on Gecko
7114
			// Or on IE when there was an exception
7115
			if (!r)
7116
				r = doc.createRange ? doc.createRange() : doc.body.createTextRange();
7117
 
7118
			if (t.selectedRange && t.explicitRange) {
7119
				if (r.compareBoundaryPoints(r.START_TO_START, t.selectedRange) === 0 && r.compareBoundaryPoints(r.END_TO_END, t.selectedRange) === 0) {
7120
					// Safari, Opera and Chrome only ever select text which causes the range to change.
7121
					// This lets us use the originally set range if the selection hasn't been changed by the user.
7122
					r = t.explicitRange;
7123
				} else {
7124
					t.selectedRange = null;
7125
					t.explicitRange = null;
7126
				}
7127
			}
7128
 
7129
			return r;
7130
		},
7131
 
7132
		setRng : function(r) {
7133
			var s, t = this;
7134
 
7135
			if (!t.tridentSel) {
7136
				s = t.getSel();
7137
 
7138
				if (s) {
7139
					t.explicitRange = r;
7140
 
7141
					try {
7142
						s.removeAllRanges();
7143
					} catch (ex) {
7144
						// IE9 might throw errors here don't know why
7145
					}
7146
 
7147
					s.addRange(r);
7148
					t.selectedRange = s.getRangeAt(0);
7149
				}
7150
			} else {
7151
				// Is W3C Range
7152
				if (r.cloneRange) {
7153
					t.tridentSel.addRange(r);
7154
					return;
7155
				}
7156
 
7157
				// Is IE specific range
7158
				try {
7159
					r.select();
7160
				} catch (ex) {
7161
					// Needed for some odd IE bug #1843306
7162
				}
7163
			}
7164
		},
7165
 
7166
		setNode : function(n) {
7167
			var t = this;
7168
 
7169
			t.setContent(t.dom.getOuterHTML(n));
7170
 
7171
			return n;
7172
		},
7173
 
7174
		getNode : function() {
7175
			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
7176
 
7177
			// Range maybe lost after the editor is made visible again
7178
			if (!rng)
7179
				return t.dom.getRoot();
7180
 
7181
			if (rng.setStart) {
7182
				elm = rng.commonAncestorContainer;
7183
 
7184
				// Handle selection a image or other control like element such as anchors
7185
				if (!rng.collapsed) {
7186
					if (rng.startContainer == rng.endContainer) {
7187
						if (rng.endOffset - rng.startOffset < 2) {
7188
							if (rng.startContainer.hasChildNodes())
7189
								elm = rng.startContainer.childNodes[rng.startOffset];
7190
						}
7191
					}
7192
 
7193
					// If the anchor node is a element instead of a text node then return this element
7194
					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
7195
					//	return sel.anchorNode.childNodes[sel.anchorOffset];
7196
 
7197
					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
7198
					// This happens when you double click an underlined word in FireFox.
7199
					if (start.nodeType === 3 && end.nodeType === 3) {
7200
						function skipEmptyTextNodes(n, forwards) {
7201
							var orig = n;
7202
							while (n && n.nodeType === 3 && n.length === 0) {
7203
								n = forwards ? n.nextSibling : n.previousSibling;
7204
							}
7205
							return n || orig;
7206
						}
7207
						if (start.length === rng.startOffset) {
7208
							start = skipEmptyTextNodes(start.nextSibling, true);
7209
						} else {
7210
							start = start.parentNode;
7211
						}
7212
						if (rng.endOffset === 0) {
7213
							end = skipEmptyTextNodes(end.previousSibling, false);
7214
						} else {
7215
							end = end.parentNode;
7216
						}
7217
 
7218
						if (start && start === end)
7219
							return start;
7220
					}
7221
				}
7222
 
7223
				if (elm && elm.nodeType == 3)
7224
					return elm.parentNode;
7225
 
7226
				return elm;
7227
			}
7228
 
7229
			return rng.item ? rng.item(0) : rng.parentElement();
7230
		},
7231
 
7232
		getSelectedBlocks : function(st, en) {
7233
			var t = this, dom = t.dom, sb, eb, n, bl = [];
7234
 
7235
			sb = dom.getParent(st || t.getStart(), dom.isBlock);
7236
			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
7237
 
7238
			if (sb)
7239
				bl.push(sb);
7240
 
7241
			if (sb && eb && sb != eb) {
7242
				n = sb;
7243
 
7244
				while ((n = n.nextSibling) && n != eb) {
7245
					if (dom.isBlock(n))
7246
						bl.push(n);
7247
				}
7248
			}
7249
 
7250
			if (eb && sb != eb)
7251
				bl.push(eb);
7252
 
7253
			return bl;
7254
		},
7255
 
7256
		normalize : function() {
7257
			var self = this, rng, normalized;
7258
 
7259
			// Normalize only on non IE browsers for now
7260
			if (tinymce.isIE)
7261
				return;
7262
 
7263
			function normalizeEndPoint(start) {
7264
				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node;
7265
 
7266
				container = rng[(start ? 'start' : 'end') + 'Container'];
7267
				offset = rng[(start ? 'start' : 'end') + 'Offset'];
7268
 
7269
				// If the container is a document move it to the body element
7270
				if (container.nodeType === 9) {
7271
					container = container.body;
7272
					offset = 0;
7273
				}
7274
 
7275
				// If the container is body try move it into the closest text node or position
7276
				// TODO: Add more logic here to handle element selection cases
7277
				if (container === body) {
7278
					// Resolve the index
7279
					if (container.hasChildNodes()) {
7280
						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
7281
						offset = 0;
7282
 
7283
						// Don't walk into elements that doesn't have any child nodes like a IMG
7284
						if (container.hasChildNodes()) {
7285
							// Walk the DOM to find a text node to place the caret at or a BR
7286
							node = container;
7287
							walker = new tinymce.dom.TreeWalker(container, body);
7288
							do {
7289
								// Found a text node use that position
7290
								if (node.nodeType === 3) {
7291
									offset = start ? 0 : node.nodeValue.length - 1;
7292
									container = node;
7293
									break;
7294
								}
7295
 
7296
								// Found a BR element that we can place the caret before
7297
								if (node.nodeName === 'BR') {
7298
									offset = dom.nodeIndex(node);
7299
									container = node.parentNode;
7300
									break;
7301
								}
7302
							} while (node = (start ? walker.next() : walker.prev()));
7303
 
7304
							normalized = true;
7305
						}
7306
					}
7307
				}
7308
 
7309
				// Set endpoint if it was normalized
7310
				if (normalized)
7311
					rng['set' + (start ? 'Start' : 'End')](container, offset);
7312
			};
7313
 
7314
			rng = self.getRng();
7315
 
7316
			// Normalize the end points
7317
			normalizeEndPoint(true);
7318
 
7319
			if (rng.collapsed)
7320
				normalizeEndPoint();
7321
 
7322
			// Set the selection if it was normalized
7323
			if (normalized) {
7324
				//console.log(self.dom.dumpRng(rng));
7325
				self.setRng(rng);
7326
			}
7327
		},
7328
 
7329
		destroy : function(s) {
7330
			var t = this;
7331
 
7332
			t.win = null;
7333
 
7334
			// Manual destroy then remove unload handler
7335
			if (!s)
7336
				tinymce.removeUnload(t.destroy);
7337
		},
7338
 
7339
		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
7340
		_fixIESelection : function() {
7341
			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
7342
 
7343
			// Make HTML element unselectable since we are going to handle selection by hand
7344
			doc.documentElement.unselectable = true;
7345
 
7346
			// Return range from point or null if it failed
7347
			function rngFromPoint(x, y) {
7348
				var rng = body.createTextRange();
7349
 
7350
				try {
7351
					rng.moveToPoint(x, y);
7352
				} catch (ex) {
7353
					// IE sometimes throws and exception, so lets just ignore it
7354
					rng = null;
7355
				}
7356
 
7357
				return rng;
7358
			};
7359
 
7360
			// Fires while the selection is changing
7361
			function selectionChange(e) {
7362
				var pointRng;
7363
 
7364
				// Check if the button is down or not
7365
				if (e.button) {
7366
					// Create range from mouse position
7367
					pointRng = rngFromPoint(e.x, e.y);
7368
 
7369
					if (pointRng) {
7370
						// Check if pointRange is before/after selection then change the endPoint
7371
						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
7372
							pointRng.setEndPoint('StartToStart', startRng);
7373
						else
7374
							pointRng.setEndPoint('EndToEnd', startRng);
7375
 
7376
						pointRng.select();
7377
					}
7378
				} else
7379
					endSelection();
7380
			}
7381
 
7382
			// Removes listeners
7383
			function endSelection() {
7384
				var rng = doc.selection.createRange();
7385
 
7386
				// If the range is collapsed then use the last start range
7387
				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
7388
					startRng.select();
7389
 
7390
				dom.unbind(doc, 'mouseup', endSelection);
7391
				dom.unbind(doc, 'mousemove', selectionChange);
7392
				startRng = started = 0;
7393
			};
7394
 
7395
			// Detect when user selects outside BODY
7396
			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
7397
				if (e.target.nodeName === 'HTML') {
7398
					if (started)
7399
						endSelection();
7400
 
7401
					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
7402
					htmlElm = doc.documentElement;
7403
					if (htmlElm.scrollHeight > htmlElm.clientHeight)
7404
						return;
7405
 
7406
					started = 1;
7407
					// Setup start position
7408
					startRng = rngFromPoint(e.x, e.y);
7409
					if (startRng) {
7410
						// Listen for selection change events
7411
						dom.bind(doc, 'mouseup', endSelection);
7412
						dom.bind(doc, 'mousemove', selectionChange);
7413
 
7414
						dom.win.focus();
7415
						startRng.select();
7416
					}
7417
				}
7418
			});
7419
		}
7420
	});
7421
})(tinymce);
7422
 
7423
(function(tinymce) {
7424
	tinymce.dom.Serializer = function(settings, dom, schema) {
7425
		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
7426
 
7427
		// Support the old apply_source_formatting option
7428
		if (!settings.apply_source_formatting)
7429
			settings.indent = false;
7430
 
7431
		settings.remove_trailing_brs = true;
7432
 
7433
		// Default DOM and Schema if they are undefined
7434
		dom = dom || tinymce.DOM;
7435
		schema = schema || new tinymce.html.Schema(settings);
7436
		settings.entity_encoding = settings.entity_encoding || 'named';
7437
 
7438
		onPreProcess = new tinymce.util.Dispatcher(self);
7439
 
7440
		onPostProcess = new tinymce.util.Dispatcher(self);
7441
 
7442
		htmlParser = new tinymce.html.DomParser(settings, schema);
7443
 
7444
		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
7445
		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
7446
			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
7447
 
7448
			while (i--) {
7449
				node = nodes[i];
7450
 
7451
				value = node.attributes.map[internalName];
7452
				if (value !== undef) {
7453
					// Set external name to internal value and remove internal
7454
					node.attr(name, value.length > 0 ? value : null);
7455
					node.attr(internalName, null);
7456
				} else {
7457
					// No internal attribute found then convert the value we have in the DOM
7458
					value = node.attributes.map[name];
7459
 
7460
					if (name === "style")
7461
						value = dom.serializeStyle(dom.parseStyle(value), node.name);
7462
					else if (urlConverter)
7463
						value = urlConverter.call(urlConverterScope, value, name, node.name);
7464
 
7465
					node.attr(name, value.length > 0 ? value : null);
7466
				}
7467
			}
7468
		});
7469
 
7470
		// Remove internal classes mceItem<..>
7471
		htmlParser.addAttributeFilter('class', function(nodes, name) {
7472
			var i = nodes.length, node, value;
7473
 
7474
			while (i--) {
7475
				node = nodes[i];
7476
				value = node.attr('class').replace(/\s*mce(Item\w+|Selected)\s*/g, '');
7477
				node.attr('class', value.length > 0 ? value : null);
7478
			}
7479
		});
7480
 
7481
		// Remove bookmark elements
7482
		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
7483
			var i = nodes.length, node;
7484
 
7485
			while (i--) {
7486
				node = nodes[i];
7487
 
7488
				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
7489
					node.remove();
7490
			}
7491
		});
7492
 
7493
		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
7494
		htmlParser.addNodeFilter('script,style', function(nodes, name) {
7495
			var i = nodes.length, node, value;
7496
 
7497
			function trim(value) {
7498
				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
7499
						.replace(/^[\r\n]*|[\r\n]*$/g, '')
7500
						.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '')
7501
						.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
7502
			};
7503
 
7504
			while (i--) {
7505
				node = nodes[i];
7506
				value = node.firstChild ? node.firstChild.value : '';
7507
 
7508
				if (name === "script") {
7509
					// Remove mce- prefix from script elements
7510
					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
7511
 
7512
					if (value.length > 0)
7513
						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
7514
				} else {
7515
					if (value.length > 0)
7516
						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
7517
				}
7518
			}
7519
		});
7520
 
7521
		// Convert comments to cdata and handle protected comments
7522
		htmlParser.addNodeFilter('#comment', function(nodes, name) {
7523
			var i = nodes.length, node;
7524
 
7525
			while (i--) {
7526
				node = nodes[i];
7527
 
7528
				if (node.value.indexOf('[CDATA[') === 0) {
7529
					node.name = '#cdata';
7530
					node.type = 4;
7531
					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
7532
				} else if (node.value.indexOf('mce:protected ') === 0) {
7533
					node.name = "#text";
7534
					node.type = 3;
7535
					node.raw = true;
7536
					node.value = unescape(node.value).substr(14);
7537
				}
7538
			}
7539
		});
7540
 
7541
		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
7542
			var i = nodes.length, node;
7543
 
7544
			while (i--) {
7545
				node = nodes[i];
7546
				if (node.type === 7)
7547
					node.remove();
7548
				else if (node.type === 1) {
7549
					if (name === "input" && !("type" in node.attributes.map))
7550
						node.attr('type', 'text');
7551
				}
7552
			}
7553
		});
7554
 
7555
		// Fix list elements, TODO: Replace this later
7556
		if (settings.fix_list_elements) {
7557
			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
7558
				var i = nodes.length, node, parentNode;
7559
 
7560
				while (i--) {
7561
					node = nodes[i];
7562
					parentNode = node.parent;
7563
 
7564
					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
7565
						if (node.prev && node.prev.name === 'li') {
7566
							node.prev.append(node);
7567
						}
7568
					}
7569
				}
7570
			});
7571
		}
7572
 
7573
		// Remove internal data attributes
7574
		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
7575
			var i = nodes.length;
7576
 
7577
			while (i--) {
7578
				nodes[i].attr(name, null);
7579
			}
7580
		});
7581
 
7582
		// Return public methods
7583
		return {
7584
			schema : schema,
7585
 
7586
			addNodeFilter : htmlParser.addNodeFilter,
7587
 
7588
			addAttributeFilter : htmlParser.addAttributeFilter,
7589
 
7590
			onPreProcess : onPreProcess,
7591
 
7592
			onPostProcess : onPostProcess,
7593
 
7594
			serialize : function(node, args) {
7595
				var impl, doc, oldDoc, htmlSerializer, content;
7596
 
7597
				// Explorer won't clone contents of script and style and the
7598
				// selected index of select elements are cleared on a clone operation.
7599
				if (isIE && dom.select('script,style,select,map').length > 0) {
7600
					content = node.innerHTML;
7601
					node = node.cloneNode(false);
7602
					dom.setHTML(node, content);
7603
				} else
7604
					node = node.cloneNode(true);
7605
 
7606
				// Nodes needs to be attached to something in WebKit/Opera
7607
				// Older builds of Opera crashes if you attach the node to an document created dynamically
7608
				// and since we can't feature detect a crash we need to sniff the acutal build number
7609
				// This fix will make DOM ranges and make Sizzle happy!
7610
				impl = node.ownerDocument.implementation;
7611
				if (impl.createHTMLDocument) {
7612
					// Create an empty HTML document
7613
					doc = impl.createHTMLDocument("");
7614
 
7615
					// Add the element or it's children if it's a body element to the new document
7616
					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
7617
						doc.body.appendChild(doc.importNode(node, true));
7618
					});
7619
 
7620
					// Grab first child or body element for serialization
7621
					if (node.nodeName != 'BODY')
7622
						node = doc.body.firstChild;
7623
					else
7624
						node = doc.body;
7625
 
7626
					// set the new document in DOMUtils so createElement etc works
7627
					oldDoc = dom.doc;
7628
					dom.doc = doc;
7629
				}
7630
 
7631
				args = args || {};
7632
				args.format = args.format || 'html';
7633
 
7634
				// Pre process
7635
				if (!args.no_events) {
7636
					args.node = node;
7637
					onPreProcess.dispatch(self, args);
7638
				}
7639
 
7640
				// Setup serializer
7641
				htmlSerializer = new tinymce.html.Serializer(settings, schema);
7642
 
7643
				// Parse and serialize HTML
7644
				args.content = htmlSerializer.serialize(
7645
					htmlParser.parse(args.getInner ? node.innerHTML : tinymce.trim(dom.getOuterHTML(node), args), args)
7646
				);
7647
 
7648
				// Replace all BOM characters for now until we can find a better solution
7649
				if (!args.cleanup)
7650
					args.content = args.content.replace(/\uFEFF/g, '');
7651
 
7652
				// Post process
7653
				if (!args.no_events)
7654
					onPostProcess.dispatch(self, args);
7655
 
7656
				// Restore the old document if it was changed
7657
				if (oldDoc)
7658
					dom.doc = oldDoc;
7659
 
7660
				args.node = null;
7661
 
7662
				return args.content;
7663
			},
7664
 
7665
			addRules : function(rules) {
7666
				schema.addValidElements(rules);
7667
			},
7668
 
7669
			setRules : function(rules) {
7670
				schema.setValidElements(rules);
7671
			}
7672
		};
7673
	};
7674
})(tinymce);
7675
(function(tinymce) {
7676
	tinymce.dom.ScriptLoader = function(settings) {
7677
		var QUEUED = 0,
7678
			LOADING = 1,
7679
			LOADED = 2,
7680
			states = {},
7681
			queue = [],
7682
			scriptLoadedCallbacks = {},
7683
			queueLoadedCallbacks = [],
7684
			loading = 0,
7685
			undefined;
7686
 
7687
		function loadScript(url, callback) {
7688
			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
7689
 
7690
			// Execute callback when script is loaded
7691
			function done() {
7692
				dom.remove(id);
7693
 
7694
				if (elm)
7695
					elm.onreadystatechange = elm.onload = elm = null;
7696
 
7697
				callback();
7698
			};
7699
 
7700
			function error() {
7701
				// Report the error so it's easier for people to spot loading errors
7702
				if (typeof(console) !== "undefined" && console.log)
7703
					console.log("Failed to load: " + url);
7704
 
7705
				// We can't mark it as done if there is a load error since
7706
				// A) We don't want to produce 404 errors on the server and
7707
				// B) the onerror event won't fire on all browsers.
7708
				// done();
7709
			};
7710
 
7711
			id = dom.uniqueId();
7712
 
7713
			if (tinymce.isIE6) {
7714
				uri = new tinymce.util.URI(url);
7715
				loc = location;
7716
 
7717
				// If script is from same domain and we
7718
				// use IE 6 then use XHR since it's more reliable
7719
				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
7720
					tinymce.util.XHR.send({
7721
						url : tinymce._addVer(uri.getURI()),
7722
						success : function(content) {
7723
							// Create new temp script element
7724
							var script = dom.create('script', {
7725
								type : 'text/javascript'
7726
							});
7727
 
7728
							// Evaluate script in global scope
7729
							script.text = content;
7730
							document.getElementsByTagName('head')[0].appendChild(script);
7731
							dom.remove(script);
7732
 
7733
							done();
7734
						},
7735
 
7736
						error : error
7737
					});
7738
 
7739
					return;
7740
				}
7741
			}
7742
 
7743
			// Create new script element
7744
			elm = dom.create('script', {
7745
				id : id,
7746
				type : 'text/javascript',
7747
				src : tinymce._addVer(url)
7748
			});
7749
 
7750
			// Add onload listener for non IE browsers since IE9
7751
			// fires onload event before the script is parsed and executed
7752
			if (!tinymce.isIE)
7753
				elm.onload = done;
7754
 
7755
			// Add onerror event will get fired on some browsers but not all of them
7756
			elm.onerror = error;
7757
 
7758
			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
7759
			if (!tinymce.isOpera) {
7760
				elm.onreadystatechange = function() {
7761
					var state = elm.readyState;
7762
 
7763
					// Loaded state is passed on IE 6 however there
7764
					// are known issues with this method but we can't use
7765
					// XHR in a cross domain loading
7766
					if (state == 'complete' || state == 'loaded')
7767
						done();
7768
				};
7769
			}
7770
 
7771
			// Most browsers support this feature so we report errors
7772
			// for those at least to help users track their missing plugins etc
7773
			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
7774
			/*elm.onerror = function() {
7775
				alert('Failed to load: ' + url);
7776
			};*/
7777
 
7778
			// Add script to document
7779
			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
7780
		};
7781
 
7782
		this.isDone = function(url) {
7783
			return states[url] == LOADED;
7784
		};
7785
 
7786
		this.markDone = function(url) {
7787
			states[url] = LOADED;
7788
		};
7789
 
7790
		this.add = this.load = function(url, callback, scope) {
7791
			var item, state = states[url];
7792
 
7793
			// Add url to load queue
7794
			if (state == undefined) {
7795
				queue.push(url);
7796
				states[url] = QUEUED;
7797
			}
7798
 
7799
			if (callback) {
7800
				// Store away callback for later execution
7801
				if (!scriptLoadedCallbacks[url])
7802
					scriptLoadedCallbacks[url] = [];
7803
 
7804
				scriptLoadedCallbacks[url].push({
7805
					func : callback,
7806
					scope : scope || this
7807
				});
7808
			}
7809
		};
7810
 
7811
		this.loadQueue = function(callback, scope) {
7812
			this.loadScripts(queue, callback, scope);
7813
		};
7814
 
7815
		this.loadScripts = function(scripts, callback, scope) {
7816
			var loadScripts;
7817
 
7818
			function execScriptLoadedCallbacks(url) {
7819
				// Execute URL callback functions
7820
				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
7821
					callback.func.call(callback.scope);
7822
				});
7823
 
7824
				scriptLoadedCallbacks[url] = undefined;
7825
			};
7826
 
7827
			queueLoadedCallbacks.push({
7828
				func : callback,
7829
				scope : scope || this
7830
			});
7831
 
7832
			loadScripts = function() {
7833
				var loadingScripts = tinymce.grep(scripts);
7834
 
7835
				// Current scripts has been handled
7836
				scripts.length = 0;
7837
 
7838
				// Load scripts that needs to be loaded
7839
				tinymce.each(loadingScripts, function(url) {
7840
					// Script is already loaded then execute script callbacks directly
7841
					if (states[url] == LOADED) {
7842
						execScriptLoadedCallbacks(url);
7843
						return;
7844
					}
7845
 
7846
					// Is script not loading then start loading it
7847
					if (states[url] != LOADING) {
7848
						states[url] = LOADING;
7849
						loading++;
7850
 
7851
						loadScript(url, function() {
7852
							states[url] = LOADED;
7853
							loading--;
7854
 
7855
							execScriptLoadedCallbacks(url);
7856
 
7857
							// Load more scripts if they where added by the recently loaded script
7858
							loadScripts();
7859
						});
7860
					}
7861
				});
7862
 
7863
				// No scripts are currently loading then execute all pending queue loaded callbacks
7864
				if (!loading) {
7865
					tinymce.each(queueLoadedCallbacks, function(callback) {
7866
						callback.func.call(callback.scope);
7867
					});
7868
 
7869
					queueLoadedCallbacks.length = 0;
7870
				}
7871
			};
7872
 
7873
			loadScripts();
7874
		};
7875
	};
7876
 
7877
	// Global script loader
7878
	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
7879
})(tinymce);
7880
 
7881
tinymce.dom.TreeWalker = function(start_node, root_node) {
7882
	var node = start_node;
7883
 
7884
	function findSibling(node, start_name, sibling_name, shallow) {
7885
		var sibling, parent;
7886
 
7887
		if (node) {
7888
			// Walk into nodes if it has a start
7889
			if (!shallow && node[start_name])
7890
				return node[start_name];
7891
 
7892
			// Return the sibling if it has one
7893
			if (node != root_node) {
7894
				sibling = node[sibling_name];
7895
				if (sibling)
7896
					return sibling;
7897
 
7898
				// Walk up the parents to look for siblings
7899
				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
7900
					sibling = parent[sibling_name];
7901
					if (sibling)
7902
						return sibling;
7903
				}
7904
			}
7905
		}
7906
	};
7907
 
7908
	this.current = function() {
7909
		return node;
7910
	};
7911
 
7912
	this.next = function(shallow) {
7913
		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
7914
	};
7915
 
7916
	this.prev = function(shallow) {
7917
		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
7918
	};
7919
};
7920
 
7921
(function(tinymce) {
7922
	tinymce.dom.RangeUtils = function(dom) {
7923
		var INVISIBLE_CHAR = '\uFEFF';
7924
 
7925
		this.walk = function(rng, callback) {
7926
			var startContainer = rng.startContainer,
7927
				startOffset = rng.startOffset,
7928
				endContainer = rng.endContainer,
7929
				endOffset = rng.endOffset,
7930
				ancestor, startPoint,
7931
				endPoint, node, parent, siblings, nodes;
7932
 
7933
			// Handle table cell selection the table plugin enables
7934
			// you to fake select table cells and perform formatting actions on them
7935
			nodes = dom.select('td.mceSelected,th.mceSelected');
7936
			if (nodes.length > 0) {
7937
				tinymce.each(nodes, function(node) {
7938
					callback([node]);
7939
				});
7940
 
7941
				return;
7942
			}
7943
 
7944
			function collectSiblings(node, name, end_node) {
7945
				var siblings = [];
7946
 
7947
				for (; node && node != end_node; node = node[name])
7948
					siblings.push(node);
7949
 
7950
				return siblings;
7951
			};
7952
 
7953
			function findEndPoint(node, root) {
7954
				do {
7955
					if (node.parentNode == root)
7956
						return node;
7957
 
7958
					node = node.parentNode;
7959
				} while(node);
7960
			};
7961
 
7962
			function walkBoundary(start_node, end_node, next) {
7963
				var siblingName = next ? 'nextSibling' : 'previousSibling';
7964
 
7965
				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
7966
					parent = node.parentNode;
7967
					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
7968
 
7969
					if (siblings.length) {
7970
						if (!next)
7971
							siblings.reverse();
7972
 
7973
						callback(siblings);
7974
					}
7975
				}
7976
			};
7977
 
7978
			// If index based start position then resolve it
7979
			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
7980
				startContainer = startContainer.childNodes[startOffset];
7981
 
7982
			// If index based end position then resolve it
7983
			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
7984
				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
7985
 
7986
			// Find common ancestor and end points
7987
			ancestor = dom.findCommonAncestor(startContainer, endContainer);
7988
 
7989
			// Same container
7990
			if (startContainer == endContainer)
7991
				return callback([startContainer]);
7992
 
7993
			// Process left side
7994
			for (node = startContainer; node; node = node.parentNode) {
7995
				if (node == endContainer)
7996
					return walkBoundary(startContainer, ancestor, true);
7997
 
7998
				if (node == ancestor)
7999
					break;
8000
			}
8001
 
8002
			// Process right side
8003
			for (node = endContainer; node; node = node.parentNode) {
8004
				if (node == startContainer)
8005
					return walkBoundary(endContainer, ancestor);
8006
 
8007
				if (node == ancestor)
8008
					break;
8009
			}
8010
 
8011
			// Find start/end point
8012
			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
8013
			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
8014
 
8015
			// Walk left leaf
8016
			walkBoundary(startContainer, startPoint, true);
8017
 
8018
			// Walk the middle from start to end point
8019
			siblings = collectSiblings(
8020
				startPoint == startContainer ? startPoint : startPoint.nextSibling,
8021
				'nextSibling',
8022
				endPoint == endContainer ? endPoint.nextSibling : endPoint
8023
			);
8024
 
8025
			if (siblings.length)
8026
				callback(siblings);
8027
 
8028
			// Walk right leaf
8029
			walkBoundary(endContainer, endPoint);
8030
		};
8031
 
8032
		/*		this.split = function(rng) {
8033
			var startContainer = rng.startContainer,
8034
				startOffset = rng.startOffset,
8035
				endContainer = rng.endContainer,
8036
				endOffset = rng.endOffset;
8037
 
8038
			function splitText(node, offset) {
8039
				if (offset == node.nodeValue.length)
8040
					node.appendData(INVISIBLE_CHAR);
8041
 
8042
				node = node.splitText(offset);
8043
 
8044
				if (node.nodeValue === INVISIBLE_CHAR)
8045
					node.nodeValue = '';
8046
 
8047
				return node;
8048
			};
8049
 
8050
			// Handle single text node
8051
			if (startContainer == endContainer) {
8052
				if (startContainer.nodeType == 3) {
8053
					if (startOffset != 0)
8054
						startContainer = endContainer = splitText(startContainer, startOffset);
8055
 
8056
					if (endOffset - startOffset != startContainer.nodeValue.length)
8057
						splitText(startContainer, endOffset - startOffset);
8058
				}
8059
			} else {
8060
				// Split startContainer text node if needed
8061
				if (startContainer.nodeType == 3 && startOffset != 0) {
8062
					startContainer = splitText(startContainer, startOffset);
8063
					startOffset = 0;
8064
				}
8065
 
8066
				// Split endContainer text node if needed
8067
				if (endContainer.nodeType == 3 && endOffset != endContainer.nodeValue.length) {
8068
					endContainer = splitText(endContainer, endOffset).previousSibling;
8069
					endOffset = endContainer.nodeValue.length;
8070
				}
8071
			}
8072
 
8073
			return {
8074
				startContainer : startContainer,
8075
				startOffset : startOffset,
8076
				endContainer : endContainer,
8077
				endOffset : endOffset
8078
			};
8079
		};
8080
*/
8081
	};
8082
 
8083
	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
8084
		if (rng1 && rng2) {
8085
			// Compare native IE ranges
8086
			if (rng1.item || rng1.duplicate) {
8087
				// Both are control ranges and the selected element matches
8088
				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
8089
					return true;
8090
 
8091
				// Both are text ranges and the range matches
8092
				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
8093
					return true;
8094
			} else {
8095
				// Compare w3c ranges
8096
				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
8097
			}
8098
		}
8099
 
8100
		return false;
8101
	};
8102
})(tinymce);
8103
 
8104
(function(tinymce) {
8105
	var Event = tinymce.dom.Event, each = tinymce.each;
8106
 
8107
	tinymce.create('tinymce.ui.KeyboardNavigation', {
8108
		KeyboardNavigation: function(settings, dom) {
8109
			var t = this, root = settings.root, items = settings.items,
8110
					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
8111
					excludeFromTabOrder = settings.excludeFromTabOrder,
8112
					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
8113
 
8114
			dom = dom || tinymce.DOM;
8115
 
8116
			itemFocussed = function(evt) {
8117
				focussedId = evt.target.id;
8118
			};
8119
 
8120
			itemBlurred = function(evt) {
8121
				dom.setAttrib(evt.target.id, 'tabindex', '-1');
8122
			};
8123
 
8124
			rootFocussed = function(evt) {
8125
				var item = dom.get(focussedId);
8126
				dom.setAttrib(item, 'tabindex', '0');
8127
				item.focus();
8128
			};
8129
 
8130
			t.focus = function() {
8131
				dom.get(focussedId).focus();
8132
			};
8133
 
8134
			t.destroy = function() {
8135
				each(items, function(item) {
8136
					dom.unbind(dom.get(item.id), 'focus', itemFocussed);
8137
					dom.unbind(dom.get(item.id), 'blur', itemBlurred);
8138
				});
8139
 
8140
				dom.unbind(dom.get(root), 'focus', rootFocussed);
8141
				dom.unbind(dom.get(root), 'keydown', rootKeydown);
8142
 
8143
				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
8144
				t.destroy = function() {};
8145
			};
8146
 
8147
			t.moveFocus = function(dir, evt) {
8148
				var idx = -1, controls = t.controls, newFocus;
8149
 
8150
				if (!focussedId)
8151
					return;
8152
 
8153
				each(items, function(item, index) {
8154
					if (item.id === focussedId) {
8155
						idx = index;
8156
						return false;
8157
					}
8158
				});
8159
 
8160
				idx += dir;
8161
				if (idx < 0) {
8162
					idx = items.length - 1;
8163
				} else if (idx >= items.length) {
8164
					idx = 0;
8165
				}
8166
 
8167
				newFocus = items[idx];
8168
				dom.setAttrib(focussedId, 'tabindex', '-1');
8169
				dom.setAttrib(newFocus.id, 'tabindex', '0');
8170
				dom.get(newFocus.id).focus();
8171
 
8172
				if (settings.actOnFocus) {
8173
					settings.onAction(newFocus.id);
8174
				}
8175
 
8176
				if (evt)
8177
					Event.cancel(evt);
8178
			};
8179
 
8180
			rootKeydown = function(evt) {
8181
				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;
8182
 
8183
				switch (evt.keyCode) {
8184
					case DOM_VK_LEFT:
8185
						if (enableLeftRight) t.moveFocus(-1);
8186
						break;
8187
 
8188
					case DOM_VK_RIGHT:
8189
						if (enableLeftRight) t.moveFocus(1);
8190
						break;
8191
 
8192
					case DOM_VK_UP:
8193
						if (enableUpDown) t.moveFocus(-1);
8194
						break;
8195
 
8196
					case DOM_VK_DOWN:
8197
						if (enableUpDown) t.moveFocus(1);
8198
						break;
8199
 
8200
					case DOM_VK_ESCAPE:
8201
						if (settings.onCancel) {
8202
							settings.onCancel();
8203
							Event.cancel(evt);
8204
						}
8205
						break;
8206
 
8207
					case DOM_VK_ENTER:
8208
					case DOM_VK_RETURN:
8209
					case DOM_VK_SPACE:
8210
						if (settings.onAction) {
8211
							settings.onAction(focussedId);
8212
							Event.cancel(evt);
8213
						}
8214
						break;
8215
				}
8216
			};
8217
 
8218
			// Set up state and listeners for each item.
8219
			each(items, function(item, idx) {
8220
				var tabindex;
8221
 
8222
				if (!item.id) {
8223
					item.id = dom.uniqueId('_mce_item_');
8224
				}
8225
 
8226
				if (excludeFromTabOrder) {
8227
					dom.bind(item.id, 'blur', itemBlurred);
8228
					tabindex = '-1';
8229
				} else {
8230
					tabindex = (idx === 0 ? '0' : '-1');
8231
				}
8232
 
8233
				dom.setAttrib(item.id, 'tabindex', tabindex);
8234
				dom.bind(dom.get(item.id), 'focus', itemFocussed);
8235
			});
8236
 
8237
			// Setup initial state for root element.
8238
			if (items[0]){
8239
				focussedId = items[0].id;
8240
			}
8241
 
8242
			dom.setAttrib(root, 'tabindex', '-1');
8243
 
8244
			// Setup listeners for root element.
8245
			dom.bind(dom.get(root), 'focus', rootFocussed);
8246
			dom.bind(dom.get(root), 'keydown', rootKeydown);
8247
		}
8248
	});
8249
})(tinymce);
8250
 
8251
(function(tinymce) {
8252
	// Shorten class names
8253
	var DOM = tinymce.DOM, is = tinymce.is;
8254
 
8255
	tinymce.create('tinymce.ui.Control', {
8256
		Control : function(id, s, editor) {
8257
			this.id = id;
8258
			this.settings = s = s || {};
8259
			this.rendered = false;
8260
			this.onRender = new tinymce.util.Dispatcher(this);
8261
			this.classPrefix = '';
8262
			this.scope = s.scope || this;
8263
			this.disabled = 0;
8264
			this.active = 0;
8265
			this.editor = editor;
8266
		},
8267
 
8268
		setAriaProperty : function(property, value) {
8269
			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
8270
			if (element) {
8271
				DOM.setAttrib(element, 'aria-' + property, !!value);
8272
			}
8273
		},
8274
 
8275
		focus : function() {
8276
			DOM.get(this.id).focus();
8277
		},
8278
 
8279
		setDisabled : function(s) {
8280
			if (s != this.disabled) {
8281
				this.setAriaProperty('disabled', s);
8282
 
8283
				this.setState('Disabled', s);
8284
				this.setState('Enabled', !s);
8285
				this.disabled = s;
8286
			}
8287
		},
8288
 
8289
		isDisabled : function() {
8290
			return this.disabled;
8291
		},
8292
 
8293
		setActive : function(s) {
8294
			if (s != this.active) {
8295
				this.setState('Active', s);
8296
				this.active = s;
8297
				this.setAriaProperty('pressed', s);
8298
			}
8299
		},
8300
 
8301
		isActive : function() {
8302
			return this.active;
8303
		},
8304
 
8305
		setState : function(c, s) {
8306
			var n = DOM.get(this.id);
8307
 
8308
			c = this.classPrefix + c;
8309
 
8310
			if (s)
8311
				DOM.addClass(n, c);
8312
			else
8313
				DOM.removeClass(n, c);
8314
		},
8315
 
8316
		isRendered : function() {
8317
			return this.rendered;
8318
		},
8319
 
8320
		renderHTML : function() {
8321
		},
8322
 
8323
		renderTo : function(n) {
8324
			DOM.setHTML(n, this.renderHTML());
8325
		},
8326
 
8327
		postRender : function() {
8328
			var t = this, b;
8329
 
8330
			// Set pending states
8331
			if (is(t.disabled)) {
8332
				b = t.disabled;
8333
				t.disabled = -1;
8334
				t.setDisabled(b);
8335
			}
8336
 
8337
			if (is(t.active)) {
8338
				b = t.active;
8339
				t.active = -1;
8340
				t.setActive(b);
8341
			}
8342
		},
8343
 
8344
		remove : function() {
8345
			DOM.remove(this.id);
8346
			this.destroy();
8347
		},
8348
 
8349
		destroy : function() {
8350
			tinymce.dom.Event.clear(this.id);
8351
		}
8352
	});
8353
})(tinymce);
8354
tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
8355
	Container : function(id, s, editor) {
8356
		this.parent(id, s, editor);
8357
 
8358
		this.controls = [];
8359
 
8360
		this.lookup = {};
8361
	},
8362
 
8363
	add : function(c) {
8364
		this.lookup[c.id] = c;
8365
		this.controls.push(c);
8366
 
8367
		return c;
8368
	},
8369
 
8370
	get : function(n) {
8371
		return this.lookup[n];
8372
	}
8373
});
8374
 
8375
 
8376
tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
8377
	Separator : function(id, s) {
8378
		this.parent(id, s);
8379
		this.classPrefix = 'mceSeparator';
8380
		this.setDisabled(true);
8381
	},
8382
 
8383
	renderHTML : function() {
8384
		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
8385
	}
8386
});
8387
 
8388
(function(tinymce) {
8389
	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8390
 
8391
	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
8392
		MenuItem : function(id, s) {
8393
			this.parent(id, s);
8394
			this.classPrefix = 'mceMenuItem';
8395
		},
8396
 
8397
		setSelected : function(s) {
8398
			this.setState('Selected', s);
8399
			this.setAriaProperty('checked', !!s);
8400
			this.selected = s;
8401
		},
8402
 
8403
		isSelected : function() {
8404
			return this.selected;
8405
		},
8406
 
8407
		postRender : function() {
8408
			var t = this;
8409
 
8410
			t.parent();
8411
 
8412
			// Set pending state
8413
			if (is(t.selected))
8414
				t.setSelected(t.selected);
8415
		}
8416
	});
8417
})(tinymce);
8418
 
8419
(function(tinymce) {
8420
	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
8421
 
8422
	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
8423
		Menu : function(id, s) {
8424
			var t = this;
8425
 
8426
			t.parent(id, s);
8427
			t.items = {};
8428
			t.collapsed = false;
8429
			t.menuCount = 0;
8430
			t.onAddItem = new tinymce.util.Dispatcher(this);
8431
		},
8432
 
8433
		expand : function(d) {
8434
			var t = this;
8435
 
8436
			if (d) {
8437
				walk(t, function(o) {
8438
					if (o.expand)
8439
						o.expand();
8440
				}, 'items', t);
8441
			}
8442
 
8443
			t.collapsed = false;
8444
		},
8445
 
8446
		collapse : function(d) {
8447
			var t = this;
8448
 
8449
			if (d) {
8450
				walk(t, function(o) {
8451
					if (o.collapse)
8452
						o.collapse();
8453
				}, 'items', t);
8454
			}
8455
 
8456
			t.collapsed = true;
8457
		},
8458
 
8459
		isCollapsed : function() {
8460
			return this.collapsed;
8461
		},
8462
 
8463
		add : function(o) {
8464
			if (!o.settings)
8465
				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
8466
 
8467
			this.onAddItem.dispatch(this, o);
8468
 
8469
			return this.items[o.id] = o;
8470
		},
8471
 
8472
		addSeparator : function() {
8473
			return this.add({separator : true});
8474
		},
8475
 
8476
		addMenu : function(o) {
8477
			if (!o.collapse)
8478
				o = this.createMenu(o);
8479
 
8480
			this.menuCount++;
8481
 
8482
			return this.add(o);
8483
		},
8484
 
8485
		hasMenus : function() {
8486
			return this.menuCount !== 0;
8487
		},
8488
 
8489
		remove : function(o) {
8490
			delete this.items[o.id];
8491
		},
8492
 
8493
		removeAll : function() {
8494
			var t = this;
8495
 
8496
			walk(t, function(o) {
8497
				if (o.removeAll)
8498
					o.removeAll();
8499
				else
8500
					o.remove();
8501
 
8502
				o.destroy();
8503
			}, 'items', t);
8504
 
8505
			t.items = {};
8506
		},
8507
 
8508
		createMenu : function(o) {
8509
			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
8510
 
8511
			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
8512
 
8513
			return m;
8514
		}
8515
	});
8516
})(tinymce);
8517
(function(tinymce) {
8518
	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
8519
 
8520
	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
8521
		DropMenu : function(id, s) {
8522
			s = s || {};
8523
			s.container = s.container || DOM.doc.body;
8524
			s.offset_x = s.offset_x || 0;
8525
			s.offset_y = s.offset_y || 0;
8526
			s.vp_offset_x = s.vp_offset_x || 0;
8527
			s.vp_offset_y = s.vp_offset_y || 0;
8528
 
8529
			if (is(s.icons) && !s.icons)
8530
				s['class'] += ' mceNoIcons';
8531
 
8532
			this.parent(id, s);
8533
			this.onShowMenu = new tinymce.util.Dispatcher(this);
8534
			this.onHideMenu = new tinymce.util.Dispatcher(this);
8535
			this.classPrefix = 'mceMenu';
8536
		},
8537
 
8538
		createMenu : function(s) {
8539
			var t = this, cs = t.settings, m;
8540
 
8541
			s.container = s.container || cs.container;
8542
			s.parent = t;
8543
			s.constrain = s.constrain || cs.constrain;
8544
			s['class'] = s['class'] || cs['class'];
8545
			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
8546
			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
8547
			s.keyboard_focus = cs.keyboard_focus;
8548
			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
8549
 
8550
			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
8551
 
8552
			return m;
8553
		},
8554
 
8555
		focus : function() {
8556
			var t = this;
8557
			if (t.keyboardNav) {
8558
				t.keyboardNav.focus();
8559
			}
8560
		},
8561
 
8562
		update : function() {
8563
			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
8564
 
8565
			tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
8566
			th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
8567
 
8568
			if (!DOM.boxModel)
8569
				t.element.setStyles({width : tw + 2, height : th + 2});
8570
			else
8571
				t.element.setStyles({width : tw, height : th});
8572
 
8573
			if (s.max_width)
8574
				DOM.setStyle(co, 'width', tw);
8575
 
8576
			if (s.max_height) {
8577
				DOM.setStyle(co, 'height', th);
8578
 
8579
				if (tb.clientHeight < s.max_height)
8580
					DOM.setStyle(co, 'overflow', 'hidden');
8581
			}
8582
		},
8583
 
8584
		showMenu : function(x, y, px) {
8585
			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
8586
 
8587
			t.collapse(1);
8588
 
8589
			if (t.isMenuVisible)
8590
				return;
8591
 
8592
			if (!t.rendered) {
8593
				co = DOM.add(t.settings.container, t.renderNode());
8594
 
8595
				each(t.items, function(o) {
8596
					o.postRender();
8597
				});
8598
 
8599
				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8600
			} else
8601
				co = DOM.get('menu_' + t.id);
8602
 
8603
			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
8604
			if (!tinymce.isOpera)
8605
				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
8606
 
8607
			DOM.show(co);
8608
			t.update();
8609
 
8610
			x += s.offset_x || 0;
8611
			y += s.offset_y || 0;
8612
			vp.w -= 4;
8613
			vp.h -= 4;
8614
 
8615
			// Move inside viewport if not submenu
8616
			if (s.constrain) {
8617
				w = co.clientWidth - ot;
8618
				h = co.clientHeight - ot;
8619
				mx = vp.x + vp.w;
8620
				my = vp.y + vp.h;
8621
 
8622
				if ((x + s.vp_offset_x + w) > mx)
8623
					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
8624
 
8625
				if ((y + s.vp_offset_y + h) > my)
8626
					y = Math.max(0, (my - s.vp_offset_y) - h);
8627
			}
8628
 
8629
			DOM.setStyles(co, {left : x , top : y});
8630
			t.element.update();
8631
 
8632
			t.isMenuVisible = 1;
8633
			t.mouseClickFunc = Event.add(co, 'click', function(e) {
8634
				var m;
8635
 
8636
				e = e.target;
8637
 
8638
				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
8639
					m = t.items[e.id];
8640
 
8641
					if (m.isDisabled())
8642
						return;
8643
 
8644
					dm = t;
8645
 
8646
					while (dm) {
8647
						if (dm.hideMenu)
8648
							dm.hideMenu();
8649
 
8650
						dm = dm.settings.parent;
8651
					}
8652
 
8653
					if (m.settings.onclick)
8654
						m.settings.onclick(e);
8655
 
8656
					return Event.cancel(e); // Cancel to fix onbeforeunload problem
8657
				}
8658
			});
8659
 
8660
			if (t.hasMenus()) {
8661
				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
8662
					var m, r, mi;
8663
 
8664
					e = e.target;
8665
					if (e && (e = DOM.getParent(e, 'tr'))) {
8666
						m = t.items[e.id];
8667
 
8668
						if (t.lastMenu)
8669
							t.lastMenu.collapse(1);
8670
 
8671
						if (m.isDisabled())
8672
							return;
8673
 
8674
						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
8675
							//p = DOM.getPos(s.container);
8676
							r = DOM.getRect(e);
8677
							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
8678
							t.lastMenu = m;
8679
							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
8680
						}
8681
					}
8682
				});
8683
			}
8684
 
8685
			Event.add(co, 'keydown', t._keyHandler, t);
8686
 
8687
			t.onShowMenu.dispatch(t);
8688
 
8689
			if (s.keyboard_focus) {
8690
				t._setupKeyboardNav();
8691
			}
8692
		},
8693
 
8694
		hideMenu : function(c) {
8695
			var t = this, co = DOM.get('menu_' + t.id), e;
8696
 
8697
			if (!t.isMenuVisible)
8698
				return;
8699
 
8700
			if (t.keyboardNav) t.keyboardNav.destroy();
8701
			Event.remove(co, 'mouseover', t.mouseOverFunc);
8702
			Event.remove(co, 'click', t.mouseClickFunc);
8703
			Event.remove(co, 'keydown', t._keyHandler);
8704
			DOM.hide(co);
8705
			t.isMenuVisible = 0;
8706
 
8707
			if (!c)
8708
				t.collapse(1);
8709
 
8710
			if (t.element)
8711
				t.element.hide();
8712
 
8713
			if (e = DOM.get(t.id))
8714
				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
8715
 
8716
			t.onHideMenu.dispatch(t);
8717
		},
8718
 
8719
		add : function(o) {
8720
			var t = this, co;
8721
 
8722
			o = t.parent(o);
8723
 
8724
			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
8725
				t._add(DOM.select('tbody', co)[0], o);
8726
 
8727
			return o;
8728
		},
8729
 
8730
		collapse : function(d) {
8731
			this.parent(d);
8732
			this.hideMenu(1);
8733
		},
8734
 
8735
		remove : function(o) {
8736
			DOM.remove(o.id);
8737
			this.destroy();
8738
 
8739
			return this.parent(o);
8740
		},
8741
 
8742
		destroy : function() {
8743
			var t = this, co = DOM.get('menu_' + t.id);
8744
 
8745
			if (t.keyboardNav) t.keyboardNav.destroy();
8746
			Event.remove(co, 'mouseover', t.mouseOverFunc);
8747
			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
8748
			Event.remove(co, 'click', t.mouseClickFunc);
8749
			Event.remove(co, 'keydown', t._keyHandler);
8750
 
8751
			if (t.element)
8752
				t.element.remove();
8753
 
8754
			DOM.remove(co);
8755
		},
8756
 
8757
		renderNode : function() {
8758
			var t = this, s = t.settings, n, tb, co, w;
8759
 
8760
			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'});
8761
			if (t.settings.parent) {
8762
				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
8763
			}
8764
			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
8765
			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
8766
 
8767
			if (s.menu_line)
8768
				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
8769
 
8770
//			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
8771
			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
8772
			tb = DOM.add(n, 'tbody');
8773
 
8774
			each(t.items, function(o) {
8775
				t._add(tb, o);
8776
			});
8777
 
8778
			t.rendered = true;
8779
 
8780
			return w;
8781
		},
8782
 
8783
		// Internal functions
8784
		_setupKeyboardNav : function(){
8785
			var contextMenu, menuItems, t=this;
8786
			contextMenu = DOM.select('#menu_' + t.id)[0];
8787
			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
8788
			menuItems.splice(0,0,contextMenu);
8789
			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
8790
				root: 'menu_' + t.id,
8791
				items: menuItems,
8792
				onCancel: function() {
8793
					t.hideMenu();
8794
				},
8795
				enableUpDown: true
8796
			});
8797
			contextMenu.focus();
8798
		},
8799
 
8800
		_keyHandler : function(evt) {
8801
			var t = this, e;
8802
			switch (evt.keyCode) {
8803
				case 37: // Left
8804
					if (t.settings.parent) {
8805
						t.hideMenu();
8806
						t.settings.parent.focus();
8807
						Event.cancel(evt);
8808
					}
8809
					break;
8810
				case 39: // Right
8811
					if (t.mouseOverFunc)
8812
						t.mouseOverFunc(evt);
8813
					break;
8814
			}
8815
		},
8816
 
8817
		_add : function(tb, o) {
8818
			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
8819
 
8820
			if (s.separator) {
8821
				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
8822
				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
8823
 
8824
				if (n = ro.previousSibling)
8825
					DOM.addClass(n, 'mceLast');
8826
 
8827
				return;
8828
			}
8829
 
8830
			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
8831
			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
8832
			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
8833
 
8834
			if (s.parent) {
8835
				DOM.setAttrib(a, 'aria-haspopup', 'true');
8836
				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
8837
			}
8838
 
8839
			DOM.addClass(it, s['class']);
8840
//			n = DOM.add(n, 'span', {'class' : 'item'});
8841
 
8842
			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
8843
 
8844
			if (s.icon_src)
8845
				DOM.add(ic, 'img', {src : s.icon_src});
8846
 
8847
			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
8848
 
8849
			if (o.settings.style)
8850
				DOM.setAttrib(n, 'style', o.settings.style);
8851
 
8852
			if (tb.childNodes.length == 1)
8853
				DOM.addClass(ro, 'mceFirst');
8854
 
8855
			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
8856
				DOM.addClass(ro, 'mceFirst');
8857
 
8858
			if (o.collapse)
8859
				DOM.addClass(ro, cp + 'ItemSub');
8860
 
8861
			if (n = ro.previousSibling)
8862
				DOM.removeClass(n, 'mceLast');
8863
 
8864
			DOM.addClass(ro, 'mceLast');
8865
		}
8866
	});
8867
})(tinymce);
8868
(function(tinymce) {
8869
	var DOM = tinymce.DOM;
8870
 
8871
	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
8872
		Button : function(id, s, ed) {
8873
			this.parent(id, s, ed);
8874
			this.classPrefix = 'mceButton';
8875
		},
8876
 
8877
		renderHTML : function() {
8878
			var cp = this.classPrefix, s = this.settings, h, l;
8879
 
8880
			l = DOM.encode(s.label || '');
8881
			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) + '">';
8882
			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
8883
				h += '<img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" />' + l;
8884
			else
8885
				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
8886
 
8887
			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>';
8888
			h += '</a>';
8889
			return h;
8890
		},
8891
 
8892
		postRender : function() {
8893
			var t = this, s = t.settings;
8894
 
8895
			tinymce.dom.Event.add(t.id, 'click', function(e) {
8896
				if (!t.isDisabled())
8897
					return s.onclick.call(s.scope, e);
8898
			});
8899
		}
8900
	});
8901
})(tinymce);
8902
 
8903
(function(tinymce) {
8904
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
8905
 
8906
	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
8907
		ListBox : function(id, s, ed) {
8908
			var t = this;
8909
 
8910
			t.parent(id, s, ed);
8911
 
8912
			t.items = [];
8913
 
8914
			t.onChange = new Dispatcher(t);
8915
 
8916
			t.onPostRender = new Dispatcher(t);
8917
 
8918
			t.onAdd = new Dispatcher(t);
8919
 
8920
			t.onRenderMenu = new tinymce.util.Dispatcher(this);
8921
 
8922
			t.classPrefix = 'mceListBox';
8923
		},
8924
 
8925
		select : function(va) {
8926
			var t = this, fv, f;
8927
 
8928
			if (va == undefined)
8929
				return t.selectByIndex(-1);
8930
 
8931
			// Is string or number make function selector
8932
			if (va && va.call)
8933
				f = va;
8934
			else {
8935
				f = function(v) {
8936
					return v == va;
8937
				};
8938
			}
8939
 
8940
			// Do we need to do something?
8941
			if (va != t.selectedValue) {
8942
				// Find item
8943
				each(t.items, function(o, i) {
8944
					if (f(o.value)) {
8945
						fv = 1;
8946
						t.selectByIndex(i);
8947
						return false;
8948
					}
8949
				});
8950
 
8951
				if (!fv)
8952
					t.selectByIndex(-1);
8953
			}
8954
		},
8955
 
8956
		selectByIndex : function(idx) {
8957
			var t = this, e, o, label;
8958
 
8959
			if (idx != t.selectedIndex) {
8960
				e = DOM.get(t.id + '_text');
8961
				label = DOM.get(t.id + '_voiceDesc');
8962
				o = t.items[idx];
8963
 
8964
				if (o) {
8965
					t.selectedValue = o.value;
8966
					t.selectedIndex = idx;
8967
					DOM.setHTML(e, DOM.encode(o.title));
8968
					DOM.setHTML(label, t.settings.title + " - " + o.title);
8969
					DOM.removeClass(e, 'mceTitle');
8970
					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
8971
				} else {
8972
					DOM.setHTML(e, DOM.encode(t.settings.title));
8973
					DOM.setHTML(label, DOM.encode(t.settings.title));
8974
					DOM.addClass(e, 'mceTitle');
8975
					t.selectedValue = t.selectedIndex = null;
8976
					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
8977
				}
8978
				e = 0;
8979
			}
8980
		},
8981
 
8982
		add : function(n, v, o) {
8983
			var t = this;
8984
 
8985
			o = o || {};
8986
			o = tinymce.extend(o, {
8987
				title : n,
8988
				value : v
8989
			});
8990
 
8991
			t.items.push(o);
8992
			t.onAdd.dispatch(t, o);
8993
		},
8994
 
8995
		getLength : function() {
8996
			return this.items.length;
8997
		},
8998
 
8999
		renderHTML : function() {
9000
			var h = '', t = this, s = t.settings, cp = t.classPrefix;
9001
 
9002
			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>';
9003
			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title);
9004
			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>';
9005
			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>';
9006
			h += '</tr></tbody></table></span>';
9007
 
9008
			return h;
9009
		},
9010
 
9011
		showMenu : function() {
9012
			var t = this, p2, e = DOM.get(this.id), m;
9013
 
9014
			if (t.isDisabled() || t.items.length == 0)
9015
				return;
9016
 
9017
			if (t.menu && t.menu.isMenuVisible)
9018
				return t.hideMenu();
9019
 
9020
			if (!t.isMenuRendered) {
9021
				t.renderMenu();
9022
				t.isMenuRendered = true;
9023
			}
9024
 
9025
			p2 = DOM.getPos(e);
9026
 
9027
			m = t.menu;
9028
			m.settings.offset_x = p2.x;
9029
			m.settings.offset_y = p2.y;
9030
			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
9031
 
9032
			// Select in menu
9033
			if (t.oldID)
9034
				m.items[t.oldID].setSelected(0);
9035
 
9036
			each(t.items, function(o) {
9037
				if (o.value === t.selectedValue) {
9038
					m.items[o.id].setSelected(1);
9039
					t.oldID = o.id;
9040
				}
9041
			});
9042
 
9043
			m.showMenu(0, e.clientHeight);
9044
 
9045
			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9046
			DOM.addClass(t.id, t.classPrefix + 'Selected');
9047
 
9048
			//DOM.get(t.id + '_text').focus();
9049
		},
9050
 
9051
		hideMenu : function(e) {
9052
			var t = this;
9053
 
9054
			if (t.menu && t.menu.isMenuVisible) {
9055
				DOM.removeClass(t.id, t.classPrefix + 'Selected');
9056
 
9057
				// Prevent double toogles by canceling the mouse click event to the button
9058
				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
9059
					return;
9060
 
9061
				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9062
					DOM.removeClass(t.id, t.classPrefix + 'Selected');
9063
					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9064
					t.menu.hideMenu();
9065
				}
9066
			}
9067
		},
9068
 
9069
		renderMenu : function() {
9070
			var t = this, m;
9071
 
9072
			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9073
				menu_line : 1,
9074
				'class' : t.classPrefix + 'Menu mceNoIcons',
9075
				max_width : 150,
9076
				max_height : 150
9077
			});
9078
 
9079
			m.onHideMenu.add(function() {
9080
				t.hideMenu();
9081
				t.focus();
9082
			});
9083
 
9084
			m.add({
9085
				title : t.settings.title,
9086
				'class' : 'mceMenuItemTitle',
9087
				onclick : function() {
9088
					if (t.settings.onselect('') !== false)
9089
						t.select(''); // Must be runned after
9090
				}
9091
			});
9092
 
9093
			each(t.items, function(o) {
9094
				// No value then treat it as a title
9095
				if (o.value === undefined) {
9096
					m.add({
9097
						title : o.title,
9098
						role : "option",
9099
						'class' : 'mceMenuItemTitle',
9100
						onclick : function() {
9101
							if (t.settings.onselect('') !== false)
9102
								t.select(''); // Must be runned after
9103
						}
9104
					});
9105
				} else {
9106
					o.id = DOM.uniqueId();
9107
					o.role= "option";
9108
					o.onclick = function() {
9109
						if (t.settings.onselect(o.value) !== false)
9110
							t.select(o.value); // Must be runned after
9111
					};
9112
 
9113
					m.add(o);
9114
				}
9115
			});
9116
 
9117
			t.onRenderMenu.dispatch(t, m);
9118
			t.menu = m;
9119
		},
9120
 
9121
		postRender : function() {
9122
			var t = this, cp = t.classPrefix;
9123
 
9124
			Event.add(t.id, 'click', t.showMenu, t);
9125
			Event.add(t.id, 'keydown', function(evt) {
9126
				if (evt.keyCode == 32) { // Space
9127
					t.showMenu(evt);
9128
					Event.cancel(evt);
9129
				}
9130
			});
9131
			Event.add(t.id, 'focus', function() {
9132
				if (!t._focused) {
9133
					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
9134
						if (e.keyCode == 40) {
9135
							t.showMenu();
9136
							Event.cancel(e);
9137
						}
9138
					});
9139
					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
9140
						var v;
9141
						if (e.keyCode == 13) {
9142
							// Fake select on enter
9143
							v = t.selectedValue;
9144
							t.selectedValue = null; // Needs to be null to fake change
9145
							Event.cancel(e);
9146
							t.settings.onselect(v);
9147
						}
9148
					});
9149
				}
9150
 
9151
				t._focused = 1;
9152
			});
9153
			Event.add(t.id, 'blur', function() {
9154
				Event.remove(t.id, 'keydown', t.keyDownHandler);
9155
				Event.remove(t.id, 'keypress', t.keyPressHandler);
9156
				t._focused = 0;
9157
			});
9158
 
9159
			// Old IE doesn't have hover on all elements
9160
			if (tinymce.isIE6 || !DOM.boxModel) {
9161
				Event.add(t.id, 'mouseover', function() {
9162
					if (!DOM.hasClass(t.id, cp + 'Disabled'))
9163
						DOM.addClass(t.id, cp + 'Hover');
9164
				});
9165
 
9166
				Event.add(t.id, 'mouseout', function() {
9167
					if (!DOM.hasClass(t.id, cp + 'Disabled'))
9168
						DOM.removeClass(t.id, cp + 'Hover');
9169
				});
9170
			}
9171
 
9172
			t.onPostRender.dispatch(t, DOM.get(t.id));
9173
		},
9174
 
9175
		destroy : function() {
9176
			this.parent();
9177
 
9178
			Event.clear(this.id + '_text');
9179
			Event.clear(this.id + '_open');
9180
		}
9181
	});
9182
})(tinymce);
9183
 
9184
(function(tinymce) {
9185
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
9186
 
9187
	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
9188
		NativeListBox : function(id, s) {
9189
			this.parent(id, s);
9190
			this.classPrefix = 'mceNativeListBox';
9191
		},
9192
 
9193
		setDisabled : function(s) {
9194
			DOM.get(this.id).disabled = s;
9195
			this.setAriaProperty('disabled', s);
9196
		},
9197
 
9198
		isDisabled : function() {
9199
			return DOM.get(this.id).disabled;
9200
		},
9201
 
9202
		select : function(va) {
9203
			var t = this, fv, f;
9204
 
9205
			if (va == undefined)
9206
				return t.selectByIndex(-1);
9207
 
9208
			// Is string or number make function selector
9209
			if (va && va.call)
9210
				f = va;
9211
			else {
9212
				f = function(v) {
9213
					return v == va;
9214
				};
9215
			}
9216
 
9217
			// Do we need to do something?
9218
			if (va != t.selectedValue) {
9219
				// Find item
9220
				each(t.items, function(o, i) {
9221
					if (f(o.value)) {
9222
						fv = 1;
9223
						t.selectByIndex(i);
9224
						return false;
9225
					}
9226
				});
9227
 
9228
				if (!fv)
9229
					t.selectByIndex(-1);
9230
			}
9231
		},
9232
 
9233
		selectByIndex : function(idx) {
9234
			DOM.get(this.id).selectedIndex = idx + 1;
9235
			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
9236
		},
9237
 
9238
		add : function(n, v, a) {
9239
			var o, t = this;
9240
 
9241
			a = a || {};
9242
			a.value = v;
9243
 
9244
			if (t.isRendered())
9245
				DOM.add(DOM.get(this.id), 'option', a, n);
9246
 
9247
			o = {
9248
				title : n,
9249
				value : v,
9250
				attribs : a
9251
			};
9252
 
9253
			t.items.push(o);
9254
			t.onAdd.dispatch(t, o);
9255
		},
9256
 
9257
		getLength : function() {
9258
			return this.items.length;
9259
		},
9260
 
9261
		renderHTML : function() {
9262
			var h, t = this;
9263
 
9264
			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
9265
 
9266
			each(t.items, function(it) {
9267
				h += DOM.createHTML('option', {value : it.value}, it.title);
9268
			});
9269
 
9270
			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
9271
			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
9272
			return h;
9273
		},
9274
 
9275
		postRender : function() {
9276
			var t = this, ch, changeListenerAdded = true;
9277
 
9278
			t.rendered = true;
9279
 
9280
			function onChange(e) {
9281
				var v = t.items[e.target.selectedIndex - 1];
9282
 
9283
				if (v && (v = v.value)) {
9284
					t.onChange.dispatch(t, v);
9285
 
9286
					if (t.settings.onselect)
9287
						t.settings.onselect(v);
9288
				}
9289
			};
9290
 
9291
			Event.add(t.id, 'change', onChange);
9292
 
9293
			// Accessibility keyhandler
9294
			Event.add(t.id, 'keydown', function(e) {
9295
				var bf;
9296
 
9297
				Event.remove(t.id, 'change', ch);
9298
				changeListenerAdded = false;
9299
 
9300
				bf = Event.add(t.id, 'blur', function() {
9301
					if (changeListenerAdded) return;
9302
					changeListenerAdded = true;
9303
					Event.add(t.id, 'change', onChange);
9304
					Event.remove(t.id, 'blur', bf);
9305
				});
9306
 
9307
				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
9308
				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
9309
					return Event.prevent(e);
9310
				}
9311
 
9312
				if (e.keyCode == 13 || e.keyCode == 32) {
9313
					onChange(e);
9314
					return Event.cancel(e);
9315
				}
9316
			});
9317
 
9318
			t.onPostRender.dispatch(t, DOM.get(t.id));
9319
		}
9320
	});
9321
})(tinymce);
9322
 
9323
(function(tinymce) {
9324
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9325
 
9326
	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
9327
		MenuButton : function(id, s, ed) {
9328
			this.parent(id, s, ed);
9329
 
9330
			this.onRenderMenu = new tinymce.util.Dispatcher(this);
9331
 
9332
			s.menu_container = s.menu_container || DOM.doc.body;
9333
		},
9334
 
9335
		showMenu : function() {
9336
			var t = this, p1, p2, e = DOM.get(t.id), m;
9337
 
9338
			if (t.isDisabled())
9339
				return;
9340
 
9341
			if (!t.isMenuRendered) {
9342
				t.renderMenu();
9343
				t.isMenuRendered = true;
9344
			}
9345
 
9346
			if (t.isMenuVisible)
9347
				return t.hideMenu();
9348
 
9349
			p1 = DOM.getPos(t.settings.menu_container);
9350
			p2 = DOM.getPos(e);
9351
 
9352
			m = t.menu;
9353
			m.settings.offset_x = p2.x;
9354
			m.settings.offset_y = p2.y;
9355
			m.settings.vp_offset_x = p2.x;
9356
			m.settings.vp_offset_y = p2.y;
9357
			m.settings.keyboard_focus = t._focused;
9358
			m.showMenu(0, e.clientHeight);
9359
 
9360
			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9361
			t.setState('Selected', 1);
9362
 
9363
			t.isMenuVisible = 1;
9364
		},
9365
 
9366
		renderMenu : function() {
9367
			var t = this, m;
9368
 
9369
			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
9370
				menu_line : 1,
9371
				'class' : this.classPrefix + 'Menu',
9372
				icons : t.settings.icons
9373
			});
9374
 
9375
			m.onHideMenu.add(function() {
9376
				t.hideMenu();
9377
				t.focus();
9378
			});
9379
 
9380
			t.onRenderMenu.dispatch(t, m);
9381
			t.menu = m;
9382
		},
9383
 
9384
		hideMenu : function(e) {
9385
			var t = this;
9386
 
9387
			// Prevent double toogles by canceling the mouse click event to the button
9388
			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
9389
				return;
9390
 
9391
			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
9392
				t.setState('Selected', 0);
9393
				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9394
				if (t.menu)
9395
					t.menu.hideMenu();
9396
			}
9397
 
9398
			t.isMenuVisible = 0;
9399
		},
9400
 
9401
		postRender : function() {
9402
			var t = this, s = t.settings;
9403
 
9404
			Event.add(t.id, 'click', function() {
9405
				if (!t.isDisabled()) {
9406
					if (s.onclick)
9407
						s.onclick(t.value);
9408
 
9409
					t.showMenu();
9410
				}
9411
			});
9412
		}
9413
	});
9414
})(tinymce);
9415
 
9416
(function(tinymce) {
9417
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
9418
 
9419
	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
9420
		SplitButton : function(id, s, ed) {
9421
			this.parent(id, s, ed);
9422
			this.classPrefix = 'mceSplitButton';
9423
		},
9424
 
9425
		renderHTML : function() {
9426
			var h, t = this, s = t.settings, h1;
9427
 
9428
			h = '<tbody><tr>';
9429
 
9430
			if (s.image)
9431
				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
9432
			else
9433
				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
9434
 
9435
			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
9436
			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>';
9437
 
9438
			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
9439
			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>';
9440
 
9441
			h += '</tr></tbody>';
9442
			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
9443
			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
9444
		},
9445
 
9446
		postRender : function() {
9447
			var t = this, s = t.settings, activate;
9448
 
9449
			if (s.onclick) {
9450
				activate = function(evt) {
9451
					if (!t.isDisabled()) {
9452
						s.onclick(t.value);
9453
						Event.cancel(evt);
9454
					}
9455
				};
9456
				Event.add(t.id + '_action', 'click', activate);
9457
				Event.add(t.id, ['click', 'keydown'], function(evt) {
9458
					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
9459
					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
9460
						activate();
9461
						Event.cancel(evt);
9462
					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
9463
						t.showMenu();
9464
						Event.cancel(evt);
9465
					}
9466
				});
9467
			}
9468
 
9469
			Event.add(t.id + '_open', 'click', function (evt) {
9470
				t.showMenu();
9471
				Event.cancel(evt);
9472
			});
9473
			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
9474
			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
9475
 
9476
			// Old IE doesn't have hover on all elements
9477
			if (tinymce.isIE6 || !DOM.boxModel) {
9478
				Event.add(t.id, 'mouseover', function() {
9479
					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9480
						DOM.addClass(t.id, 'mceSplitButtonHover');
9481
				});
9482
 
9483
				Event.add(t.id, 'mouseout', function() {
9484
					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
9485
						DOM.removeClass(t.id, 'mceSplitButtonHover');
9486
				});
9487
			}
9488
		},
9489
 
9490
		destroy : function() {
9491
			this.parent();
9492
 
9493
			Event.clear(this.id + '_action');
9494
			Event.clear(this.id + '_open');
9495
			Event.clear(this.id);
9496
		}
9497
	});
9498
})(tinymce);
9499
 
9500
(function(tinymce) {
9501
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
9502
 
9503
	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
9504
		ColorSplitButton : function(id, s, ed) {
9505
			var t = this;
9506
 
9507
			t.parent(id, s, ed);
9508
 
9509
			t.settings = s = tinymce.extend({
9510
				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',
9511
				grid_width : 8,
9512
				default_color : '#888888'
9513
			}, t.settings);
9514
 
9515
			t.onShowMenu = new tinymce.util.Dispatcher(t);
9516
 
9517
			t.onHideMenu = new tinymce.util.Dispatcher(t);
9518
 
9519
			t.value = s.default_color;
9520
		},
9521
 
9522
		showMenu : function() {
9523
			var t = this, r, p, e, p2;
9524
 
9525
			if (t.isDisabled())
9526
				return;
9527
 
9528
			if (!t.isMenuRendered) {
9529
				t.renderMenu();
9530
				t.isMenuRendered = true;
9531
			}
9532
 
9533
			if (t.isMenuVisible)
9534
				return t.hideMenu();
9535
 
9536
			e = DOM.get(t.id);
9537
			DOM.show(t.id + '_menu');
9538
			DOM.addClass(e, 'mceSplitButtonSelected');
9539
			p2 = DOM.getPos(e);
9540
			DOM.setStyles(t.id + '_menu', {
9541
				left : p2.x,
9542
				top : p2.y + e.clientHeight,
9543
				zIndex : 200000
9544
			});
9545
			e = 0;
9546
 
9547
			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
9548
			t.onShowMenu.dispatch(t);
9549
 
9550
			if (t._focused) {
9551
				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
9552
					if (e.keyCode == 27)
9553
						t.hideMenu();
9554
				});
9555
 
9556
				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
9557
			}
9558
 
9559
			t.isMenuVisible = 1;
9560
		},
9561
 
9562
		hideMenu : function(e) {
9563
			var t = this;
9564
 
9565
			if (t.isMenuVisible) {
9566
				// Prevent double toogles by canceling the mouse click event to the button
9567
				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
9568
					return;
9569
 
9570
				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
9571
					DOM.removeClass(t.id, 'mceSplitButtonSelected');
9572
					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
9573
					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
9574
					DOM.hide(t.id + '_menu');
9575
				}
9576
 
9577
				t.isMenuVisible = 0;
9578
				t.onHideMenu.dispatch();
9579
			}
9580
		},
9581
 
9582
		renderMenu : function() {
9583
			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
9584
 
9585
			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;'});
9586
			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
9587
			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
9588
 
9589
			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
9590
			tb = DOM.add(n, 'tbody');
9591
 
9592
			// Generate color grid
9593
			i = 0;
9594
			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
9595
				c = c.replace(/^#/, '');
9596
 
9597
				if (!i--) {
9598
					tr = DOM.add(tb, 'tr');
9599
					i = s.grid_width - 1;
9600
				}
9601
 
9602
				n = DOM.add(tr, 'td');
9603
				n = DOM.add(n, 'a', {
9604
					role : 'option',
9605
					href : 'javascript:;',
9606
					style : {
9607
						backgroundColor : '#' + c
9608
					},
9609
					'title': t.editor.getLang('colors.' + c, c),
9610
					'data-mce-color' : '#' + c
9611
				});
9612
 
9613
				if (t.editor.forcedHighContrastMode) {
9614
					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
9615
					if (n.getContext && (context = n.getContext("2d"))) {
9616
						context.fillStyle = '#' + c;
9617
						context.fillRect(0, 0, 16, 16);
9618
					} else {
9619
						// No point leaving a canvas element around if it's not supported for drawing on anyway.
9620
						DOM.remove(n);
9621
					}
9622
				}
9623
			});
9624
 
9625
			if (s.more_colors_func) {
9626
				n = DOM.add(tb, 'tr');
9627
				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
9628
				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
9629
 
9630
				Event.add(n, 'click', function(e) {
9631
					s.more_colors_func.call(s.more_colors_scope || this);
9632
					return Event.cancel(e); // Cancel to fix onbeforeunload problem
9633
				});
9634
			}
9635
 
9636
			DOM.addClass(m, 'mceColorSplitMenu');
9637
 
9638
			new tinymce.ui.KeyboardNavigation({
9639
				root: t.id + '_menu',
9640
				items: DOM.select('a', t.id + '_menu'),
9641
				onCancel: function() {
9642
					t.hideMenu();
9643
					t.focus();
9644
				}
9645
			});
9646
 
9647
			// Prevent IE from scrolling and hindering click to occur #4019
9648
			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
9649
 
9650
			Event.add(t.id + '_menu', 'click', function(e) {
9651
				var c;
9652
 
9653
				e = DOM.getParent(e.target, 'a', tb);
9654
 
9655
				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
9656
					t.setColor(c);
9657
 
9658
				return Event.cancel(e); // Prevent IE auto save warning
9659
			});
9660
 
9661
			return w;
9662
		},
9663
 
9664
		setColor : function(c) {
9665
			this.displayColor(c);
9666
			this.hideMenu();
9667
			this.settings.onselect(c);
9668
		},
9669
 
9670
		displayColor : function(c) {
9671
			var t = this;
9672
 
9673
			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
9674
 
9675
			t.value = c;
9676
		},
9677
 
9678
		postRender : function() {
9679
			var t = this, id = t.id;
9680
 
9681
			t.parent();
9682
			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
9683
			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
9684
		},
9685
 
9686
		destroy : function() {
9687
			this.parent();
9688
 
9689
			Event.clear(this.id + '_menu');
9690
			Event.clear(this.id + '_more');
9691
			DOM.remove(this.id + '_menu');
9692
		}
9693
	});
9694
})(tinymce);
9695
 
9696
(function(tinymce) {
9697
// Shorten class names
9698
var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
9699
tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
9700
	renderHTML : function() {
9701
		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
9702
 
9703
		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
9704
		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
9705
		h.push("<span role='application'>");
9706
		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
9707
		each(controls, function(toolbar) {
9708
			h.push(toolbar.renderHTML());
9709
		});
9710
		h.push("</span>");
9711
		h.push('</div>');
9712
 
9713
		return h.join('');
9714
	},
9715
 
9716
	focus : function() {
9717
		var t = this;
9718
		dom.get(t.id).focus();
9719
	},
9720
 
9721
	postRender : function() {
9722
		var t = this, items = [];
9723
 
9724
		each(t.controls, function(toolbar) {
9725
			each (toolbar.controls, function(control) {
9726
				if (control.id) {
9727
					items.push(control);
9728
				}
9729
			});
9730
		});
9731
 
9732
		t.keyNav = new tinymce.ui.KeyboardNavigation({
9733
			root: t.id,
9734
			items: items,
9735
			onCancel: function() {
9736
				//Move focus if webkit so that navigation back will read the item.
9737
				if (tinymce.isWebKit) {
9738
					dom.get(t.editor.id+"_ifr").focus();
9739
				}
9740
				t.editor.focus();
9741
			},
9742
			excludeFromTabOrder: !t.settings.tab_focus_toolbar
9743
		});
9744
	},
9745
 
9746
	destroy : function() {
9747
		var self = this;
9748
 
9749
		self.parent();
9750
		self.keyNav.destroy();
9751
		Event.clear(self.id);
9752
	}
9753
});
9754
})(tinymce);
9755
 
9756
(function(tinymce) {
9757
// Shorten class names
9758
var dom = tinymce.DOM, each = tinymce.each;
9759
tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
9760
	renderHTML : function() {
9761
		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
9762
 
9763
		cl = t.controls;
9764
		for (i=0; i<cl.length; i++) {
9765
			// Get current control, prev control, next control and if the control is a list box or not
9766
			co = cl[i];
9767
			pr = cl[i - 1];
9768
			nx = cl[i + 1];
9769
 
9770
			// Add toolbar start
9771
			if (i === 0) {
9772
				c = 'mceToolbarStart';
9773
 
9774
				if (co.Button)
9775
					c += ' mceToolbarStartButton';
9776
				else if (co.SplitButton)
9777
					c += ' mceToolbarStartSplitButton';
9778
				else if (co.ListBox)
9779
					c += ' mceToolbarStartListBox';
9780
 
9781
				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9782
			}
9783
 
9784
			// Add toolbar end before list box and after the previous button
9785
			// This is to fix the o2k7 editor skins
9786
			if (pr && co.ListBox) {
9787
				if (pr.Button || pr.SplitButton)
9788
					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
9789
			}
9790
 
9791
			// Render control HTML
9792
 
9793
			// IE 8 quick fix, needed to propertly generate a hit area for anchors
9794
			if (dom.stdMode)
9795
				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
9796
			else
9797
				h += '<td>' + co.renderHTML() + '</td>';
9798
 
9799
			// Add toolbar start after list box and before the next button
9800
			// This is to fix the o2k7 editor skins
9801
			if (nx && co.ListBox) {
9802
				if (nx.Button || nx.SplitButton)
9803
					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
9804
			}
9805
		}
9806
 
9807
		c = 'mceToolbarEnd';
9808
 
9809
		if (co.Button)
9810
			c += ' mceToolbarEndButton';
9811
		else if (co.SplitButton)
9812
			c += ' mceToolbarEndSplitButton';
9813
		else if (co.ListBox)
9814
			c += ' mceToolbarEndListBox';
9815
 
9816
		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
9817
 
9818
		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>');
9819
	}
9820
});
9821
})(tinymce);
9822
 
9823
(function(tinymce) {
9824
	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
9825
 
9826
	tinymce.create('tinymce.AddOnManager', {
9827
		AddOnManager : function() {
9828
			var self = this;
9829
 
9830
			self.items = [];
9831
			self.urls = {};
9832
			self.lookup = {};
9833
			self.onAdd = new Dispatcher(self);
9834
		},
9835
 
9836
		get : function(n) {
9837
			if (this.lookup[n]) {
9838
				return this.lookup[n].instance;
9839
			} else {
9840
				return undefined;
9841
			}
9842
		},
9843
 
9844
		dependencies : function(n) {
9845
			var result;
9846
			if (this.lookup[n]) {
9847
				result = this.lookup[n].dependencies;
9848
			}
9849
			return result || [];
9850
		},
9851
 
9852
		requireLangPack : function(n) {
9853
			var s = tinymce.settings;
9854
 
9855
			if (s && s.language && s.language_load !== false)
9856
				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
9857
		},
9858
 
9859
		add : function(id, o, dependencies) {
9860
			this.items.push(o);
9861
			this.lookup[id] = {instance:o, dependencies:dependencies};
9862
			this.onAdd.dispatch(this, id, o);
9863
 
9864
			return o;
9865
		},
9866
		createUrl: function(baseUrl, dep) {
9867
			if (typeof dep === "object") {
9868
				return dep
9869
			} else {
9870
				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
9871
			}
9872
		},
9873
 
9874
		addComponents: function(pluginName, scripts) {
9875
			var pluginUrl = this.urls[pluginName];
9876
			tinymce.each(scripts, function(script){
9877
				tinymce.ScriptLoader.add(pluginUrl+"/"+script);
9878
			});
9879
		},
9880
 
9881
		load : function(n, u, cb, s) {
9882
			var t = this, url = u;
9883
 
9884
			function loadDependencies() {
9885
				var dependencies = t.dependencies(n);
9886
				tinymce.each(dependencies, function(dep) {
9887
					var newUrl = t.createUrl(u, dep);
9888
					t.load(newUrl.resource, newUrl, undefined, undefined);
9889
				});
9890
				if (cb) {
9891
					if (s) {
9892
						cb.call(s);
9893
					} else {
9894
						cb.call(tinymce.ScriptLoader);
9895
					}
9896
				}
9897
			}
9898
 
9899
			if (t.urls[n])
9900
				return;
9901
			if (typeof u === "object")
9902
				url = u.prefix + u.resource + u.suffix;
9903
 
9904
			if (url.indexOf('/') != 0 && url.indexOf('://') == -1)
9905
				url = tinymce.baseURL + '/' + url;
9906
 
9907
			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
9908
 
9909
			if (t.lookup[n]) {
9910
				loadDependencies();
9911
			} else {
9912
				tinymce.ScriptLoader.add(url, loadDependencies, s);
9913
			}
9914
		}
9915
	});
9916
 
9917
	// Create plugin and theme managers
9918
	tinymce.PluginManager = new tinymce.AddOnManager();
9919
	tinymce.ThemeManager = new tinymce.AddOnManager();
9920
}(tinymce));
9921
 
9922
(function(tinymce) {
9923
	// Shorten names
9924
	var each = tinymce.each, extend = tinymce.extend,
9925
		DOM = tinymce.DOM, Event = tinymce.dom.Event,
9926
		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
9927
		explode = tinymce.explode,
9928
		Dispatcher = tinymce.util.Dispatcher, undefined, instanceCounter = 0;
9929
 
9930
	// Setup some URLs where the editor API is located and where the document is
9931
	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
9932
	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
9933
		tinymce.documentBaseURL += '/';
9934
 
9935
	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
9936
 
9937
	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
9938
 
9939
	// Add before unload listener
9940
	// This was required since IE was leaking memory if you added and removed beforeunload listeners
9941
	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
9942
	tinymce.onBeforeUnload = new Dispatcher(tinymce);
9943
 
9944
	// Must be on window or IE will leak if the editor is placed in frame or iframe
9945
	Event.add(window, 'beforeunload', function(e) {
9946
		tinymce.onBeforeUnload.dispatch(tinymce, e);
9947
	});
9948
 
9949
	tinymce.onAddEditor = new Dispatcher(tinymce);
9950
 
9951
	tinymce.onRemoveEditor = new Dispatcher(tinymce);
9952
 
9953
	tinymce.EditorManager = extend(tinymce, {
9954
		editors : [],
9955
 
9956
		i18n : {},
9957
 
9958
		activeEditor : null,
9959
 
9960
		init : function(s) {
9961
			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
9962
 
9963
			function execCallback(se, n, s) {
9964
				var f = se[n];
9965
 
9966
				if (!f)
9967
					return;
9968
 
9969
				if (tinymce.is(f, 'string')) {
9970
					s = f.replace(/\.\w+$/, '');
9971
					s = s ? tinymce.resolve(s) : 0;
9972
					f = tinymce.resolve(f);
9973
				}
9974
 
9975
				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
9976
			};
9977
 
9978
			s = extend({
9979
				theme : "simple",
9980
				language : "en"
9981
			}, s);
9982
 
9983
			t.settings = s;
9984
 
9985
			// Legacy call
9986
			Event.add(document, 'init', function() {
9987
				var l, co;
9988
 
9989
				execCallback(s, 'onpageload');
9990
 
9991
				switch (s.mode) {
9992
					case "exact":
9993
						l = s.elements || '';
9994
 
9995
						if(l.length > 0) {
9996
							each(explode(l), function(v) {
9997
								if (DOM.get(v)) {
9998
									ed = new tinymce.Editor(v, s);
9999
									el.push(ed);
10000
									ed.render(1);
10001
								} else {
10002
									each(document.forms, function(f) {
10003
										each(f.elements, function(e) {
10004
											if (e.name === v) {
10005
												v = 'mce_editor_' + instanceCounter++;
10006
												DOM.setAttrib(e, 'id', v);
10007
 
10008
												ed = new tinymce.Editor(v, s);
10009
												el.push(ed);
10010
												ed.render(1);
10011
											}
10012
										});
10013
									});
10014
								}
10015
							});
10016
						}
10017
						break;
10018
 
10019
					case "textareas":
10020
					case "specific_textareas":
10021
						function hasClass(n, c) {
10022
							return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
10023
						};
10024
 
10025
						each(DOM.select('textarea'), function(v) {
10026
							if (s.editor_deselector && hasClass(v, s.editor_deselector))
10027
								return;
10028
 
10029
							if (!s.editor_selector || hasClass(v, s.editor_selector)) {
10030
								// Can we use the name
10031
								e = DOM.get(v.name);
10032
								if (!v.id && !e)
10033
									v.id = v.name;
10034
 
10035
								// Generate unique name if missing or already exists
10036
								if (!v.id || t.get(v.id))
10037
									v.id = DOM.uniqueId();
10038
 
10039
								ed = new tinymce.Editor(v.id, s);
10040
								el.push(ed);
10041
								ed.render(1);
10042
							}
10043
						});
10044
						break;
10045
				}
10046
 
10047
				// Call onInit when all editors are initialized
10048
				if (s.oninit) {
10049
					l = co = 0;
10050
 
10051
					each(el, function(ed) {
10052
						co++;
10053
 
10054
						if (!ed.initialized) {
10055
							// Wait for it
10056
							ed.onInit.add(function() {
10057
								l++;
10058
 
10059
								// All done
10060
								if (l == co)
10061
									execCallback(s, 'oninit');
10062
							});
10063
						} else
10064
							l++;
10065
 
10066
						// All done
10067
						if (l == co)
10068
							execCallback(s, 'oninit');
10069
					});
10070
				}
10071
			});
10072
		},
10073
 
10074
		get : function(id) {
10075
			if (id === undefined)
10076
				return this.editors;
10077
 
10078
			return this.editors[id];
10079
		},
10080
 
10081
		getInstanceById : function(id) {
10082
			return this.get(id);
10083
		},
10084
 
10085
		add : function(editor) {
10086
			var self = this, editors = self.editors;
10087
 
10088
			// Add named and index editor instance
10089
			editors[editor.id] = editor;
10090
			editors.push(editor);
10091
 
10092
			self._setActive(editor);
10093
			self.onAddEditor.dispatch(self, editor);
10094
 
10095
 
10096
			// Patch the tinymce.Editor instance with jQuery adapter logic
10097
			if (tinymce.adapter)
10098
				tinymce.adapter.patchEditor(editor);
10099
 
10100
 
10101
			return editor;
10102
		},
10103
 
10104
		remove : function(editor) {
10105
			var t = this, i, editors = t.editors;
10106
 
10107
			// Not in the collection
10108
			if (!editors[editor.id])
10109
				return null;
10110
 
10111
			delete editors[editor.id];
10112
 
10113
			for (i = 0; i < editors.length; i++) {
10114
				if (editors[i] == editor) {
10115
					editors.splice(i, 1);
10116
					break;
10117
				}
10118
			}
10119
 
10120
			// Select another editor since the active one was removed
10121
			if (t.activeEditor == editor)
10122
				t._setActive(editors[0]);
10123
 
10124
			editor.destroy();
10125
			t.onRemoveEditor.dispatch(t, editor);
10126
 
10127
			return editor;
10128
		},
10129
 
10130
		execCommand : function(c, u, v) {
10131
			var t = this, ed = t.get(v), w;
10132
 
10133
			// Manager commands
10134
			switch (c) {
10135
				case "mceFocus":
10136
					ed.focus();
10137
					return true;
10138
 
10139
				case "mceAddEditor":
10140
				case "mceAddControl":
10141
					if (!t.get(v))
10142
						new tinymce.Editor(v, t.settings).render();
10143
 
10144
					return true;
10145
 
10146
				case "mceAddFrameControl":
10147
					w = v.window;
10148
 
10149
					// Add tinyMCE global instance and tinymce namespace to specified window
10150
					w.tinyMCE = tinyMCE;
10151
					w.tinymce = tinymce;
10152
 
10153
					tinymce.DOM.doc = w.document;
10154
					tinymce.DOM.win = w;
10155
 
10156
					ed = new tinymce.Editor(v.element_id, v);
10157
					ed.render();
10158
 
10159
					// Fix IE memory leaks
10160
					if (tinymce.isIE) {
10161
						function clr() {
10162
							ed.destroy();
10163
							w.detachEvent('onunload', clr);
10164
							w = w.tinyMCE = w.tinymce = null; // IE leak
10165
						};
10166
 
10167
						w.attachEvent('onunload', clr);
10168
					}
10169
 
10170
					v.page_window = null;
10171
 
10172
					return true;
10173
 
10174
				case "mceRemoveEditor":
10175
				case "mceRemoveControl":
10176
					if (ed)
10177
						ed.remove();
10178
 
10179
					return true;
10180
 
10181
				case 'mceToggleEditor':
10182
					if (!ed) {
10183
						t.execCommand('mceAddControl', 0, v);
10184
						return true;
10185
					}
10186
 
10187
					if (ed.isHidden())
10188
						ed.show();
10189
					else
10190
						ed.hide();
10191
 
10192
					return true;
10193
			}
10194
 
10195
			// Run command on active editor
10196
			if (t.activeEditor)
10197
				return t.activeEditor.execCommand(c, u, v);
10198
 
10199
			return false;
10200
		},
10201
 
10202
		execInstanceCommand : function(id, c, u, v) {
10203
			var ed = this.get(id);
10204
 
10205
			if (ed)
10206
				return ed.execCommand(c, u, v);
10207
 
10208
			return false;
10209
		},
10210
 
10211
		triggerSave : function() {
10212
			each(this.editors, function(e) {
10213
				e.save();
10214
			});
10215
		},
10216
 
10217
		addI18n : function(p, o) {
10218
			var lo, i18n = this.i18n;
10219
 
10220
			if (!tinymce.is(p, 'string')) {
10221
				each(p, function(o, lc) {
10222
					each(o, function(o, g) {
10223
						each(o, function(o, k) {
10224
							if (g === 'common')
10225
								i18n[lc + '.' + k] = o;
10226
							else
10227
								i18n[lc + '.' + g + '.' + k] = o;
10228
						});
10229
					});
10230
				});
10231
			} else {
10232
				each(o, function(o, k) {
10233
					i18n[p + '.' + k] = o;
10234
				});
10235
			}
10236
		},
10237
 
10238
		// Private methods
10239
 
10240
		_setActive : function(editor) {
10241
			this.selectedInstance = this.activeEditor = editor;
10242
		}
10243
	});
10244
})(tinymce);
10245
 
10246
(function(tinymce) {
10247
	// Shorten these names
10248
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
10249
		Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isGecko = tinymce.isGecko,
10250
		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
10251
		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
10252
		inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
10253
 
10254
	tinymce.create('tinymce.Editor', {
10255
		Editor : function(id, s) {
10256
			var t = this;
10257
 
10258
			t.id = t.editorId = id;
10259
 
10260
			t.execCommands = {};
10261
			t.queryStateCommands = {};
10262
			t.queryValueCommands = {};
10263
 
10264
			t.isNotDirty = false;
10265
 
10266
			t.plugins = {};
10267
 
10268
			// Add events to the editor
10269
			each([
10270
				'onPreInit',
10271
 
10272
				'onBeforeRenderUI',
10273
 
10274
				'onPostRender',
10275
 
10276
				'onInit',
10277
 
10278
				'onRemove',
10279
 
10280
				'onActivate',
10281
 
10282
				'onDeactivate',
10283
 
10284
				'onClick',
10285
 
10286
				'onEvent',
10287
 
10288
				'onMouseUp',
10289
 
10290
				'onMouseDown',
10291
 
10292
				'onDblClick',
10293
 
10294
				'onKeyDown',
10295
 
10296
				'onKeyUp',
10297
 
10298
				'onKeyPress',
10299
 
10300
				'onContextMenu',
10301
 
10302
				'onSubmit',
10303
 
10304
				'onReset',
10305
 
10306
				'onPaste',
10307
 
10308
				'onPreProcess',
10309
 
10310
				'onPostProcess',
10311
 
10312
				'onBeforeSetContent',
10313
 
10314
				'onBeforeGetContent',
10315
 
10316
				'onSetContent',
10317
 
10318
				'onGetContent',
10319
 
10320
				'onLoadContent',
10321
 
10322
				'onSaveContent',
10323
 
10324
				'onNodeChange',
10325
 
10326
				'onChange',
10327
 
10328
				'onBeforeExecCommand',
10329
 
10330
				'onExecCommand',
10331
 
10332
				'onUndo',
10333
 
10334
				'onRedo',
10335
 
10336
				'onVisualAid',
10337
 
10338
				'onSetProgressState'
10339
			], function(e) {
10340
				t[e] = new Dispatcher(t);
10341
			});
10342
 
10343
			t.settings = s = extend({
10344
				id : id,
10345
				language : 'en',
10346
				docs_language : 'en',
10347
				theme : 'simple',
10348
				skin : 'default',
10349
				delta_width : 0,
10350
				delta_height : 0,
10351
				popup_css : '',
10352
				plugins : '',
10353
				document_base_url : tinymce.documentBaseURL,
10354
				add_form_submit_trigger : 1,
10355
				submit_patch : 1,
10356
				add_unload_trigger : 1,
10357
				convert_urls : 1,
10358
				relative_urls : 1,
10359
				remove_script_host : 1,
10360
				table_inline_editing : 0,
10361
				object_resizing : 1,
10362
				cleanup : 1,
10363
				accessibility_focus : 1,
10364
				custom_shortcuts : 1,
10365
				custom_undo_redo_keyboard_shortcuts : 1,
10366
				custom_undo_redo_restore_selection : 1,
10367
				custom_undo_redo : 1,
10368
				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
10369
				visual_table_class : 'mceItemTable',
10370
				visual : 1,
10371
				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
10372
				apply_source_formatting : 1,
10373
				directionality : 'ltr',
10374
				forced_root_block : 'p',
10375
				hidden_input : 1,
10376
				padd_empty_editor : 1,
10377
				render_ui : 1,
10378
				init_theme : 1,
10379
				force_p_newlines : 1,
10380
				indentation : '30px',
10381
				keep_styles : 1,
10382
				fix_table_elements : 1,
10383
				inline_styles : 1,
10384
				convert_fonts_to_spans : true,
10385
				indent : 'simple',
10386
				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10387
				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr',
10388
				validate : true,
10389
				entity_encoding : 'named',
10390
				url_converter : t.convertURL,
10391
				url_converter_scope : t,
10392
				ie7_compat : true
10393
			}, s);
10394
 
10395
			t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
10396
				base_uri : tinyMCE.baseURI
10397
			});
10398
 
10399
			t.baseURI = tinymce.baseURI;
10400
 
10401
			t.contentCSS = [];
10402
 
10403
			// Call setup
10404
			t.execCallback('setup', t);
10405
		},
10406
 
10407
		render : function(nst) {
10408
			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
10409
 
10410
			// Page is not loaded yet, wait for it
10411
			if (!Event.domLoaded) {
10412
				Event.add(document, 'init', function() {
10413
					t.render();
10414
				});
10415
				return;
10416
			}
10417
 
10418
			tinyMCE.settings = s;
10419
 
10420
			// Element not found, then skip initialization
10421
			if (!t.getElement())
10422
				return;
10423
 
10424
			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff
10425
			// here since the browser says it has contentEditable support but there is no visible
10426
			// caret We will remove this check ones Apple implements full contentEditable support
10427
			if (tinymce.isIDevice && !tinymce.isIOS5)
10428
				return;
10429
 
10430
			// Add hidden input for non input elements inside form elements
10431
			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
10432
				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
10433
 
10434
			if (tinymce.WindowManager)
10435
				t.windowManager = new tinymce.WindowManager(t);
10436
 
10437
			if (s.encoding == 'xml') {
10438
				t.onGetContent.add(function(ed, o) {
10439
					if (o.save)
10440
						o.content = DOM.encode(o.content);
10441
				});
10442
			}
10443
 
10444
			if (s.add_form_submit_trigger) {
10445
				t.onSubmit.addToTop(function() {
10446
					if (t.initialized) {
10447
						t.save();
10448
						t.isNotDirty = 1;
10449
					}
10450
				});
10451
			}
10452
 
10453
			if (s.add_unload_trigger) {
10454
				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
10455
					if (t.initialized && !t.destroyed && !t.isHidden())
10456
						t.save({format : 'raw', no_events : true});
10457
				});
10458
			}
10459
 
10460
			tinymce.addUnload(t.destroy, t);
10461
 
10462
			if (s.submit_patch) {
10463
				t.onBeforeRenderUI.add(function() {
10464
					var n = t.getElement().form;
10465
 
10466
					if (!n)
10467
						return;
10468
 
10469
					// Already patched
10470
					if (n._mceOldSubmit)
10471
						return;
10472
 
10473
					// Check page uses id="submit" or name="submit" for it's submit button
10474
					if (!n.submit.nodeType && !n.submit.length) {
10475
						t.formElement = n;
10476
						n._mceOldSubmit = n.submit;
10477
						n.submit = function() {
10478
							// Save all instances
10479
							tinymce.triggerSave();
10480
							t.isNotDirty = 1;
10481
 
10482
							return t.formElement._mceOldSubmit(t.formElement);
10483
						};
10484
					}
10485
 
10486
					n = null;
10487
				});
10488
			}
10489
 
10490
			// Load scripts
10491
			function loadScripts() {
10492
				if (s.language && s.language_load !== false)
10493
					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
10494
 
10495
				if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
10496
					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
10497
 
10498
				each(explode(s.plugins), function(p) {
10499
					if (p &&!PluginManager.urls[p]) {
10500
						if (p.charAt(0) == '-') {
10501
							p = p.substr(1, p.length);
10502
							var dependencies = PluginManager.dependencies(p);
10503
							each(dependencies, function(dep) {
10504
								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
10505
								var dep = PluginManager.createUrl(defaultSettings, dep);
10506
								PluginManager.load(dep.resource, dep);
10507
 
10508
							});
10509
						} else {
10510
							// Skip safari plugin, since it is removed as of 3.3b1
10511
							if (p == 'safari') {
10512
								return;
10513
							}
10514
							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
10515
						}
10516
					}
10517
				});
10518
 
10519
				// Init when que is loaded
10520
				sl.loadQueue(function() {
10521
					if (!t.removed)
10522
						t.init();
10523
				});
10524
			};
10525
 
10526
			loadScripts();
10527
		},
10528
 
10529
		init : function() {
10530
			var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
10531
 
10532
			tinymce.add(t);
10533
 
10534
			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
10535
 
10536
			if (s.theme) {
10537
				s.theme = s.theme.replace(/-/, '');
10538
				o = ThemeManager.get(s.theme);
10539
				t.theme = new o();
10540
 
10541
				if (t.theme.init && s.init_theme)
10542
					t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
10543
			}
10544
			function initPlugin(p) {
10545
				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
10546
				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
10547
					each(PluginManager.dependencies(p), function(dep){
10548
						initPlugin(dep);
10549
					});
10550
					po = new c(t, u);
10551
 
10552
					t.plugins[p] = po;
10553
 
10554
					if (po.init) {
10555
						po.init(t, u);
10556
						initializedPlugins.push(p);
10557
					}
10558
				}
10559
			}
10560
 
10561
			// Create all plugins
10562
			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
10563
 
10564
			// Setup popup CSS path(s)
10565
			if (s.popup_css !== false) {
10566
				if (s.popup_css)
10567
					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
10568
				else
10569
					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
10570
			}
10571
 
10572
			if (s.popup_css_add)
10573
				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
10574
 
10575
			t.controlManager = new tinymce.ControlManager(t);
10576
 
10577
			if (s.custom_undo_redo) {
10578
				t.onBeforeExecCommand.add(function(ed, cmd, ui, val, a) {
10579
					if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10580
						t.undoManager.beforeChange();
10581
				});
10582
 
10583
				t.onExecCommand.add(function(ed, cmd, ui, val, a) {
10584
					if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
10585
						t.undoManager.add();
10586
				});
10587
			}
10588
 
10589
			t.onExecCommand.add(function(ed, c) {
10590
				// Don't refresh the select lists until caret move
10591
				if (!/^(FontName|FontSize)$/.test(c))
10592
					t.nodeChanged();
10593
			});
10594
 
10595
			// Remove ghost selections on images and tables in Gecko
10596
			if (isGecko) {
10597
				function repaint(a, o) {
10598
					if (!o || !o.initial)
10599
						t.execCommand('mceRepaint');
10600
				};
10601
 
10602
				t.onUndo.add(repaint);
10603
				t.onRedo.add(repaint);
10604
				t.onSetContent.add(repaint);
10605
			}
10606
 
10607
			// Enables users to override the control factory
10608
			t.onBeforeRenderUI.dispatch(t, t.controlManager);
10609
 
10610
			// Measure box
10611
			if (s.render_ui) {
10612
				w = s.width || e.style.width || e.offsetWidth;
10613
				h = s.height || e.style.height || e.offsetHeight;
10614
				t.orgDisplay = e.style.display;
10615
				re = /^[0-9\.]+(|px)$/i;
10616
 
10617
				if (re.test('' + w))
10618
					w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
10619
 
10620
				if (re.test('' + h))
10621
					h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
10622
 
10623
				// Render UI
10624
				o = t.theme.renderUI({
10625
					targetNode : e,
10626
					width : w,
10627
					height : h,
10628
					deltaWidth : s.delta_width,
10629
					deltaHeight : s.delta_height
10630
				});
10631
 
10632
				t.editorContainer = o.editorContainer;
10633
			}
10634
 
10635
 
10636
			// User specified a document.domain value
10637
			if (document.domain && location.hostname != document.domain)
10638
				tinymce.relaxedDomain = document.domain;
10639
 
10640
			// Resize editor
10641
			DOM.setStyles(o.sizeContainer || o.editorContainer, {
10642
				width : w,
10643
				height : h
10644
			});
10645
 
10646
			// Load specified content CSS last
10647
			if (s.content_css) {
10648
				tinymce.each(explode(s.content_css), function(u) {
10649
					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
10650
				});
10651
			}
10652
 
10653
			h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
10654
			if (h < 100)
10655
				h = 100;
10656
 
10657
			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
10658
 
10659
			// We only need to override paths if we have to
10660
			// IE has a bug where it remove site absolute urls to relative ones if this is specified
10661
			if (s.document_base_url != tinymce.documentBaseURL)
10662
				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
10663
 
10664
			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
10665
			if (s.ie7_compat)
10666
				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
10667
			else
10668
				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
10669
 
10670
			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
10671
 
10672
			// Load the CSS by injecting them into the HTML this will reduce "flicker"
10673
			for (i = 0; i < t.contentCSS.length; i++) {
10674
				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
10675
			}
10676
 
10677
			bi = s.body_id || 'tinymce';
10678
			if (bi.indexOf('=') != -1) {
10679
				bi = t.getParam('body_id', '', 'hash');
10680
				bi = bi[t.id] || bi;
10681
			}
10682
 
10683
			bc = s.body_class || '';
10684
			if (bc.indexOf('=') != -1) {
10685
				bc = t.getParam('body_class', '', 'hash');
10686
				bc = bc[t.id] || '';
10687
			}
10688
 
10689
			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"><br></body></html>';
10690
 
10691
			// Domain relaxing enabled, then set document domain
10692
			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
10693
				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
10694
				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
10695
			}
10696
 
10697
			// Create iframe
10698
			// TODO: ACC add the appropriate description on this.
10699
			n = DOM.add(o.iframeContainer, 'iframe', {
10700
				id : t.id + "_ifr",
10701
				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
10702
				frameBorder : '0',
10703
				allowTransparency : "true",
10704
				title : s.aria_label,
10705
				style : {
10706
					width : '100%',
10707
					height : h,
10708
					display : 'block' // Important for Gecko to render the iframe correctly
10709
				}
10710
			});
10711
 
10712
			t.contentAreaContainer = o.iframeContainer;
10713
			DOM.get(o.editorContainer).style.display = t.orgDisplay;
10714
			DOM.get(t.id).style.display = 'none';
10715
			DOM.setAttrib(t.id, 'aria-hidden', true);
10716
 
10717
			if (!tinymce.relaxedDomain || !u)
10718
				t.setupIframe();
10719
 
10720
			e = n = o = null; // Cleanup
10721
		},
10722
 
10723
		setupIframe : function() {
10724
			var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
10725
 
10726
			// Setup iframe body
10727
			if (!isIE || !tinymce.relaxedDomain) {
10728
				d.open();
10729
				d.write(t.iframeHTML);
10730
				d.close();
10731
 
10732
				if (tinymce.relaxedDomain)
10733
					d.domain = tinymce.relaxedDomain;
10734
			}
10735
 
10736
			// It will not steal focus while setting contentEditable
10737
			b = t.getBody();
10738
			b.disabled = true;
10739
 
10740
			if (!s.readonly)
10741
				b.contentEditable = true;
10742
 
10743
			b.disabled = false;
10744
 
10745
			t.schema = new tinymce.html.Schema(s);
10746
 
10747
			t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
10748
				keep_values : true,
10749
				url_converter : t.convertURL,
10750
				url_converter_scope : t,
10751
				hex_colors : s.force_hex_style_colors,
10752
				class_filter : s.class_filter,
10753
				update_styles : 1,
10754
				fix_ie_paragraphs : 1,
10755
				schema : t.schema
10756
			});
10757
 
10758
			t.parser = new tinymce.html.DomParser(s, t.schema);
10759
 
10760
			// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
10761
			if (!t.settings.allow_html_in_named_anchor) {
10762
				t.parser.addAttributeFilter('name', function(nodes, name) {
10763
					var i = nodes.length, sibling, prevSibling, parent, node;
10764
 
10765
					while (i--) {
10766
						node = nodes[i];
10767
						if (node.name === 'a' && node.firstChild) {
10768
							parent = node.parent;
10769
 
10770
							// Move children after current node
10771
							sibling = node.lastChild;
10772
							do {
10773
								prevSibling = sibling.prev;
10774
								parent.insert(sibling, node);
10775
								sibling = prevSibling;
10776
							} while (sibling);
10777
						}
10778
					}
10779
				});
10780
			}
10781
 
10782
			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
10783
			t.parser.addAttributeFilter('src,href,style', function(nodes, name) {
10784
				var i = nodes.length, node, dom = t.dom, value, internalName;
10785
 
10786
				while (i--) {
10787
					node = nodes[i];
10788
					value = node.attr(name);
10789
					internalName = 'data-mce-' + name;
10790
 
10791
					// Add internal attribute if we need to we don't on a refresh of the document
10792
					if (!node.attributes.map[internalName]) {
10793
						if (name === "style")
10794
							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
10795
						else
10796
							node.attr(internalName, t.convertURL(value, name, node.name));
10797
					}
10798
				}
10799
			});
10800
 
10801
			// Keep scripts from executing
10802
			t.parser.addNodeFilter('script', function(nodes, name) {
10803
				var i = nodes.length;
10804
 
10805
				while (i--)
10806
					nodes[i].attr('type', 'mce-text/javascript');
10807
			});
10808
 
10809
			t.parser.addNodeFilter('#cdata', function(nodes, name) {
10810
				var i = nodes.length, node;
10811
 
10812
				while (i--) {
10813
					node = nodes[i];
10814
					node.type = 8;
10815
					node.name = '#comment';
10816
					node.value = '[CDATA[' + node.value + ']]';
10817
				}
10818
			});
10819
 
10820
			t.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
10821
				var i = nodes.length, node, nonEmptyElements = t.schema.getNonEmptyElements();
10822
 
10823
				while (i--) {
10824
					node = nodes[i];
10825
 
10826
					if (node.isEmpty(nonEmptyElements))
10827
						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
10828
				}
10829
			});
10830
 
10831
			t.serializer = new tinymce.dom.Serializer(s, t.dom, t.schema);
10832
 
10833
			t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
10834
 
10835
			t.formatter = new tinymce.Formatter(this);
10836
 
10837
			// Register default formats
10838
			t.formatter.register({
10839
				alignleft : [
10840
					{selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
10841
					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
10842
				],
10843
 
10844
				aligncenter : [
10845
					{selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
10846
					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
10847
					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
10848
				],
10849
 
10850
				alignright : [
10851
					{selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
10852
					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
10853
				],
10854
 
10855
				alignfull : [
10856
					{selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}}
10857
				],
10858
 
10859
				bold : [
10860
					{inline : 'strong', remove : 'all'},
10861
					{inline : 'span', styles : {fontWeight : 'bold'}},
10862
					{inline : 'b', remove : 'all'}
10863
				],
10864
 
10865
				italic : [
10866
					{inline : 'em', remove : 'all'},
10867
					{inline : 'span', styles : {fontStyle : 'italic'}},
10868
					{inline : 'i', remove : 'all'}
10869
				],
10870
 
10871
				underline : [
10872
					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
10873
					{inline : 'u', remove : 'all'}
10874
				],
10875
 
10876
				strikethrough : [
10877
					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
10878
					{inline : 'strike', remove : 'all'}
10879
				],
10880
 
10881
				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
10882
				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
10883
				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
10884
				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
10885
				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
10886
				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
10887
				subscript : {inline : 'sub'},
10888
				superscript : {inline : 'sup'},
10889
 
10890
				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
10891
					onmatch : function(node) {
10892
						return true;
10893
					},
10894
 
10895
					onformat : function(elm, fmt, vars) {
10896
						each(vars, function(value, key) {
10897
							t.dom.setAttrib(elm, key, value);
10898
						});
10899
					}
10900
				},
10901
 
10902
				removeformat : [
10903
					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
10904
					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
10905
					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
10906
				]
10907
			});
10908
 
10909
			// Register default block formats
10910
			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
10911
				t.formatter.register(name, {block : name, remove : 'all'});
10912
			});
10913
 
10914
			// Register user defined formats
10915
			t.formatter.register(t.settings.formats);
10916
 
10917
			t.undoManager = new tinymce.UndoManager(t);
10918
 
10919
			// Pass through
10920
			t.undoManager.onAdd.add(function(um, l) {
10921
				if (um.hasUndo())
10922
					return t.onChange.dispatch(t, l, um);
10923
			});
10924
 
10925
			t.undoManager.onUndo.add(function(um, l) {
10926
				return t.onUndo.dispatch(t, l, um);
10927
			});
10928
 
10929
			t.undoManager.onRedo.add(function(um, l) {
10930
				return t.onRedo.dispatch(t, l, um);
10931
			});
10932
 
10933
			t.forceBlocks = new tinymce.ForceBlocks(t, {
10934
				forced_root_block : s.forced_root_block
10935
			});
10936
 
10937
			t.editorCommands = new tinymce.EditorCommands(t);
10938
 
10939
			// Pass through
10940
			t.serializer.onPreProcess.add(function(se, o) {
10941
				return t.onPreProcess.dispatch(t, o, se);
10942
			});
10943
 
10944
			t.serializer.onPostProcess.add(function(se, o) {
10945
				return t.onPostProcess.dispatch(t, o, se);
10946
			});
10947
 
10948
			t.onPreInit.dispatch(t);
10949
 
10950
			if (!s.gecko_spellcheck)
10951
				t.getBody().spellcheck = 0;
10952
 
10953
			if (!s.readonly)
10954
				t._addEvents();
10955
 
10956
			t.controlManager.onPostRender.dispatch(t, t.controlManager);
10957
			t.onPostRender.dispatch(t);
10958
 
10959
			t.quirks = new tinymce.util.Quirks(this);
10960
 
10961
			if (s.directionality)
10962
				t.getBody().dir = s.directionality;
10963
 
10964
			if (s.nowrap)
10965
				t.getBody().style.whiteSpace = "nowrap";
10966
 
10967
			if (s.handle_node_change_callback) {
10968
				t.onNodeChange.add(function(ed, cm, n) {
10969
					t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
10970
				});
10971
			}
10972
 
10973
			if (s.save_callback) {
10974
				t.onSaveContent.add(function(ed, o) {
10975
					var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
10976
 
10977
					if (h)
10978
						o.content = h;
10979
				});
10980
			}
10981
 
10982
			if (s.onchange_callback) {
10983
				t.onChange.add(function(ed, l) {
10984
					t.execCallback('onchange_callback', t, l);
10985
				});
10986
			}
10987
 
10988
			if (s.protect) {
10989
				t.onBeforeSetContent.add(function(ed, o) {
10990
					if (s.protect) {
10991
						each(s.protect, function(pattern) {
10992
							o.content = o.content.replace(pattern, function(str) {
10993
								return '<!--mce:protected ' + escape(str) + '-->';
10994
							});
10995
						});
10996
					}
10997
				});
10998
			}
10999
 
11000
			if (s.convert_newlines_to_brs) {
11001
				t.onBeforeSetContent.add(function(ed, o) {
11002
					if (o.initial)
11003
						o.content = o.content.replace(/\r?\n/g, '<br />');
11004
				});
11005
			}
11006
 
11007
			if (s.preformatted) {
11008
				t.onPostProcess.add(function(ed, o) {
11009
					o.content = o.content.replace(/^\s*<pre.*?>/, '');
11010
					o.content = o.content.replace(/<\/pre>\s*$/, '');
11011
 
11012
					if (o.set)
11013
						o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
11014
				});
11015
			}
11016
 
11017
			if (s.verify_css_classes) {
11018
				t.serializer.attribValueFilter = function(n, v) {
11019
					var s, cl;
11020
 
11021
					if (n == 'class') {
11022
						// Build regexp for classes
11023
						if (!t.classesRE) {
11024
							cl = t.dom.getClasses();
11025
 
11026
							if (cl.length > 0) {
11027
								s = '';
11028
 
11029
								each (cl, function(o) {
11030
									s += (s ? '|' : '') + o['class'];
11031
								});
11032
 
11033
								t.classesRE = new RegExp('(' + s + ')', 'gi');
11034
							}
11035
						}
11036
 
11037
						return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
11038
					}
11039
 
11040
					return v;
11041
				};
11042
			}
11043
 
11044
			if (s.cleanup_callback) {
11045
				t.onBeforeSetContent.add(function(ed, o) {
11046
					o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11047
				});
11048
 
11049
				t.onPreProcess.add(function(ed, o) {
11050
					if (o.set)
11051
						t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
11052
 
11053
					if (o.get)
11054
						t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
11055
				});
11056
 
11057
				t.onPostProcess.add(function(ed, o) {
11058
					if (o.set)
11059
						o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
11060
 
11061
					if (o.get)
11062
						o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
11063
				});
11064
			}
11065
 
11066
			if (s.save_callback) {
11067
				t.onGetContent.add(function(ed, o) {
11068
					if (o.save)
11069
						o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
11070
				});
11071
			}
11072
 
11073
			if (s.handle_event_callback) {
11074
				t.onEvent.add(function(ed, e, o) {
11075
					if (t.execCallback('handle_event_callback', e, ed, o) === false)
11076
						Event.cancel(e);
11077
				});
11078
			}
11079
 
11080
			// Add visual aids when new contents is added
11081
			t.onSetContent.add(function() {
11082
				t.addVisual(t.getBody());
11083
			});
11084
 
11085
			// Remove empty contents
11086
			if (s.padd_empty_editor) {
11087
				t.onPostProcess.add(function(ed, o) {
11088
					o.content = o.content.replace(/^(<p[^>]*>(&nbsp;|&#160;|\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
11089
				});
11090
			}
11091
 
11092
			if (isGecko) {
11093
				// Fix gecko link bug, when a link is placed at the end of block elements there is
11094
				// no way to move the caret behind the link. This fix adds a bogus br element after the link
11095
				function fixLinks(ed, o) {
11096
					each(ed.dom.select('a'), function(n) {
11097
						var pn = n.parentNode;
11098
 
11099
						if (ed.dom.isBlock(pn) && pn.lastChild === n)
11100
							ed.dom.add(pn, 'br', {'data-mce-bogus' : 1});
11101
					});
11102
				};
11103
 
11104
				t.onExecCommand.add(function(ed, cmd) {
11105
					if (cmd === 'CreateLink')
11106
						fixLinks(ed);
11107
				});
11108
 
11109
				t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
11110
			}
11111
 
11112
			t.load({initial : true, format : 'html'});
11113
			t.startContent = t.getContent({format : 'raw'});
11114
			t.undoManager.add();
11115
			t.initialized = true;
11116
 
11117
			t.onInit.dispatch(t);
11118
			t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
11119
			t.execCallback('init_instance_callback', t);
11120
			t.focus(true);
11121
			t.nodeChanged({initial : 1});
11122
 
11123
			// Load specified content CSS last
11124
			each(t.contentCSS, function(u) {
11125
				t.dom.loadCSS(u);
11126
			});
11127
 
11128
			// Handle auto focus
11129
			if (s.auto_focus) {
11130
				setTimeout(function () {
11131
					var ed = tinymce.get(s.auto_focus);
11132
 
11133
					ed.selection.select(ed.getBody(), 1);
11134
					ed.selection.collapse(1);
11135
					ed.getBody().focus();
11136
					ed.getWin().focus();
11137
				}, 100);
11138
			}
11139
 
11140
			e = null;
11141
		},
11142
 
11143
 
11144
		focus : function(sf) {
11145
			var oed, t = this, selection = t.selection, ce = t.settings.content_editable, ieRng, controlElm, doc = t.getDoc();
11146
 
11147
			if (!sf) {
11148
				// Get selected control element
11149
				ieRng = selection.getRng();
11150
				if (ieRng.item) {
11151
					controlElm = ieRng.item(0);
11152
				}
11153
 
11154
				t._refreshContentEditable();
11155
				selection.normalize();
11156
 
11157
				// Is not content editable
11158
				if (!ce)
11159
					t.getWin().focus();
11160
 
11161
				// Focus the body as well since it's contentEditable
11162
				if (tinymce.isGecko) {
11163
					t.getBody().focus();
11164
				}
11165
 
11166
				// Restore selected control element
11167
				// This is needed when for example an image is selected within a
11168
				// layer a call to focus will then remove the control selection
11169
				if (controlElm && controlElm.ownerDocument == doc) {
11170
					ieRng = doc.body.createControlRange();
11171
					ieRng.addElement(controlElm);
11172
					ieRng.select();
11173
				}
11174
 
11175
			}
11176
 
11177
			if (tinymce.activeEditor != t) {
11178
				if ((oed = tinymce.activeEditor) != null)
11179
					oed.onDeactivate.dispatch(oed, t);
11180
 
11181
				t.onActivate.dispatch(t, oed);
11182
			}
11183
 
11184
			tinymce._setActive(t);
11185
		},
11186
 
11187
		execCallback : function(n) {
11188
			var t = this, f = t.settings[n], s;
11189
 
11190
			if (!f)
11191
				return;
11192
 
11193
			// Look through lookup
11194
			if (t.callbackLookup && (s = t.callbackLookup[n])) {
11195
				f = s.func;
11196
				s = s.scope;
11197
			}
11198
 
11199
			if (is(f, 'string')) {
11200
				s = f.replace(/\.\w+$/, '');
11201
				s = s ? tinymce.resolve(s) : 0;
11202
				f = tinymce.resolve(f);
11203
				t.callbackLookup = t.callbackLookup || {};
11204
				t.callbackLookup[n] = {func : f, scope : s};
11205
			}
11206
 
11207
			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
11208
		},
11209
 
11210
		translate : function(s) {
11211
			var c = this.settings.language || 'en', i18n = tinymce.i18n;
11212
 
11213
			if (!s)
11214
				return '';
11215
 
11216
			return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
11217
				return i18n[c + '.' + b] || '{#' + b + '}';
11218
			});
11219
		},
11220
 
11221
		getLang : function(n, dv) {
11222
			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
11223
		},
11224
 
11225
		getParam : function(n, dv, ty) {
11226
			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
11227
 
11228
			if (ty === 'hash') {
11229
				o = {};
11230
 
11231
				if (is(v, 'string')) {
11232
					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
11233
						v = v.split('=');
11234
 
11235
						if (v.length > 1)
11236
							o[tr(v[0])] = tr(v[1]);
11237
						else
11238
							o[tr(v[0])] = tr(v);
11239
					});
11240
				} else
11241
					o = v;
11242
 
11243
				return o;
11244
			}
11245
 
11246
			return v;
11247
		},
11248
 
11249
		nodeChanged : function(o) {
11250
			var t = this, s = t.selection, n = s.getStart() || t.getBody();
11251
 
11252
			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
11253
			if (t.initialized) {
11254
				o = o || {};
11255
				n = isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n; // Fix for IE initial state
11256
 
11257
				// Get parents and add them to object
11258
				o.parents = [];
11259
				t.dom.getParent(n, function(node) {
11260
					if (node.nodeName == 'BODY')
11261
						return true;
11262
 
11263
					o.parents.push(node);
11264
				});
11265
 
11266
				t.onNodeChange.dispatch(
11267
					t,
11268
					o ? o.controlManager || t.controlManager : t.controlManager,
11269
					n,
11270
					s.isCollapsed(),
11271
					o
11272
				);
11273
			}
11274
		},
11275
 
11276
		addButton : function(n, s) {
11277
			var t = this;
11278
 
11279
			t.buttons = t.buttons || {};
11280
			t.buttons[n] = s;
11281
		},
11282
 
11283
		addCommand : function(name, callback, scope) {
11284
			this.execCommands[name] = {func : callback, scope : scope || this};
11285
		},
11286
 
11287
		addQueryStateHandler : function(name, callback, scope) {
11288
			this.queryStateCommands[name] = {func : callback, scope : scope || this};
11289
		},
11290
 
11291
		addQueryValueHandler : function(name, callback, scope) {
11292
			this.queryValueCommands[name] = {func : callback, scope : scope || this};
11293
		},
11294
 
11295
		addShortcut : function(pa, desc, cmd_func, sc) {
11296
			var t = this, c;
11297
 
11298
			if (!t.settings.custom_shortcuts)
11299
				return false;
11300
 
11301
			t.shortcuts = t.shortcuts || {};
11302
 
11303
			if (is(cmd_func, 'string')) {
11304
				c = cmd_func;
11305
 
11306
				cmd_func = function() {
11307
					t.execCommand(c, false, null);
11308
				};
11309
			}
11310
 
11311
			if (is(cmd_func, 'object')) {
11312
				c = cmd_func;
11313
 
11314
				cmd_func = function() {
11315
					t.execCommand(c[0], c[1], c[2]);
11316
				};
11317
			}
11318
 
11319
			each(explode(pa), function(pa) {
11320
				var o = {
11321
					func : cmd_func,
11322
					scope : sc || this,
11323
					desc : desc,
11324
					alt : false,
11325
					ctrl : false,
11326
					shift : false
11327
				};
11328
 
11329
				each(explode(pa, '+'), function(v) {
11330
					switch (v) {
11331
						case 'alt':
11332
						case 'ctrl':
11333
						case 'shift':
11334
							o[v] = true;
11335
							break;
11336
 
11337
						default:
11338
							o.charCode = v.charCodeAt(0);
11339
							o.keyCode = v.toUpperCase().charCodeAt(0);
11340
					}
11341
				});
11342
 
11343
				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
11344
			});
11345
 
11346
			return true;
11347
		},
11348
 
11349
		execCommand : function(cmd, ui, val, a) {
11350
			var t = this, s = 0, o, st;
11351
 
11352
			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
11353
				t.focus();
11354
 
11355
			o = {};
11356
			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
11357
			if (o.terminate)
11358
				return false;
11359
 
11360
			// Command callback
11361
			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
11362
				t.onExecCommand.dispatch(t, cmd, ui, val, a);
11363
				return true;
11364
			}
11365
 
11366
			// Registred commands
11367
			if (o = t.execCommands[cmd]) {
11368
				st = o.func.call(o.scope, ui, val);
11369
 
11370
				// Fall through on true
11371
				if (st !== true) {
11372
					t.onExecCommand.dispatch(t, cmd, ui, val, a);
11373
					return st;
11374
				}
11375
			}
11376
 
11377
			// Plugin commands
11378
			each(t.plugins, function(p) {
11379
				if (p.execCommand && p.execCommand(cmd, ui, val)) {
11380
					t.onExecCommand.dispatch(t, cmd, ui, val, a);
11381
					s = 1;
11382
					return false;
11383
				}
11384
			});
11385
 
11386
			if (s)
11387
				return true;
11388
 
11389
			// Theme commands
11390
			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
11391
				t.onExecCommand.dispatch(t, cmd, ui, val, a);
11392
				return true;
11393
			}
11394
 
11395
			// Editor commands
11396
			if (t.editorCommands.execCommand(cmd, ui, val)) {
11397
				t.onExecCommand.dispatch(t, cmd, ui, val, a);
11398
				return true;
11399
			}
11400
 
11401
			// Browser commands
11402
			t.getDoc().execCommand(cmd, ui, val);
11403
			t.onExecCommand.dispatch(t, cmd, ui, val, a);
11404
		},
11405
 
11406
		queryCommandState : function(cmd) {
11407
			var t = this, o, s;
11408
 
11409
			// Is hidden then return undefined
11410
			if (t._isHidden())
11411
				return;
11412
 
11413
			// Registred commands
11414
			if (o = t.queryStateCommands[cmd]) {
11415
				s = o.func.call(o.scope);
11416
 
11417
				// Fall though on true
11418
				if (s !== true)
11419
					return s;
11420
			}
11421
 
11422
			// Registred commands
11423
			o = t.editorCommands.queryCommandState(cmd);
11424
			if (o !== -1)
11425
				return o;
11426
 
11427
			// Browser commands
11428
			try {
11429
				return this.getDoc().queryCommandState(cmd);
11430
			} catch (ex) {
11431
				// Fails sometimes see bug: 1896577
11432
			}
11433
		},
11434
 
11435
		queryCommandValue : function(c) {
11436
			var t = this, o, s;
11437
 
11438
			// Is hidden then return undefined
11439
			if (t._isHidden())
11440
				return;
11441
 
11442
			// Registred commands
11443
			if (o = t.queryValueCommands[c]) {
11444
				s = o.func.call(o.scope);
11445
 
11446
				// Fall though on true
11447
				if (s !== true)
11448
					return s;
11449
			}
11450
 
11451
			// Registred commands
11452
			o = t.editorCommands.queryCommandValue(c);
11453
			if (is(o))
11454
				return o;
11455
 
11456
			// Browser commands
11457
			try {
11458
				return this.getDoc().queryCommandValue(c);
11459
			} catch (ex) {
11460
				// Fails sometimes see bug: 1896577
11461
			}
11462
		},
11463
 
11464
		show : function() {
11465
			var t = this;
11466
 
11467
			DOM.show(t.getContainer());
11468
			DOM.hide(t.id);
11469
			t.load();
11470
		},
11471
 
11472
		hide : function() {
11473
			var t = this, d = t.getDoc();
11474
 
11475
			// Fixed bug where IE has a blinking cursor left from the editor
11476
			if (isIE && d)
11477
				d.execCommand('SelectAll');
11478
 
11479
			// We must save before we hide so Safari doesn't crash
11480
			t.save();
11481
			DOM.hide(t.getContainer());
11482
			DOM.setStyle(t.id, 'display', t.orgDisplay);
11483
		},
11484
 
11485
		isHidden : function() {
11486
			return !DOM.isHidden(this.id);
11487
		},
11488
 
11489
		setProgressState : function(b, ti, o) {
11490
			this.onSetProgressState.dispatch(this, b, ti, o);
11491
 
11492
			return b;
11493
		},
11494
 
11495
		load : function(o) {
11496
			var t = this, e = t.getElement(), h;
11497
 
11498
			if (e) {
11499
				o = o || {};
11500
				o.load = true;
11501
 
11502
				// Double encode existing entities in the value
11503
				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
11504
				o.element = e;
11505
 
11506
				if (!o.no_events)
11507
					t.onLoadContent.dispatch(t, o);
11508
 
11509
				o.element = e = null;
11510
 
11511
				return h;
11512
			}
11513
		},
11514
 
11515
		save : function(o) {
11516
			var t = this, e = t.getElement(), h, f;
11517
 
11518
			if (!e || !t.initialized)
11519
				return;
11520
 
11521
			o = o || {};
11522
			o.save = true;
11523
 
11524
			// Add undo level will trigger onchange event
11525
			if (!o.no_events) {
11526
				t.undoManager.typing = false;
11527
				t.undoManager.add();
11528
			}
11529
 
11530
			o.element = e;
11531
			h = o.content = t.getContent(o);
11532
 
11533
			if (!o.no_events)
11534
				t.onSaveContent.dispatch(t, o);
11535
 
11536
			h = o.content;
11537
 
11538
			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
11539
				e.innerHTML = h;
11540
 
11541
				// Update hidden form element
11542
				if (f = DOM.getParent(t.id, 'form')) {
11543
					each(f.elements, function(e) {
11544
						if (e.name == t.id) {
11545
							e.value = h;
11546
							return false;
11547
						}
11548
					});
11549
				}
11550
			} else
11551
				e.value = h;
11552
 
11553
			o.element = e = null;
11554
 
11555
			return h;
11556
		},
11557
 
11558
		setContent : function(content, args) {
11559
			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
11560
 
11561
			// Setup args object
11562
			args = args || {};
11563
			args.format = args.format || 'html';
11564
			args.set = true;
11565
			args.content = content;
11566
 
11567
			// Do preprocessing
11568
			if (!args.no_events)
11569
				self.onBeforeSetContent.dispatch(self, args);
11570
 
11571
			content = args.content;
11572
 
11573
			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
11574
			// It will also be impossible to place the caret in the editor unless there is a BR element present
11575
			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
11576
				forcedRootBlockName = self.settings.forced_root_block;
11577
				if (forcedRootBlockName)
11578
					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
11579
				else
11580
					content = '<br data-mce-bogus="1">';
11581
 
11582
				body.innerHTML = content;
11583
				self.selection.select(body, true);
11584
				self.selection.collapse(true);
11585
				return;
11586
			}
11587
 
11588
			// Parse and serialize the html
11589
			if (args.format !== 'raw') {
11590
				content = new tinymce.html.Serializer({}, self.schema).serialize(
11591
					self.parser.parse(content)
11592
				);
11593
			}
11594
 
11595
			// Set the new cleaned contents to the editor
11596
			args.content = tinymce.trim(content);
11597
			self.dom.setHTML(body, args.content);
11598
 
11599
			// Do post processing
11600
			if (!args.no_events)
11601
				self.onSetContent.dispatch(self, args);
11602
 
11603
			self.selection.normalize();
11604
 
11605
			return args.content;
11606
		},
11607
 
11608
		getContent : function(args) {
11609
			var self = this, content;
11610
 
11611
			// Setup args object
11612
			args = args || {};
11613
			args.format = args.format || 'html';
11614
			args.get = true;
11615
 
11616
			// Do preprocessing
11617
			if (!args.no_events)
11618
				self.onBeforeGetContent.dispatch(self, args);
11619
 
11620
			// Get raw contents or by default the cleaned contents
11621
			if (args.format == 'raw')
11622
				content = self.getBody().innerHTML;
11623
			else
11624
				content = self.serializer.serialize(self.getBody(), args);
11625
 
11626
			args.content = tinymce.trim(content);
11627
 
11628
			// Do post processing
11629
			if (!args.no_events)
11630
				self.onGetContent.dispatch(self, args);
11631
 
11632
			return args.content;
11633
		},
11634
 
11635
		isDirty : function() {
11636
			var self = this;
11637
 
11638
			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
11639
		},
11640
 
11641
		getContainer : function() {
11642
			var t = this;
11643
 
11644
			if (!t.container)
11645
				t.container = DOM.get(t.editorContainer || t.id + '_parent');
11646
 
11647
			return t.container;
11648
		},
11649
 
11650
		getContentAreaContainer : function() {
11651
			return this.contentAreaContainer;
11652
		},
11653
 
11654
		getElement : function() {
11655
			return DOM.get(this.settings.content_element || this.id);
11656
		},
11657
 
11658
		getWin : function() {
11659
			var t = this, e;
11660
 
11661
			if (!t.contentWindow) {
11662
				e = DOM.get(t.id + "_ifr");
11663
 
11664
				if (e)
11665
					t.contentWindow = e.contentWindow;
11666
			}
11667
 
11668
			return t.contentWindow;
11669
		},
11670
 
11671
		getDoc : function() {
11672
			var t = this, w;
11673
 
11674
			if (!t.contentDocument) {
11675
				w = t.getWin();
11676
 
11677
				if (w)
11678
					t.contentDocument = w.document;
11679
			}
11680
 
11681
			return t.contentDocument;
11682
		},
11683
 
11684
		getBody : function() {
11685
			return this.bodyElement || this.getDoc().body;
11686
		},
11687
 
11688
		convertURL : function(u, n, e) {
11689
			var t = this, s = t.settings;
11690
 
11691
			// Use callback instead
11692
			if (s.urlconverter_callback)
11693
				return t.execCallback('urlconverter_callback', u, e, true, n);
11694
 
11695
			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
11696
			if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
11697
				return u;
11698
 
11699
			// Convert to relative
11700
			if (s.relative_urls)
11701
				return t.documentBaseURI.toRelative(u);
11702
 
11703
			// Convert to absolute
11704
			u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
11705
 
11706
			return u;
11707
		},
11708
 
11709
		addVisual : function(e) {
11710
			var t = this, s = t.settings;
11711
 
11712
			e = e || t.getBody();
11713
 
11714
			if (!is(t.hasVisual))
11715
				t.hasVisual = s.visual;
11716
 
11717
			each(t.dom.select('table,a', e), function(e) {
11718
				var v;
11719
 
11720
				switch (e.nodeName) {
11721
					case 'TABLE':
11722
						v = t.dom.getAttrib(e, 'border');
11723
 
11724
						if (!v || v == '0') {
11725
							if (t.hasVisual)
11726
								t.dom.addClass(e, s.visual_table_class);
11727
							else
11728
								t.dom.removeClass(e, s.visual_table_class);
11729
						}
11730
 
11731
						return;
11732
 
11733
					case 'A':
11734
						v = t.dom.getAttrib(e, 'name');
11735
 
11736
						if (v) {
11737
							if (t.hasVisual)
11738
								t.dom.addClass(e, 'mceItemAnchor');
11739
							else
11740
								t.dom.removeClass(e, 'mceItemAnchor');
11741
						}
11742
 
11743
						return;
11744
				}
11745
			});
11746
 
11747
			t.onVisualAid.dispatch(t, e, t.hasVisual);
11748
		},
11749
 
11750
		remove : function() {
11751
			var t = this, e = t.getContainer();
11752
 
11753
			t.removed = 1; // Cancels post remove event execution
11754
			t.hide();
11755
 
11756
			t.execCallback('remove_instance_callback', t);
11757
			t.onRemove.dispatch(t);
11758
 
11759
			// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
11760
			t.onExecCommand.listeners = [];
11761
 
11762
			tinymce.remove(t);
11763
			DOM.remove(e);
11764
		},
11765
 
11766
		destroy : function(s) {
11767
			var t = this;
11768
 
11769
			// One time is enough
11770
			if (t.destroyed)
11771
				return;
11772
 
11773
			if (!s) {
11774
				tinymce.removeUnload(t.destroy);
11775
				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
11776
 
11777
				// Manual destroy
11778
				if (t.theme && t.theme.destroy)
11779
					t.theme.destroy();
11780
 
11781
				// Destroy controls, selection and dom
11782
				t.controlManager.destroy();
11783
				t.selection.destroy();
11784
				t.dom.destroy();
11785
 
11786
				// Remove all events
11787
 
11788
				// Don't clear the window or document if content editable
11789
				// is enabled since other instances might still be present
11790
				if (!t.settings.content_editable) {
11791
					Event.clear(t.getWin());
11792
					Event.clear(t.getDoc());
11793
				}
11794
 
11795
				Event.clear(t.getBody());
11796
				Event.clear(t.formElement);
11797
			}
11798
 
11799
			if (t.formElement) {
11800
				t.formElement.submit = t.formElement._mceOldSubmit;
11801
				t.formElement._mceOldSubmit = null;
11802
			}
11803
 
11804
			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
11805
 
11806
			if (t.selection)
11807
				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
11808
 
11809
			t.destroyed = 1;
11810
		},
11811
 
11812
		// Internal functions
11813
 
11814
		_addEvents : function() {
11815
			// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
11816
			var t = this, i, s = t.settings, dom = t.dom, lo = {
11817
				mouseup : 'onMouseUp',
11818
				mousedown : 'onMouseDown',
11819
				click : 'onClick',
11820
				keyup : 'onKeyUp',
11821
				keydown : 'onKeyDown',
11822
				keypress : 'onKeyPress',
11823
				submit : 'onSubmit',
11824
				reset : 'onReset',
11825
				contextmenu : 'onContextMenu',
11826
				dblclick : 'onDblClick',
11827
				paste : 'onPaste' // Doesn't work in all browsers yet
11828
			};
11829
 
11830
			function eventHandler(e, o) {
11831
				var ty = e.type;
11832
 
11833
				// Don't fire events when it's removed
11834
				if (t.removed)
11835
					return;
11836
 
11837
				// Generic event handler
11838
				if (t.onEvent.dispatch(t, e, o) !== false) {
11839
					// Specific event handler
11840
					t[lo[e.fakeType || e.type]].dispatch(t, e, o);
11841
				}
11842
			};
11843
 
11844
			// Add DOM events
11845
			each(lo, function(v, k) {
11846
				switch (k) {
11847
					case 'contextmenu':
11848
						dom.bind(t.getDoc(), k, eventHandler);
11849
						break;
11850
 
11851
					case 'paste':
11852
						dom.bind(t.getBody(), k, function(e) {
11853
							eventHandler(e);
11854
						});
11855
						break;
11856
 
11857
					case 'submit':
11858
					case 'reset':
11859
						dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
11860
						break;
11861
 
11862
					default:
11863
						dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
11864
				}
11865
			});
11866
 
11867
			dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
11868
				t.focus(true);
11869
			});
11870
 
11871
 
11872
			// Fixes bug where a specified document_base_uri could result in broken images
11873
			// This will also fix drag drop of images in Gecko
11874
			if (tinymce.isGecko) {
11875
				dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
11876
					var v;
11877
 
11878
					e = e.target;
11879
 
11880
					if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('data-mce-src')))
11881
						e.src = t.documentBaseURI.toAbsolute(v);
11882
				});
11883
			}
11884
 
11885
			// Set various midas options in Gecko
11886
			if (isGecko) {
11887
				function setOpts() {
11888
					var t = this, d = t.getDoc(), s = t.settings;
11889
 
11890
					if (isGecko && !s.readonly) {
11891
						t._refreshContentEditable();
11892
 
11893
						try {
11894
							// Try new Gecko method
11895
							d.execCommand("styleWithCSS", 0, false);
11896
						} catch (ex) {
11897
							// Use old method
11898
							if (!t._isHidden())
11899
								try {d.execCommand("useCSS", 0, true);} catch (ex) {}
11900
						}
11901
 
11902
						if (!s.table_inline_editing)
11903
							try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
11904
 
11905
						if (!s.object_resizing)
11906
							try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
11907
					}
11908
				};
11909
 
11910
				t.onBeforeExecCommand.add(setOpts);
11911
				t.onMouseDown.add(setOpts);
11912
			}
11913
 
11914
			// Add node change handlers
11915
			t.onMouseUp.add(t.nodeChanged);
11916
			//t.onClick.add(t.nodeChanged);
11917
			t.onKeyUp.add(function(ed, e) {
11918
				var c = e.keyCode;
11919
 
11920
				if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
11921
					t.nodeChanged();
11922
			});
11923
 
11924
 
11925
			// Add block quote deletion handler
11926
			t.onKeyDown.add(function(ed, e) {
11927
				// Was the BACKSPACE key pressed?
11928
				if (e.keyCode != 8)
11929
					return;
11930
 
11931
				var n = ed.selection.getRng().startContainer;
11932
				var offset = ed.selection.getRng().startOffset;
11933
 
11934
				while (n && n.nodeType && n.nodeType != 1 && n.parentNode)
11935
					n = n.parentNode;
11936
 
11937
				// Is the cursor at the beginning of a blockquote?
11938
				if (n && n.parentNode && n.parentNode.tagName === 'BLOCKQUOTE' && n.parentNode.firstChild == n && offset == 0) {
11939
					// Remove the blockquote
11940
					ed.formatter.toggle('blockquote', null, n.parentNode);
11941
 
11942
					// Move the caret to the beginning of n
11943
					var rng = ed.selection.getRng();
11944
					rng.setStart(n, 0);
11945
					rng.setEnd(n, 0);
11946
					ed.selection.setRng(rng);
11947
					ed.selection.collapse(false);
11948
				}
11949
			});
11950
 
11951
 
11952
 
11953
			// Add reset handler
11954
			t.onReset.add(function() {
11955
				t.setContent(t.startContent, {format : 'raw'});
11956
			});
11957
 
11958
			// Add shortcuts
11959
			if (s.custom_shortcuts) {
11960
				if (s.custom_undo_redo_keyboard_shortcuts) {
11961
					t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
11962
					t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
11963
				}
11964
 
11965
				// Add default shortcuts for gecko
11966
				t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
11967
				t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
11968
				t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
11969
 
11970
				// BlockFormat shortcuts keys
11971
				for (i=1; i<=6; i++)
11972
					t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
11973
 
11974
				t.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
11975
				t.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
11976
				t.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
11977
 
11978
				function find(e) {
11979
					var v = null;
11980
 
11981
					if (!e.altKey && !e.ctrlKey && !e.metaKey)
11982
						return v;
11983
 
11984
					each(t.shortcuts, function(o) {
11985
						if (tinymce.isMac && o.ctrl != e.metaKey)
11986
							return;
11987
						else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
11988
							return;
11989
 
11990
						if (o.alt != e.altKey)
11991
							return;
11992
 
11993
						if (o.shift != e.shiftKey)
11994
							return;
11995
 
11996
						if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
11997
							v = o;
11998
							return false;
11999
						}
12000
					});
12001
 
12002
					return v;
12003
				};
12004
 
12005
				t.onKeyUp.add(function(ed, e) {
12006
					var o = find(e);
12007
 
12008
					if (o)
12009
						return Event.cancel(e);
12010
				});
12011
 
12012
				t.onKeyPress.add(function(ed, e) {
12013
					var o = find(e);
12014
 
12015
					if (o)
12016
						return Event.cancel(e);
12017
				});
12018
 
12019
				t.onKeyDown.add(function(ed, e) {
12020
					var o = find(e);
12021
 
12022
					if (o) {
12023
						o.func.call(o.scope);
12024
						return Event.cancel(e);
12025
					}
12026
				});
12027
			}
12028
 
12029
			if (tinymce.isIE) {
12030
				// Fix so resize will only update the width and height attributes not the styles of an image
12031
				// It will also block mceItemNoResize items
12032
				dom.bind(t.getDoc(), 'controlselect', function(e) {
12033
					var re = t.resizeInfo, cb;
12034
 
12035
					e = e.target;
12036
 
12037
					// Don't do this action for non image elements
12038
					if (e.nodeName !== 'IMG')
12039
						return;
12040
 
12041
					if (re)
12042
						dom.unbind(re.node, re.ev, re.cb);
12043
 
12044
					if (!dom.hasClass(e, 'mceItemNoResize')) {
12045
						ev = 'resizeend';
12046
						cb = dom.bind(e, ev, function(e) {
12047
							var v;
12048
 
12049
							e = e.target;
12050
 
12051
							if (v = dom.getStyle(e, 'width')) {
12052
								dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
12053
								dom.setStyle(e, 'width', '');
12054
							}
12055
 
12056
							if (v = dom.getStyle(e, 'height')) {
12057
								dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
12058
								dom.setStyle(e, 'height', '');
12059
							}
12060
						});
12061
					} else {
12062
						ev = 'resizestart';
12063
						cb = dom.bind(e, 'resizestart', Event.cancel, Event);
12064
					}
12065
 
12066
					re = t.resizeInfo = {
12067
						node : e,
12068
						ev : ev,
12069
						cb : cb
12070
					};
12071
				});
12072
			}
12073
 
12074
			if (tinymce.isOpera) {
12075
				t.onClick.add(function(ed, e) {
12076
					Event.prevent(e);
12077
				});
12078
			}
12079
 
12080
			// Add custom undo/redo handlers
12081
			if (s.custom_undo_redo) {
12082
				function addUndo() {
12083
					t.undoManager.typing = false;
12084
					t.undoManager.add();
12085
				};
12086
 
12087
				dom.bind(t.getDoc(), 'focusout', function(e) {
12088
					if (!t.removed && t.undoManager.typing)
12089
						addUndo();
12090
				});
12091
 
12092
				// Add undo level when contents is drag/dropped within the editor
12093
				t.dom.bind(t.dom.getRoot(), 'dragend', function(e) {
12094
					addUndo();
12095
				});
12096
 
12097
				t.onKeyUp.add(function(ed, e) {
12098
					var keyCode = e.keyCode;
12099
 
12100
					if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || e.ctrlKey)
12101
						addUndo();
12102
				});
12103
 
12104
				t.onKeyDown.add(function(ed, e) {
12105
					var keyCode = e.keyCode, sel;
12106
 
12107
					if (keyCode == 8) {
12108
						sel = t.getDoc().selection;
12109
 
12110
						// Fix IE control + backspace browser bug
12111
						if (sel && sel.createRange && sel.createRange().item) {
12112
							t.undoManager.beforeChange();
12113
							ed.dom.remove(sel.createRange().item(0));
12114
							addUndo();
12115
 
12116
							return Event.cancel(e);
12117
						}
12118
					}
12119
 
12120
					// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
12121
					if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45) {
12122
						// Add position before enter key is pressed, used by IE since it still uses the default browser behavior
12123
						// Todo: Remove this once we normalize enter behavior on IE
12124
						if (tinymce.isIE && keyCode == 13)
12125
							t.undoManager.beforeChange();
12126
 
12127
						if (t.undoManager.typing)
12128
							addUndo();
12129
 
12130
						return;
12131
					}
12132
 
12133
					// If key isn't shift,ctrl,alt,capslock,metakey
12134
					if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !t.undoManager.typing) {
12135
						t.undoManager.beforeChange();
12136
						t.undoManager.typing = true;
12137
						t.undoManager.add();
12138
					}
12139
				});
12140
 
12141
				t.onMouseDown.add(function() {
12142
					if (t.undoManager.typing)
12143
						addUndo();
12144
				});
12145
			}
12146
 
12147
			// Fire a nodeChanged when the selection is changed on WebKit this fixes selection issues on iOS5
12148
			// It only fires the nodeChange event every 50ms since it would other wise update the UI when you type and it hogs the CPU
12149
			if (tinymce.isWebKit) {
12150
				dom.bind(t.getDoc(), 'selectionchange', function() {
12151
					if (t.selectionTimer) {
12152
						clearTimeout(t.selectionTimer);
12153
						t.selectionTimer = 0;
12154
					}
12155
 
12156
					t.selectionTimer = window.setTimeout(function() {
12157
						t.nodeChanged();
12158
					}, 50);
12159
				});
12160
			}
12161
 
12162
			// Bug fix for FireFox keeping styles from end of selection instead of start.
12163
			if (tinymce.isGecko) {
12164
				function getAttributeApplyFunction() {
12165
					var template = t.dom.getAttribs(t.selection.getStart().cloneNode(false));
12166
 
12167
					return function() {
12168
						var target = t.selection.getStart();
12169
 
12170
						if (target !== t.getBody()) {
12171
							t.dom.removeAllAttribs(target);
12172
 
12173
							each(template, function(attr) {
12174
								target.setAttributeNode(attr.cloneNode(true));
12175
							});
12176
						}
12177
					};
12178
				}
12179
 
12180
				function isSelectionAcrossElements() {
12181
					var s = t.selection;
12182
 
12183
					return !s.isCollapsed() && s.getStart() != s.getEnd();
12184
				}
12185
 
12186
				t.onKeyPress.add(function(ed, e) {
12187
					var applyAttributes;
12188
 
12189
					if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
12190
						applyAttributes = getAttributeApplyFunction();
12191
						t.getDoc().execCommand('delete', false, null);
12192
						applyAttributes();
12193
 
12194
						return Event.cancel(e);
12195
					}
12196
				});
12197
 
12198
				t.dom.bind(t.getDoc(), 'cut', function(e) {
12199
					var applyAttributes;
12200
 
12201
					if (isSelectionAcrossElements()) {
12202
						applyAttributes = getAttributeApplyFunction();
12203
						t.onKeyUp.addToTop(Event.cancel, Event);
12204
 
12205
						setTimeout(function() {
12206
							applyAttributes();
12207
							t.onKeyUp.remove(Event.cancel, Event);
12208
						}, 0);
12209
					}
12210
				});
12211
			}
12212
		},
12213
 
12214
		_refreshContentEditable : function() {
12215
			var self = this, body, parent;
12216
 
12217
			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
12218
			if (self._isHidden()) {
12219
				body = self.getBody();
12220
				parent = body.parentNode;
12221
 
12222
				parent.removeChild(body);
12223
				parent.appendChild(body);
12224
 
12225
				body.focus();
12226
			}
12227
		},
12228
 
12229
		_isHidden : function() {
12230
			var s;
12231
 
12232
			if (!isGecko)
12233
				return 0;
12234
 
12235
			// Weird, wheres that cursor selection?
12236
			s = this.selection.getSel();
12237
			return (!s || !s.rangeCount || s.rangeCount == 0);
12238
		}
12239
	});
12240
})(tinymce);
12241
 
12242
(function(tinymce) {
12243
	// Added for compression purposes
12244
	var each = tinymce.each, undefined, TRUE = true, FALSE = false;
12245
 
12246
	tinymce.EditorCommands = function(editor) {
12247
		var dom = editor.dom,
12248
			selection = editor.selection,
12249
			commands = {state: {}, exec : {}, value : {}},
12250
			settings = editor.settings,
12251
			formatter = editor.formatter,
12252
			bookmark;
12253
 
12254
		function execCommand(command, ui, value) {
12255
			var func;
12256
 
12257
			command = command.toLowerCase();
12258
			if (func = commands.exec[command]) {
12259
				func(command, ui, value);
12260
				return TRUE;
12261
			}
12262
 
12263
			return FALSE;
12264
		};
12265
 
12266
		function queryCommandState(command) {
12267
			var func;
12268
 
12269
			command = command.toLowerCase();
12270
			if (func = commands.state[command])
12271
				return func(command);
12272
 
12273
			return -1;
12274
		};
12275
 
12276
		function queryCommandValue(command) {
12277
			var func;
12278
 
12279
			command = command.toLowerCase();
12280
			if (func = commands.value[command])
12281
				return func(command);
12282
 
12283
			return FALSE;
12284
		};
12285
 
12286
		function addCommands(command_list, type) {
12287
			type = type || 'exec';
12288
 
12289
			each(command_list, function(callback, command) {
12290
				each(command.toLowerCase().split(','), function(command) {
12291
					commands[type][command] = callback;
12292
				});
12293
			});
12294
		};
12295
 
12296
		// Expose public methods
12297
		tinymce.extend(this, {
12298
			execCommand : execCommand,
12299
			queryCommandState : queryCommandState,
12300
			queryCommandValue : queryCommandValue,
12301
			addCommands : addCommands
12302
		});
12303
 
12304
		// Private methods
12305
 
12306
		function execNativeCommand(command, ui, value) {
12307
			if (ui === undefined)
12308
				ui = FALSE;
12309
 
12310
			if (value === undefined)
12311
				value = null;
12312
 
12313
			return editor.getDoc().execCommand(command, ui, value);
12314
		};
12315
 
12316
		function isFormatMatch(name) {
12317
			return formatter.match(name);
12318
		};
12319
 
12320
		function toggleFormat(name, value) {
12321
			formatter.toggle(name, value ? {value : value} : undefined);
12322
		};
12323
 
12324
		function storeSelection(type) {
12325
			bookmark = selection.getBookmark(type);
12326
		};
12327
 
12328
		function restoreSelection() {
12329
			selection.moveToBookmark(bookmark);
12330
		};
12331
 
12332
		// Add execCommand overrides
12333
		addCommands({
12334
			// Ignore these, added for compatibility
12335
			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
12336
 
12337
			// Add undo manager logic
12338
			'mceEndUndoLevel,mceAddUndoLevel' : function() {
12339
				editor.undoManager.add();
12340
			},
12341
 
12342
			'Cut,Copy,Paste' : function(command) {
12343
				var doc = editor.getDoc(), failed;
12344
 
12345
				// Try executing the native command
12346
				try {
12347
					execNativeCommand(command);
12348
				} catch (ex) {
12349
					// Command failed
12350
					failed = TRUE;
12351
				}
12352
 
12353
				// Present alert message about clipboard access not being available
12354
				if (failed || !doc.queryCommandSupported(command)) {
12355
					if (tinymce.isGecko) {
12356
						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
12357
							if (state)
12358
								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
12359
						});
12360
					} else
12361
						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
12362
				}
12363
			},
12364
 
12365
			// Override unlink command
12366
			unlink : function(command) {
12367
				if (selection.isCollapsed())
12368
					selection.select(selection.getNode());
12369
 
12370
				execNativeCommand(command);
12371
				selection.collapse(FALSE);
12372
			},
12373
 
12374
			// Override justify commands to use the text formatter engine
12375
			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12376
				var align = command.substring(7);
12377
 
12378
				// Remove all other alignments first
12379
				each('left,center,right,full'.split(','), function(name) {
12380
					if (align != name)
12381
						formatter.remove('align' + name);
12382
				});
12383
 
12384
				toggleFormat('align' + align);
12385
				execCommand('mceRepaint');
12386
			},
12387
 
12388
			// Override list commands to fix WebKit bug
12389
			'InsertUnorderedList,InsertOrderedList' : function(command) {
12390
				var listElm, listParent;
12391
 
12392
				execNativeCommand(command);
12393
 
12394
				// WebKit produces lists within block elements so we need to split them
12395
				// we will replace the native list creation logic to custom logic later on
12396
				// TODO: Remove this when the list creation logic is removed
12397
				listElm = dom.getParent(selection.getNode(), 'ol,ul');
12398
				if (listElm) {
12399
					listParent = listElm.parentNode;
12400
 
12401
					// If list is within a text block then split that block
12402
					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
12403
						storeSelection();
12404
						dom.split(listParent, listElm);
12405
						restoreSelection();
12406
					}
12407
				}
12408
			},
12409
 
12410
			// Override commands to use the text formatter engine
12411
			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12412
				toggleFormat(command);
12413
			},
12414
 
12415
			// Override commands to use the text formatter engine
12416
			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
12417
				toggleFormat(command, value);
12418
			},
12419
 
12420
			FontSize : function(command, ui, value) {
12421
				var fontClasses, fontSizes;
12422
 
12423
				// Convert font size 1-7 to styles
12424
				if (value >= 1 && value <= 7) {
12425
					fontSizes = tinymce.explode(settings.font_size_style_values);
12426
					fontClasses = tinymce.explode(settings.font_size_classes);
12427
 
12428
					if (fontClasses)
12429
						value = fontClasses[value - 1] || value;
12430
					else
12431
						value = fontSizes[value - 1] || value;
12432
				}
12433
 
12434
				toggleFormat(command, value);
12435
			},
12436
 
12437
			RemoveFormat : function(command) {
12438
				formatter.remove(command);
12439
			},
12440
 
12441
			mceBlockQuote : function(command) {
12442
				toggleFormat('blockquote');
12443
			},
12444
 
12445
			FormatBlock : function(command, ui, value) {
12446
				return toggleFormat(value || 'p');
12447
			},
12448
 
12449
			mceCleanup : function() {
12450
				var bookmark = selection.getBookmark();
12451
 
12452
				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
12453
 
12454
				selection.moveToBookmark(bookmark);
12455
			},
12456
 
12457
			mceRemoveNode : function(command, ui, value) {
12458
				var node = value || selection.getNode();
12459
 
12460
				// Make sure that the body node isn't removed
12461
				if (node != editor.getBody()) {
12462
					storeSelection();
12463
					editor.dom.remove(node, TRUE);
12464
					restoreSelection();
12465
				}
12466
			},
12467
 
12468
			mceSelectNodeDepth : function(command, ui, value) {
12469
				var counter = 0;
12470
 
12471
				dom.getParent(selection.getNode(), function(node) {
12472
					if (node.nodeType == 1 && counter++ == value) {
12473
						selection.select(node);
12474
						return FALSE;
12475
					}
12476
				}, editor.getBody());
12477
			},
12478
 
12479
			mceSelectNode : function(command, ui, value) {
12480
				selection.select(value);
12481
			},
12482
 
12483
			mceInsertContent : function(command, ui, value) {
12484
				var parser, serializer, parentNode, rootNode, fragment, args,
12485
					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
12486
 
12487
				// Setup parser and serializer
12488
				parser = editor.parser;
12489
				serializer = new tinymce.html.Serializer({}, editor.schema);
12490
				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
12491
 
12492
				// Run beforeSetContent handlers on the HTML to be inserted
12493
				args = {content: value, format: 'html'};
12494
				selection.onBeforeSetContent.dispatch(selection, args);
12495
				value = args.content;
12496
 
12497
				// Add caret at end of contents if it's missing
12498
				if (value.indexOf('{$caret}') == -1)
12499
					value += '{$caret}';
12500
 
12501
				// Replace the caret marker with a span bookmark element
12502
				value = value.replace(/\{\$caret\}/, bookmarkHtml);
12503
 
12504
				// Insert node maker where we will insert the new HTML and get it's parent
12505
				if (!selection.isCollapsed())
12506
					editor.getDoc().execCommand('Delete', false, null);
12507
 
12508
				parentNode = selection.getNode();
12509
 
12510
				// Parse the fragment within the context of the parent node
12511
				args = {context : parentNode.nodeName.toLowerCase()};
12512
				fragment = parser.parse(value, args);
12513
 
12514
				// Move the caret to a more suitable location
12515
				node = fragment.lastChild;
12516
				if (node.attr('id') == 'mce_marker') {
12517
					marker = node;
12518
 
12519
					for (node = node.prev; node; node = node.walk(true)) {
12520
						if (node.type == 3 || !dom.isBlock(node.name)) {
12521
							node.parent.insert(marker, node, node.name === 'br');
12522
							break;
12523
						}
12524
					}
12525
				}
12526
 
12527
				// If parser says valid we can insert the contents into that parent
12528
				if (!args.invalid) {
12529
					value = serializer.serialize(fragment);
12530
 
12531
					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
12532
					node = parentNode.firstChild;
12533
					node2 = parentNode.lastChild;
12534
					if (!node || (node === node2 && node.nodeName === 'BR'))
12535
						dom.setHTML(parentNode, value);
12536
					else
12537
						selection.setContent(value);
12538
				} else {
12539
					// If the fragment was invalid within that context then we need
12540
					// to parse and process the parent it's inserted into
12541
 
12542
					// Insert bookmark node and get the parent
12543
					selection.setContent(bookmarkHtml);
12544
					parentNode = editor.selection.getNode();
12545
					rootNode = editor.getBody();
12546
 
12547
					// Opera will return the document node when selection is in root
12548
					if (parentNode.nodeType == 9)
12549
						parentNode = node = rootNode;
12550
					else
12551
						node = parentNode;
12552
 
12553
					// Find the ancestor just before the root element
12554
					while (node !== rootNode) {
12555
						parentNode = node;
12556
						node = node.parentNode;
12557
					}
12558
 
12559
					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
12560
					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
12561
					value = serializer.serialize(
12562
						parser.parse(
12563
							// Need to replace by using a function since $ in the contents would otherwise be a problem
12564
							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
12565
								return serializer.serialize(fragment);
12566
							})
12567
						)
12568
					);
12569
 
12570
					// Set the inner/outer HTML depending on if we are in the root or not
12571
					if (parentNode == rootNode)
12572
						dom.setHTML(rootNode, value);
12573
					else
12574
						dom.setOuterHTML(parentNode, value);
12575
				}
12576
 
12577
				marker = dom.get('mce_marker');
12578
 
12579
				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
12580
				nodeRect = dom.getRect(marker);
12581
				viewPortRect = dom.getViewPort(editor.getWin());
12582
 
12583
				// Check if node is out side the viewport if it is then scroll to it
12584
				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
12585
					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
12586
					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
12587
					viewportBodyElement.scrollLeft = nodeRect.x;
12588
					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
12589
				}
12590
 
12591
				// Move selection before marker and remove it
12592
				rng = dom.createRng();
12593
 
12594
				// If previous sibling is a text node set the selection to the end of that node
12595
				node = marker.previousSibling;
12596
				if (node && node.nodeType == 3) {
12597
					rng.setStart(node, node.nodeValue.length);
12598
				} else {
12599
					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
12600
					rng.setStartBefore(marker);
12601
					rng.setEndBefore(marker);
12602
				}
12603
 
12604
				// Remove the marker node and set the new range
12605
				dom.remove(marker);
12606
				selection.setRng(rng);
12607
 
12608
				// Dispatch after event and add any visual elements needed
12609
				selection.onSetContent.dispatch(selection, args);
12610
				editor.addVisual();
12611
			},
12612
 
12613
			mceInsertRawHTML : function(command, ui, value) {
12614
				selection.setContent('tiny_mce_marker');
12615
				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
12616
			},
12617
 
12618
			mceSetContent : function(command, ui, value) {
12619
				editor.setContent(value);
12620
			},
12621
 
12622
			'Indent,Outdent' : function(command) {
12623
				var intentValue, indentUnit, value;
12624
 
12625
				// Setup indent level
12626
				intentValue = settings.indentation;
12627
				indentUnit = /[a-z%]+$/i.exec(intentValue);
12628
				intentValue = parseInt(intentValue);
12629
 
12630
				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
12631
					each(selection.getSelectedBlocks(), function(element) {
12632
						if (command == 'outdent') {
12633
							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
12634
							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
12635
						} else
12636
							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
12637
					});
12638
				} else
12639
					execNativeCommand(command);
12640
			},
12641
 
12642
			mceRepaint : function() {
12643
				var bookmark;
12644
 
12645
				if (tinymce.isGecko) {
12646
					try {
12647
						storeSelection(TRUE);
12648
 
12649
						if (selection.getSel())
12650
							selection.getSel().selectAllChildren(editor.getBody());
12651
 
12652
						selection.collapse(TRUE);
12653
						restoreSelection();
12654
					} catch (ex) {
12655
						// Ignore
12656
					}
12657
				}
12658
			},
12659
 
12660
			mceToggleFormat : function(command, ui, value) {
12661
				formatter.toggle(value);
12662
			},
12663
 
12664
			InsertHorizontalRule : function() {
12665
				editor.execCommand('mceInsertContent', false, '<hr />');
12666
			},
12667
 
12668
			mceToggleVisualAid : function() {
12669
				editor.hasVisual = !editor.hasVisual;
12670
				editor.addVisual();
12671
			},
12672
 
12673
			mceReplaceContent : function(command, ui, value) {
12674
				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
12675
			},
12676
 
12677
			mceInsertLink : function(command, ui, value) {
12678
				var anchor;
12679
 
12680
				if (typeof(value) == 'string')
12681
					value = {href : value};
12682
 
12683
				anchor = dom.getParent(selection.getNode(), 'a');
12684
 
12685
				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
12686
				value.href = value.href.replace(' ', '%20');
12687
 
12688
				// Remove existing links if there could be child links or that the href isn't specified
12689
				if (!anchor || !value.href) {
12690
					formatter.remove('link');
12691
				}
12692
 
12693
				// Apply new link to selection
12694
				if (value.href) {
12695
					formatter.apply('link', value, anchor);
12696
				}
12697
			},
12698
 
12699
			selectAll : function() {
12700
				var root = dom.getRoot(), rng = dom.createRng();
12701
 
12702
				rng.setStart(root, 0);
12703
				rng.setEnd(root, root.childNodes.length);
12704
 
12705
				editor.selection.setRng(rng);
12706
			}
12707
		});
12708
 
12709
		// Add queryCommandState overrides
12710
		addCommands({
12711
			// Override justify commands
12712
			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
12713
				return isFormatMatch('align' + command.substring(7));
12714
			},
12715
 
12716
			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
12717
				return isFormatMatch(command);
12718
			},
12719
 
12720
			mceBlockQuote : function() {
12721
				return isFormatMatch('blockquote');
12722
			},
12723
 
12724
			Outdent : function() {
12725
				var node;
12726
 
12727
				if (settings.inline_styles) {
12728
					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12729
						return TRUE;
12730
 
12731
					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
12732
						return TRUE;
12733
				}
12734
 
12735
				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
12736
			},
12737
 
12738
			'InsertUnorderedList,InsertOrderedList' : function(command) {
12739
				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
12740
			}
12741
		}, 'state');
12742
 
12743
		// Add queryCommandValue overrides
12744
		addCommands({
12745
			'FontSize,FontName' : function(command) {
12746
				var value = 0, parent;
12747
 
12748
				if (parent = dom.getParent(selection.getNode(), 'span')) {
12749
					if (command == 'fontsize')
12750
						value = parent.style.fontSize;
12751
					else
12752
						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
12753
				}
12754
 
12755
				return value;
12756
			}
12757
		}, 'value');
12758
 
12759
		// Add undo manager logic
12760
		if (settings.custom_undo_redo) {
12761
			addCommands({
12762
				Undo : function() {
12763
					editor.undoManager.undo();
12764
				},
12765
 
12766
				Redo : function() {
12767
					editor.undoManager.redo();
12768
				}
12769
			});
12770
		}
12771
	};
12772
})(tinymce);
12773
 
12774
(function(tinymce) {
12775
	var Dispatcher = tinymce.util.Dispatcher;
12776
 
12777
	tinymce.UndoManager = function(editor) {
12778
		var self, index = 0, data = [], beforeBookmark;
12779
 
12780
		function getContent() {
12781
			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}));
12782
		};
12783
 
12784
		return self = {
12785
			typing : false,
12786
 
12787
			onAdd : new Dispatcher(self),
12788
 
12789
			onUndo : new Dispatcher(self),
12790
 
12791
			onRedo : new Dispatcher(self),
12792
 
12793
			beforeChange : function() {
12794
				beforeBookmark = editor.selection.getBookmark(2, true);
12795
			},
12796
 
12797
			add : function(level) {
12798
				var i, settings = editor.settings, lastLevel;
12799
 
12800
				level = level || {};
12801
				level.content = getContent();
12802
 
12803
				// Add undo level if needed
12804
				lastLevel = data[index];
12805
				if (lastLevel && lastLevel.content == level.content)
12806
					return null;
12807
 
12808
				// Set before bookmark on previous level
12809
				if (data[index])
12810
					data[index].beforeBookmark = beforeBookmark;
12811
 
12812
				// Time to compress
12813
				if (settings.custom_undo_redo_levels) {
12814
					if (data.length > settings.custom_undo_redo_levels) {
12815
						for (i = 0; i < data.length - 1; i++)
12816
							data[i] = data[i + 1];
12817
 
12818
						data.length--;
12819
						index = data.length;
12820
					}
12821
				}
12822
 
12823
				// Get a non intrusive normalized bookmark
12824
				level.bookmark = editor.selection.getBookmark(2, true);
12825
 
12826
				// Crop array if needed
12827
				if (index < data.length - 1)
12828
					data.length = index + 1;
12829
 
12830
				data.push(level);
12831
				index = data.length - 1;
12832
 
12833
				self.onAdd.dispatch(self, level);
12834
				editor.isNotDirty = 0;
12835
 
12836
				return level;
12837
			},
12838
 
12839
			undo : function() {
12840
				var level, i;
12841
 
12842
				if (self.typing) {
12843
					self.add();
12844
					self.typing = false;
12845
				}
12846
 
12847
				if (index > 0) {
12848
					level = data[--index];
12849
 
12850
					editor.setContent(level.content, {format : 'raw'});
12851
					editor.selection.moveToBookmark(level.beforeBookmark);
12852
 
12853
					self.onUndo.dispatch(self, level);
12854
				}
12855
 
12856
				return level;
12857
			},
12858
 
12859
			redo : function() {
12860
				var level;
12861
 
12862
				if (index < data.length - 1) {
12863
					level = data[++index];
12864
 
12865
					editor.setContent(level.content, {format : 'raw'});
12866
					editor.selection.moveToBookmark(level.bookmark);
12867
 
12868
					self.onRedo.dispatch(self, level);
12869
				}
12870
 
12871
				return level;
12872
			},
12873
 
12874
			clear : function() {
12875
				data = [];
12876
				index = 0;
12877
				self.typing = false;
12878
			},
12879
 
12880
			hasUndo : function() {
12881
				return index > 0 || this.typing;
12882
			},
12883
 
12884
			hasRedo : function() {
12885
				return index < data.length - 1 && !this.typing;
12886
			}
12887
		};
12888
	};
12889
})(tinymce);
12890
 
12891
(function(tinymce) {
12892
	// Shorten names
12893
	var Event = tinymce.dom.Event,
12894
		isIE = tinymce.isIE,
12895
		isGecko = tinymce.isGecko,
12896
		isOpera = tinymce.isOpera,
12897
		each = tinymce.each,
12898
		extend = tinymce.extend,
12899
		TRUE = true,
12900
		FALSE = false;
12901
 
12902
	function cloneFormats(node) {
12903
		var clone, temp, inner;
12904
 
12905
		do {
12906
			if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
12907
				if (clone) {
12908
					temp = node.cloneNode(false);
12909
					temp.appendChild(clone);
12910
					clone = temp;
12911
				} else {
12912
					clone = inner = node.cloneNode(false);
12913
				}
12914
 
12915
				clone.removeAttribute('id');
12916
			}
12917
		} while (node = node.parentNode);
12918
 
12919
		if (clone)
12920
			return {wrapper : clone, inner : inner};
12921
	};
12922
 
12923
	// Checks if the selection/caret is at the end of the specified block element
12924
	function isAtEnd(rng, par) {
12925
		var rng2 = par.ownerDocument.createRange();
12926
 
12927
		rng2.setStart(rng.endContainer, rng.endOffset);
12928
		rng2.setEndAfter(par);
12929
 
12930
		// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
12931
		return rng2.cloneContents().textContent.length == 0;
12932
	};
12933
 
12934
	function splitList(selection, dom, li) {
12935
		var listBlock, block;
12936
 
12937
		if (dom.isEmpty(li)) {
12938
			listBlock = dom.getParent(li, 'ul,ol');
12939
 
12940
			if (!dom.getParent(listBlock.parentNode, 'ul,ol')) {
12941
				dom.split(listBlock, li);
12942
				block = dom.create('p', 0, '<br data-mce-bogus="1" />');
12943
				dom.replace(block, li);
12944
				selection.select(block, 1);
12945
			}
12946
 
12947
			return FALSE;
12948
		}
12949
 
12950
		return TRUE;
12951
	};
12952
 
12953
	tinymce.create('tinymce.ForceBlocks', {
12954
		ForceBlocks : function(ed) {
12955
			var t = this, s = ed.settings, elm;
12956
 
12957
			t.editor = ed;
12958
			t.dom = ed.dom;
12959
			elm = (s.forced_root_block || 'p').toLowerCase();
12960
			s.element = elm.toUpperCase();
12961
 
12962
			ed.onPreInit.add(t.setup, t);
12963
		},
12964
 
12965
		setup : function() {
12966
			var t = this, ed = t.editor, s = ed.settings, dom = ed.dom, selection = ed.selection, blockElements = ed.schema.getBlockElements();
12967
 
12968
			// Force root blocks
12969
			if (s.forced_root_block) {
12970
				function addRootBlocks() {
12971
					var node = selection.getStart(), rootNode = ed.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF;
12972
 
12973
					if (!node || node.nodeType !== 1)
12974
						return;
12975
 
12976
					// Check if node is wrapped in block
12977
					while (node != rootNode) {
12978
						if (blockElements[node.nodeName])
12979
							return;
12980
 
12981
						node = node.parentNode;
12982
					}
12983
 
12984
					// Get current selection
12985
					rng = selection.getRng();
12986
					if (rng.setStart) {
12987
						startContainer = rng.startContainer;
12988
						startOffset = rng.startOffset;
12989
						endContainer = rng.endContainer;
12990
						endOffset = rng.endOffset;
12991
					} else {
12992
						// Force control range into text range
12993
						if (rng.item) {
12994
							rng = ed.getDoc().body.createTextRange();
12995
							rng.moveToElementText(rng.item(0));
12996
						}
12997
 
12998
						tmpRng = rng.duplicate();
12999
						tmpRng.collapse(true);
13000
						startOffset = tmpRng.move('character', offset) * -1;
13001
 
13002
						if (!tmpRng.collapsed) {
13003
							tmpRng = rng.duplicate();
13004
							tmpRng.collapse(false);
13005
							endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
13006
						}
13007
					}
13008
 
13009
					// Wrap non block elements and text nodes
13010
					for (node = rootNode.firstChild; node; node) {
13011
						if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
13012
							if (!rootBlockNode) {
13013
								rootBlockNode = dom.create(s.forced_root_block);
13014
								node.parentNode.insertBefore(rootBlockNode, node);
13015
							}
13016
 
13017
							tempNode = node;
13018
							node = node.nextSibling;
13019
							rootBlockNode.appendChild(tempNode);
13020
						} else {
13021
							rootBlockNode = null;
13022
							node = node.nextSibling;
13023
						}
13024
					}
13025
 
13026
					if (rng.setStart) {
13027
						rng.setStart(startContainer, startOffset);
13028
						rng.setEnd(endContainer, endOffset);
13029
						selection.setRng(rng);
13030
					} else {
13031
						try {
13032
							rng = ed.getDoc().body.createTextRange();
13033
							rng.moveToElementText(rootNode);
13034
							rng.collapse(true);
13035
							rng.moveStart('character', startOffset);
13036
 
13037
							if (endOffset > 0)
13038
								rng.moveEnd('character', endOffset);
13039
 
13040
							rng.select();
13041
						} catch (ex) {
13042
							// Ignore
13043
						}
13044
					}
13045
 
13046
					ed.nodeChanged();
13047
				};
13048
 
13049
				ed.onKeyUp.add(addRootBlocks);
13050
				ed.onClick.add(addRootBlocks);
13051
			}
13052
 
13053
			if (s.force_br_newlines) {
13054
				// Force IE to produce BRs on enter
13055
				if (isIE) {
13056
					ed.onKeyPress.add(function(ed, e) {
13057
						var n;
13058
 
13059
						if (e.keyCode == 13 && selection.getNode().nodeName != 'LI') {
13060
							selection.setContent('<br id="__" /> ', {format : 'raw'});
13061
							n = dom.get('__');
13062
							n.removeAttribute('id');
13063
							selection.select(n);
13064
							selection.collapse();
13065
							return Event.cancel(e);
13066
						}
13067
					});
13068
				}
13069
			}
13070
 
13071
			if (s.force_p_newlines) {
13072
				if (!isIE) {
13073
					ed.onKeyPress.add(function(ed, e) {
13074
						if (e.keyCode == 13 && !e.shiftKey && !t.insertPara(e))
13075
							Event.cancel(e);
13076
					});
13077
				} else {
13078
					// Ungly hack to for IE to preserve the formatting when you press
13079
					// enter at the end of a block element with formatted contents
13080
					// This logic overrides the browsers default logic with
13081
					// custom logic that enables us to control the output
13082
					tinymce.addUnload(function() {
13083
						t._previousFormats = 0; // Fix IE leak
13084
					});
13085
 
13086
					ed.onKeyPress.add(function(ed, e) {
13087
						t._previousFormats = 0;
13088
 
13089
						// Clone the current formats, this will later be applied to the new block contents
13090
						if (e.keyCode == 13 && !e.shiftKey && ed.selection.isCollapsed() && s.keep_styles)
13091
							t._previousFormats = cloneFormats(ed.selection.getStart());
13092
					});
13093
 
13094
					ed.onKeyUp.add(function(ed, e) {
13095
						// Let IE break the element and the wrap the new caret location in the previous formats
13096
						if (e.keyCode == 13 && !e.shiftKey) {
13097
							var parent = ed.selection.getStart(), fmt = t._previousFormats;
13098
 
13099
							// Parent is an empty block
13100
							if (!parent.hasChildNodes() && fmt) {
13101
								parent = dom.getParent(parent, dom.isBlock);
13102
 
13103
								if (parent && parent.nodeName != 'LI') {
13104
									parent.innerHTML = '';
13105
 
13106
									if (t._previousFormats) {
13107
										parent.appendChild(fmt.wrapper);
13108
										fmt.inner.innerHTML = '\uFEFF';
13109
									} else
13110
										parent.innerHTML = '\uFEFF';
13111
 
13112
									selection.select(parent, 1);
13113
									selection.collapse(true);
13114
									ed.getDoc().execCommand('Delete', false, null);
13115
									t._previousFormats = 0;
13116
								}
13117
							}
13118
						}
13119
					});
13120
				}
13121
 
13122
				if (isGecko) {
13123
					ed.onKeyDown.add(function(ed, e) {
13124
						if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
13125
							t.backspaceDelete(e, e.keyCode == 8);
13126
					});
13127
				}
13128
			}
13129
 
13130
			// Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
13131
			if (tinymce.isWebKit) {
13132
				function insertBr(ed) {
13133
					var rng = selection.getRng(), br, div = dom.create('div', null, ' '), divYPos, vpHeight = dom.getViewPort(ed.getWin()).h;
13134
 
13135
					// Insert BR element
13136
					rng.insertNode(br = dom.create('br'));
13137
 
13138
					// Place caret after BR
13139
					rng.setStartAfter(br);
13140
					rng.setEndAfter(br);
13141
					selection.setRng(rng);
13142
 
13143
					// Could not place caret after BR then insert an nbsp entity and move the caret
13144
					if (selection.getSel().focusNode == br.previousSibling) {
13145
						selection.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
13146
						selection.collapse(TRUE);
13147
					}
13148
 
13149
					// Create a temporary DIV after the BR and get the position as it
13150
					// seems like getPos() returns 0 for text nodes and BR elements.
13151
					dom.insertAfter(div, br);
13152
					divYPos = dom.getPos(div).y;
13153
					dom.remove(div);
13154
 
13155
					// Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
13156
					if (divYPos > vpHeight) // It is not necessary to scroll if the DIV is inside the view port.
13157
						ed.getWin().scrollTo(0, divYPos);
13158
				};
13159
 
13160
				ed.onKeyPress.add(function(ed, e) {
13161
					if (e.keyCode == 13 && (e.shiftKey || (s.force_br_newlines && !dom.getParent(selection.getNode(), 'h1,h2,h3,h4,h5,h6,ol,ul')))) {
13162
						insertBr(ed);
13163
						Event.cancel(e);
13164
					}
13165
				});
13166
			}
13167
 
13168
			// IE specific fixes
13169
			if (isIE) {
13170
				// Replaces IE:s auto generated paragraphs with the specified element name
13171
				if (s.element != 'P') {
13172
					ed.onKeyPress.add(function(ed, e) {
13173
						t.lastElm = selection.getNode().nodeName;
13174
					});
13175
 
13176
					ed.onKeyUp.add(function(ed, e) {
13177
						var bl, n = selection.getNode(), b = ed.getBody();
13178
 
13179
						if (b.childNodes.length === 1 && n.nodeName == 'P') {
13180
							n = dom.rename(n, s.element);
13181
							selection.select(n);
13182
							selection.collapse();
13183
							ed.nodeChanged();
13184
						} else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
13185
							bl = dom.getParent(n, 'p');
13186
 
13187
							if (bl) {
13188
								dom.rename(bl, s.element);
13189
								ed.nodeChanged();
13190
							}
13191
						}
13192
					});
13193
				}
13194
			}
13195
		},
13196
 
13197
		getParentBlock : function(n) {
13198
			var d = this.dom;
13199
 
13200
			return d.getParent(n, d.isBlock);
13201
		},
13202
 
13203
		insertPara : function(e) {
13204
			var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
13205
			var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
13206
 
13207
			ed.undoManager.beforeChange();
13208
 
13209
			// If root blocks are forced then use Operas default behavior since it's really good
13210
// Removed due to bug: #1853816
13211
//			if (se.forced_root_block && isOpera)
13212
//				return TRUE;
13213
 
13214
			// Setup before range
13215
			rb = d.createRange();
13216
 
13217
			// If is before the first block element and in body, then move it into first block element
13218
			rb.setStart(s.anchorNode, s.anchorOffset);
13219
			rb.collapse(TRUE);
13220
 
13221
			// Setup after range
13222
			ra = d.createRange();
13223
 
13224
			// If is before the first block element and in body, then move it into first block element
13225
			ra.setStart(s.focusNode, s.focusOffset);
13226
			ra.collapse(TRUE);
13227
 
13228
			// Setup start/end points
13229
			dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
13230
			sn = dir ? s.anchorNode : s.focusNode;
13231
			so = dir ? s.anchorOffset : s.focusOffset;
13232
			en = dir ? s.focusNode : s.anchorNode;
13233
			eo = dir ? s.focusOffset : s.anchorOffset;
13234
 
13235
			// If selection is in empty table cell
13236
			if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
13237
				if (sn.firstChild.nodeName == 'BR')
13238
					dom.remove(sn.firstChild); // Remove BR
13239
 
13240
				// Create two new block elements
13241
				if (sn.childNodes.length == 0) {
13242
					ed.dom.add(sn, se.element, null, '<br />');
13243
					aft = ed.dom.add(sn, se.element, null, '<br />');
13244
				} else {
13245
					n = sn.innerHTML;
13246
					sn.innerHTML = '';
13247
					ed.dom.add(sn, se.element, null, n);
13248
					aft = ed.dom.add(sn, se.element, null, '<br />');
13249
				}
13250
 
13251
				// Move caret into the last one
13252
				r = d.createRange();
13253
				r.selectNodeContents(aft);
13254
				r.collapse(1);
13255
				ed.selection.setRng(r);
13256
 
13257
				return FALSE;
13258
			}
13259
 
13260
			// If the caret is in an invalid location in FF we need to move it into the first block
13261
			if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
13262
				sn = en = sn.firstChild;
13263
				so = eo = 0;
13264
				rb = d.createRange();
13265
				rb.setStart(sn, 0);
13266
				ra = d.createRange();
13267
				ra.setStart(en, 0);
13268
			}
13269
 
13270
			// If the body is totally empty add a BR element this might happen on webkit
13271
			if (!d.body.hasChildNodes()) {
13272
				d.body.appendChild(dom.create('br'));
13273
			}
13274
 
13275
			// Never use body as start or end node
13276
			sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13277
			sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
13278
			en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
13279
			en = en.nodeName == "BODY" ? en.firstChild : en;
13280
 
13281
			// Get start and end blocks
13282
			sb = t.getParentBlock(sn);
13283
			eb = t.getParentBlock(en);
13284
			bn = sb ? sb.nodeName : se.element; // Get block name to create
13285
 
13286
			// Return inside list use default browser behavior
13287
			if (n = t.dom.getParent(sb, 'li,pre')) {
13288
				if (n.nodeName == 'LI')
13289
					return splitList(ed.selection, t.dom, n);
13290
 
13291
				return TRUE;
13292
			}
13293
 
13294
			// If caption or absolute layers then always generate new blocks within
13295
			if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13296
				bn = se.element;
13297
				sb = null;
13298
			}
13299
 
13300
			// If caption or absolute layers then always generate new blocks within
13301
			if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
13302
				bn = se.element;
13303
				eb = null;
13304
			}
13305
 
13306
			// Use P instead
13307
			if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
13308
				bn = se.element;
13309
				sb = eb = null;
13310
			}
13311
 
13312
			// Setup new before and after blocks
13313
			bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
13314
			aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
13315
 
13316
			// Remove id from after clone
13317
			aft.removeAttribute('id');
13318
 
13319
			// Is header and cursor is at the end, then force paragraph under
13320
			if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
13321
				aft = ed.dom.create(se.element);
13322
 
13323
			// Find start chop node
13324
			n = sc = sn;
13325
			do {
13326
				if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13327
					break;
13328
 
13329
				sc = n;
13330
			} while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
13331
 
13332
			// Find end chop node
13333
			n = ec = en;
13334
			do {
13335
				if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
13336
					break;
13337
 
13338
				ec = n;
13339
			} while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
13340
 
13341
			// Place first chop part into before block element
13342
			if (sc.nodeName == bn)
13343
				rb.setStart(sc, 0);
13344
			else
13345
				rb.setStartBefore(sc);
13346
 
13347
			rb.setEnd(sn, so);
13348
			bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13349
 
13350
			// Place secnd chop part within new block element
13351
			try {
13352
				ra.setEndAfter(ec);
13353
			} catch(ex) {
13354
				//console.debug(s.focusNode, s.focusOffset);
13355
			}
13356
 
13357
			ra.setStart(en, eo);
13358
			aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
13359
 
13360
			// Create range around everything
13361
			r = d.createRange();
13362
			if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
13363
				r.setStartBefore(sc.parentNode);
13364
			} else {
13365
				if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
13366
					r.setStartBefore(rb.startContainer);
13367
				else
13368
					r.setStart(rb.startContainer, rb.startOffset);
13369
			}
13370
 
13371
			if (!ec.nextSibling && ec.parentNode.nodeName == bn)
13372
				r.setEndAfter(ec.parentNode);
13373
			else
13374
				r.setEnd(ra.endContainer, ra.endOffset);
13375
 
13376
			// Delete and replace it with new block elements
13377
			r.deleteContents();
13378
 
13379
			if (isOpera)
13380
				ed.getWin().scrollTo(0, vp.y);
13381
 
13382
			// Never wrap blocks in blocks
13383
			if (bef.firstChild && bef.firstChild.nodeName == bn)
13384
				bef.innerHTML = bef.firstChild.innerHTML;
13385
 
13386
			if (aft.firstChild && aft.firstChild.nodeName == bn)
13387
				aft.innerHTML = aft.firstChild.innerHTML;
13388
 
13389
			function appendStyles(e, en) {
13390
				var nl = [], nn, n, i;
13391
 
13392
				e.innerHTML = '';
13393
 
13394
				// Make clones of style elements
13395
				if (se.keep_styles) {
13396
					n = en;
13397
					do {
13398
						// We only want style specific elements
13399
						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
13400
							nn = n.cloneNode(FALSE);
13401
							dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
13402
							nl.push(nn);
13403
						}
13404
					} while (n = n.parentNode);
13405
				}
13406
 
13407
				// Append style elements to aft
13408
				if (nl.length > 0) {
13409
					for (i = nl.length - 1, nn = e; i >= 0; i--)
13410
						nn = nn.appendChild(nl[i]);
13411
 
13412
					// Padd most inner style element
13413
					nl[0].innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13414
					return nl[0]; // Move caret to most inner element
13415
				} else
13416
					e.innerHTML = isOpera ? '\u00a0' : '<br />'; // Extra space for Opera so that the caret can move there
13417
			};
13418
 
13419
			// Padd empty blocks
13420
			if (dom.isEmpty(bef))
13421
				appendStyles(bef, sn);
13422
 
13423
			// Fill empty afterblook with current style
13424
			if (dom.isEmpty(aft))
13425
				car = appendStyles(aft, en);
13426
 
13427
			// Opera needs this one backwards for older versions
13428
			if (isOpera && parseFloat(opera.version()) < 9.5) {
13429
				r.insertNode(bef);
13430
				r.insertNode(aft);
13431
			} else {
13432
				r.insertNode(aft);
13433
				r.insertNode(bef);
13434
			}
13435
 
13436
			// Normalize
13437
			aft.normalize();
13438
			bef.normalize();
13439
 
13440
			// Move cursor and scroll into view
13441
			ed.selection.select(aft, true);
13442
			ed.selection.collapse(true);
13443
 
13444
			// 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
13445
			y = ed.dom.getPos(aft).y;
13446
			//ch = aft.clientHeight;
13447
 
13448
			// Is element within viewport
13449
			if (y < vp.y || y + 25 > vp.y + vp.h) {
13450
				ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
13451
 
13452
				/*console.debug(
13453
					'Element: y=' + y + ', h=' + ch + ', ' +
13454
					'Viewport: y=' + vp.y + ", h=" + vp.h + ', bottom=' + (vp.y + vp.h)
13455
				);*/
13456
			}
13457
 
13458
			ed.undoManager.add();
13459
 
13460
			return FALSE;
13461
		},
13462
 
13463
		backspaceDelete : function(e, bs) {
13464
			var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn, walker;
13465
 
13466
			// Delete when caret is behind a element doesn't work correctly on Gecko see #3011651
13467
			if (!bs && r.collapsed && sc.nodeType == 1 && r.startOffset == sc.childNodes.length) {
13468
				walker = new tinymce.dom.TreeWalker(sc.lastChild, sc);
13469
 
13470
				// Walk the dom backwards until we find a text node
13471
				for (n = sc.lastChild; n; n = walker.prev()) {
13472
					if (n.nodeType == 3) {
13473
						r.setStart(n, n.nodeValue.length);
13474
						r.collapse(true);
13475
						se.setRng(r);
13476
						return;
13477
					}
13478
				}
13479
			}
13480
 
13481
			// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
13482
			// This workaround removes the element by hand and moves the caret to the previous element
13483
			if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
13484
				if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
13485
					// Find previous block element
13486
					n = sc;
13487
					while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
13488
 
13489
					if (n) {
13490
						if (sc != b.firstChild) {
13491
							// Find last text node
13492
							w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, FALSE);
13493
							while (tn = w.nextNode())
13494
								n = tn;
13495
 
13496
							// Place caret at the end of last text node
13497
							r = ed.getDoc().createRange();
13498
							r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
13499
							r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
13500
							se.setRng(r);
13501
 
13502
							// Remove the target container
13503
							ed.dom.remove(sc);
13504
						}
13505
 
13506
						return Event.cancel(e);
13507
					}
13508
				}
13509
			}
13510
		}
13511
	});
13512
})(tinymce);
13513
 
13514
(function(tinymce) {
13515
	// Shorten names
13516
	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
13517
 
13518
	tinymce.create('tinymce.ControlManager', {
13519
		ControlManager : function(ed, s) {
13520
			var t = this, i;
13521
 
13522
			s = s || {};
13523
			t.editor = ed;
13524
			t.controls = {};
13525
			t.onAdd = new tinymce.util.Dispatcher(t);
13526
			t.onPostRender = new tinymce.util.Dispatcher(t);
13527
			t.prefix = s.prefix || ed.id + '_';
13528
			t._cls = {};
13529
 
13530
			t.onPostRender.add(function() {
13531
				each(t.controls, function(c) {
13532
					c.postRender();
13533
				});
13534
			});
13535
		},
13536
 
13537
		get : function(id) {
13538
			return this.controls[this.prefix + id] || this.controls[id];
13539
		},
13540
 
13541
		setActive : function(id, s) {
13542
			var c = null;
13543
 
13544
			if (c = this.get(id))
13545
				c.setActive(s);
13546
 
13547
			return c;
13548
		},
13549
 
13550
		setDisabled : function(id, s) {
13551
			var c = null;
13552
 
13553
			if (c = this.get(id))
13554
				c.setDisabled(s);
13555
 
13556
			return c;
13557
		},
13558
 
13559
		add : function(c) {
13560
			var t = this;
13561
 
13562
			if (c) {
13563
				t.controls[c.id] = c;
13564
				t.onAdd.dispatch(c, t);
13565
			}
13566
 
13567
			return c;
13568
		},
13569
 
13570
		createControl : function(n) {
13571
			var c, t = this, ed = t.editor;
13572
 
13573
			each(ed.plugins, function(p) {
13574
				if (p.createControl) {
13575
					c = p.createControl(n, t);
13576
 
13577
					if (c)
13578
						return false;
13579
				}
13580
			});
13581
 
13582
			switch (n) {
13583
				case "|":
13584
				case "separator":
13585
					return t.createSeparator();
13586
			}
13587
 
13588
			if (!c && ed.buttons && (c = ed.buttons[n]))
13589
				return t.createButton(n, c);
13590
 
13591
			return t.add(c);
13592
		},
13593
 
13594
		createDropMenu : function(id, s, cc) {
13595
			var t = this, ed = t.editor, c, bm, v, cls;
13596
 
13597
			s = extend({
13598
				'class' : 'mceDropDown',
13599
				constrain : ed.settings.constrain_menus
13600
			}, s);
13601
 
13602
			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
13603
			if (v = ed.getParam('skin_variant'))
13604
				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
13605
 
13606
			id = t.prefix + id;
13607
			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
13608
			c = t.controls[id] = new cls(id, s);
13609
			c.onAddItem.add(function(c, o) {
13610
				var s = o.settings;
13611
 
13612
				s.title = ed.getLang(s.title, s.title);
13613
 
13614
				if (!s.onclick) {
13615
					s.onclick = function(v) {
13616
						if (s.cmd)
13617
							ed.execCommand(s.cmd, s.ui || false, s.value);
13618
					};
13619
				}
13620
			});
13621
 
13622
			ed.onRemove.add(function() {
13623
				c.destroy();
13624
			});
13625
 
13626
			// Fix for bug #1897785, #1898007
13627
			if (tinymce.isIE) {
13628
				c.onShowMenu.add(function() {
13629
					// IE 8 needs focus in order to store away a range with the current collapsed caret location
13630
					ed.focus();
13631
 
13632
					bm = ed.selection.getBookmark(1);
13633
				});
13634
 
13635
				c.onHideMenu.add(function() {
13636
					if (bm) {
13637
						ed.selection.moveToBookmark(bm);
13638
						bm = 0;
13639
					}
13640
				});
13641
			}
13642
 
13643
			return t.add(c);
13644
		},
13645
 
13646
		createListBox : function(id, s, cc) {
13647
			var t = this, ed = t.editor, cmd, c, cls;
13648
 
13649
			if (t.get(id))
13650
				return null;
13651
 
13652
			s.title = ed.translate(s.title);
13653
			s.scope = s.scope || ed;
13654
 
13655
			if (!s.onselect) {
13656
				s.onselect = function(v) {
13657
					ed.execCommand(s.cmd, s.ui || false, v || s.value);
13658
				};
13659
			}
13660
 
13661
			s = extend({
13662
				title : s.title,
13663
				'class' : 'mce_' + id,
13664
				scope : s.scope,
13665
				control_manager : t
13666
			}, s);
13667
 
13668
			id = t.prefix + id;
13669
 
13670
 
13671
			function useNativeListForAccessibility(ed) {
13672
				return ed.settings.use_accessible_selects && !tinymce.isGecko
13673
			}
13674
 
13675
			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
13676
				c = new tinymce.ui.NativeListBox(id, s);
13677
			else {
13678
				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
13679
				c = new cls(id, s, ed);
13680
			}
13681
 
13682
			t.controls[id] = c;
13683
 
13684
			// Fix focus problem in Safari
13685
			if (tinymce.isWebKit) {
13686
				c.onPostRender.add(function(c, n) {
13687
					// Store bookmark on mousedown
13688
					Event.add(n, 'mousedown', function() {
13689
						ed.bookmark = ed.selection.getBookmark(1);
13690
					});
13691
 
13692
					// Restore on focus, since it might be lost
13693
					Event.add(n, 'focus', function() {
13694
						ed.selection.moveToBookmark(ed.bookmark);
13695
						ed.bookmark = null;
13696
					});
13697
				});
13698
			}
13699
 
13700
			if (c.hideMenu)
13701
				ed.onMouseDown.add(c.hideMenu, c);
13702
 
13703
			return t.add(c);
13704
		},
13705
 
13706
		createButton : function(id, s, cc) {
13707
			var t = this, ed = t.editor, o, c, cls;
13708
 
13709
			if (t.get(id))
13710
				return null;
13711
 
13712
			s.title = ed.translate(s.title);
13713
			s.label = ed.translate(s.label);
13714
			s.scope = s.scope || ed;
13715
 
13716
			if (!s.onclick && !s.menu_button) {
13717
				s.onclick = function() {
13718
					ed.execCommand(s.cmd, s.ui || false, s.value);
13719
				};
13720
			}
13721
 
13722
			s = extend({
13723
				title : s.title,
13724
				'class' : 'mce_' + id,
13725
				unavailable_prefix : ed.getLang('unavailable', ''),
13726
				scope : s.scope,
13727
				control_manager : t
13728
			}, s);
13729
 
13730
			id = t.prefix + id;
13731
 
13732
			if (s.menu_button) {
13733
				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
13734
				c = new cls(id, s, ed);
13735
				ed.onMouseDown.add(c.hideMenu, c);
13736
			} else {
13737
				cls = t._cls.button || tinymce.ui.Button;
13738
				c = new cls(id, s, ed);
13739
			}
13740
 
13741
			return t.add(c);
13742
		},
13743
 
13744
		createMenuButton : function(id, s, cc) {
13745
			s = s || {};
13746
			s.menu_button = 1;
13747
 
13748
			return this.createButton(id, s, cc);
13749
		},
13750
 
13751
		createSplitButton : function(id, s, cc) {
13752
			var t = this, ed = t.editor, cmd, c, cls;
13753
 
13754
			if (t.get(id))
13755
				return null;
13756
 
13757
			s.title = ed.translate(s.title);
13758
			s.scope = s.scope || ed;
13759
 
13760
			if (!s.onclick) {
13761
				s.onclick = function(v) {
13762
					ed.execCommand(s.cmd, s.ui || false, v || s.value);
13763
				};
13764
			}
13765
 
13766
			if (!s.onselect) {
13767
				s.onselect = function(v) {
13768
					ed.execCommand(s.cmd, s.ui || false, v || s.value);
13769
				};
13770
			}
13771
 
13772
			s = extend({
13773
				title : s.title,
13774
				'class' : 'mce_' + id,
13775
				scope : s.scope,
13776
				control_manager : t
13777
			}, s);
13778
 
13779
			id = t.prefix + id;
13780
			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
13781
			c = t.add(new cls(id, s, ed));
13782
			ed.onMouseDown.add(c.hideMenu, c);
13783
 
13784
			return c;
13785
		},
13786
 
13787
		createColorSplitButton : function(id, s, cc) {
13788
			var t = this, ed = t.editor, cmd, c, cls, bm;
13789
 
13790
			if (t.get(id))
13791
				return null;
13792
 
13793
			s.title = ed.translate(s.title);
13794
			s.scope = s.scope || ed;
13795
 
13796
			if (!s.onclick) {
13797
				s.onclick = function(v) {
13798
					if (tinymce.isIE)
13799
						bm = ed.selection.getBookmark(1);
13800
 
13801
					ed.execCommand(s.cmd, s.ui || false, v || s.value);
13802
				};
13803
			}
13804
 
13805
			if (!s.onselect) {
13806
				s.onselect = function(v) {
13807
					ed.execCommand(s.cmd, s.ui || false, v || s.value);
13808
				};
13809
			}
13810
 
13811
			s = extend({
13812
				title : s.title,
13813
				'class' : 'mce_' + id,
13814
				'menu_class' : ed.getParam('skin') + 'Skin',
13815
				scope : s.scope,
13816
				more_colors_title : ed.getLang('more_colors')
13817
			}, s);
13818
 
13819
			id = t.prefix + id;
13820
			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
13821
			c = new cls(id, s, ed);
13822
			ed.onMouseDown.add(c.hideMenu, c);
13823
 
13824
			// Remove the menu element when the editor is removed
13825
			ed.onRemove.add(function() {
13826
				c.destroy();
13827
			});
13828
 
13829
			// Fix for bug #1897785, #1898007
13830
			if (tinymce.isIE) {
13831
				c.onShowMenu.add(function() {
13832
					// IE 8 needs focus in order to store away a range with the current collapsed caret location
13833
					ed.focus();
13834
					bm = ed.selection.getBookmark(1);
13835
				});
13836
 
13837
				c.onHideMenu.add(function() {
13838
					if (bm) {
13839
						ed.selection.moveToBookmark(bm);
13840
						bm = 0;
13841
					}
13842
				});
13843
			}
13844
 
13845
			return t.add(c);
13846
		},
13847
 
13848
		createToolbar : function(id, s, cc) {
13849
			var c, t = this, cls;
13850
 
13851
			id = t.prefix + id;
13852
			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
13853
			c = new cls(id, s, t.editor);
13854
 
13855
			if (t.get(id))
13856
				return null;
13857
 
13858
			return t.add(c);
13859
		},
13860
 
13861
		createToolbarGroup : function(id, s, cc) {
13862
			var c, t = this, cls;
13863
			id = t.prefix + id;
13864
			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
13865
			c = new cls(id, s, t.editor);
13866
 
13867
			if (t.get(id))
13868
				return null;
13869
 
13870
			return t.add(c);
13871
		},
13872
 
13873
		createSeparator : function(cc) {
13874
			var cls = cc || this._cls.separator || tinymce.ui.Separator;
13875
 
13876
			return new cls();
13877
		},
13878
 
13879
		setControlType : function(n, c) {
13880
			return this._cls[n.toLowerCase()] = c;
13881
		},
13882
 
13883
		destroy : function() {
13884
			each(this.controls, function(c) {
13885
				c.destroy();
13886
			});
13887
 
13888
			this.controls = null;
13889
		}
13890
	});
13891
})(tinymce);
13892
 
13893
(function(tinymce) {
13894
	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
13895
 
13896
	tinymce.create('tinymce.WindowManager', {
13897
		WindowManager : function(ed) {
13898
			var t = this;
13899
 
13900
			t.editor = ed;
13901
			t.onOpen = new Dispatcher(t);
13902
			t.onClose = new Dispatcher(t);
13903
			t.params = {};
13904
			t.features = {};
13905
		},
13906
 
13907
		open : function(s, p) {
13908
			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
13909
 
13910
			// Default some options
13911
			s = s || {};
13912
			p = p || {};
13913
			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
13914
			sh = isOpera ? vp.h : screen.height;
13915
			s.name = s.name || 'mc_' + new Date().getTime();
13916
			s.width = parseInt(s.width || 320);
13917
			s.height = parseInt(s.height || 240);
13918
			s.resizable = true;
13919
			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
13920
			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
13921
			p.inline = false;
13922
			p.mce_width = s.width;
13923
			p.mce_height = s.height;
13924
			p.mce_auto_focus = s.auto_focus;
13925
 
13926
			if (mo) {
13927
				if (isIE) {
13928
					s.center = true;
13929
					s.help = false;
13930
					s.dialogWidth = s.width + 'px';
13931
					s.dialogHeight = s.height + 'px';
13932
					s.scroll = s.scrollbars || false;
13933
				}
13934
			}
13935
 
13936
			// Build features string
13937
			each(s, function(v, k) {
13938
				if (tinymce.is(v, 'boolean'))
13939
					v = v ? 'yes' : 'no';
13940
 
13941
				if (!/^(name|url)$/.test(k)) {
13942
					if (isIE && mo)
13943
						f += (f ? ';' : '') + k + ':' + v;
13944
					else
13945
						f += (f ? ',' : '') + k + '=' + v;
13946
				}
13947
			});
13948
 
13949
			t.features = s;
13950
			t.params = p;
13951
			t.onOpen.dispatch(t, s, p);
13952
 
13953
			u = s.url || s.file;
13954
			u = tinymce._addVer(u);
13955
 
13956
			try {
13957
				if (isIE && mo) {
13958
					w = 1;
13959
					window.showModalDialog(u, window, f);
13960
				} else
13961
					w = window.open(u, s.name, f);
13962
			} catch (ex) {
13963
				// Ignore
13964
			}
13965
 
13966
			if (!w)
13967
				alert(t.editor.getLang('popup_blocked'));
13968
		},
13969
 
13970
		close : function(w) {
13971
			w.close();
13972
			this.onClose.dispatch(this);
13973
		},
13974
 
13975
		createInstance : function(cl, a, b, c, d, e) {
13976
			var f = tinymce.resolve(cl);
13977
 
13978
			return new f(a, b, c, d, e);
13979
		},
13980
 
13981
		confirm : function(t, cb, s, w) {
13982
			w = w || window;
13983
 
13984
			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
13985
		},
13986
 
13987
		alert : function(tx, cb, s, w) {
13988
			var t = this;
13989
 
13990
			w = w || window;
13991
			w.alert(t._decode(t.editor.getLang(tx, tx)));
13992
 
13993
			if (cb)
13994
				cb.call(s || t);
13995
		},
13996
 
13997
		resizeBy : function(dw, dh, win) {
13998
			win.resizeBy(dw, dh);
13999
		},
14000
 
14001
		// Internal functions
14002
 
14003
		_decode : function(s) {
14004
			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14005
		}
14006
	});
14007
}(tinymce));
14008
(function(tinymce) {
14009
	tinymce.Formatter = function(ed) {
14010
		var formats = {},
14011
			each = tinymce.each,
14012
			dom = ed.dom,
14013
			selection = ed.selection,
14014
			TreeWalker = tinymce.dom.TreeWalker,
14015
			rangeUtils = new tinymce.dom.RangeUtils(dom),
14016
			isValid = ed.schema.isValidChild,
14017
			isBlock = dom.isBlock,
14018
			forcedRootBlock = ed.settings.forced_root_block,
14019
			nodeIndex = dom.nodeIndex,
14020
			INVISIBLE_CHAR = '\uFEFF',
14021
			MCE_ATTR_RE = /^(src|href|style)$/,
14022
			FALSE = false,
14023
			TRUE = true,
14024
			undefined,
14025
			pendingFormats = {apply : [], remove : []};
14026
 
14027
		function isArray(obj) {
14028
			return obj instanceof Array;
14029
		};
14030
 
14031
		function getParents(node, selector) {
14032
			return dom.getParents(node, selector, dom.getRoot());
14033
		};
14034
 
14035
		function isCaretNode(node) {
14036
			return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
14037
		};
14038
 
14039
		// Public functions
14040
 
14041
		function get(name) {
14042
			return name ? formats[name] : formats;
14043
		};
14044
 
14045
		function register(name, format) {
14046
			if (name) {
14047
				if (typeof(name) !== 'string') {
14048
					each(name, function(format, name) {
14049
						register(name, format);
14050
					});
14051
				} else {
14052
					// Force format into array and add it to internal collection
14053
					format = format.length ? format : [format];
14054
 
14055
					each(format, function(format) {
14056
						// Set deep to false by default on selector formats this to avoid removing
14057
						// alignment on images inside paragraphs when alignment is changed on paragraphs
14058
						if (format.deep === undefined)
14059
							format.deep = !format.selector;
14060
 
14061
						// Default to true
14062
						if (format.split === undefined)
14063
							format.split = !format.selector || format.inline;
14064
 
14065
						// Default to true
14066
						if (format.remove === undefined && format.selector && !format.inline)
14067
							format.remove = 'none';
14068
 
14069
						// Mark format as a mixed format inline + block level
14070
						if (format.selector && format.inline) {
14071
							format.mixed = true;
14072
							format.block_expand = true;
14073
						}
14074
 
14075
						// Split classes if needed
14076
						if (typeof(format.classes) === 'string')
14077
							format.classes = format.classes.split(/\s+/);
14078
					});
14079
 
14080
					formats[name] = format;
14081
				}
14082
			}
14083
		};
14084
 
14085
		var getTextDecoration = function(node) {
14086
			var decoration;
14087
 
14088
			ed.dom.getParent(node, function(n) {
14089
				decoration = ed.dom.getStyle(n, 'text-decoration');
14090
				return decoration && decoration !== 'none';
14091
			});
14092
 
14093
			return decoration;
14094
		};
14095
 
14096
		var processUnderlineAndColor = function(node) {
14097
			var textDecoration;
14098
			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
14099
				textDecoration = getTextDecoration(node.parentNode);
14100
				if (ed.dom.getStyle(node, 'color') && textDecoration) {
14101
					ed.dom.setStyle(node, 'text-decoration', textDecoration);
14102
				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
14103
					ed.dom.setStyle(node, 'text-decoration', null);
14104
				}
14105
			}
14106
		};
14107
 
14108
		function apply(name, vars, node) {
14109
			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
14110
 
14111
			function moveStart(rng) {
14112
				var container = rng.startContainer,
14113
					offset = rng.startOffset,
14114
					walker, node;
14115
 
14116
				// Move startContainer/startOffset in to a suitable node
14117
				if (container.nodeType == 1 || container.nodeValue === "") {
14118
					container = container.nodeType == 1 ? container.childNodes[offset] : container;
14119
 
14120
					// Might fail if the offset is behind the last element in it's container
14121
					if (container) {
14122
						walker = new TreeWalker(container, container.parentNode);
14123
						for (node = walker.current(); node; node = walker.next()) {
14124
							if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14125
								rng.setStart(node, 0);
14126
								break;
14127
							}
14128
						}
14129
					}
14130
				}
14131
 
14132
				return rng;
14133
			};
14134
 
14135
			function setElementFormat(elm, fmt) {
14136
				fmt = fmt || format;
14137
 
14138
				if (elm) {
14139
					if (fmt.onformat) {
14140
						fmt.onformat(elm, fmt, vars, node);
14141
					}
14142
 
14143
					each(fmt.styles, function(value, name) {
14144
						dom.setStyle(elm, name, replaceVars(value, vars));
14145
					});
14146
 
14147
					each(fmt.attributes, function(value, name) {
14148
						dom.setAttrib(elm, name, replaceVars(value, vars));
14149
					});
14150
 
14151
					each(fmt.classes, function(value) {
14152
						value = replaceVars(value, vars);
14153
 
14154
						if (!dom.hasClass(elm, value))
14155
							dom.addClass(elm, value);
14156
					});
14157
				}
14158
			};
14159
			function adjustSelectionToVisibleSelection() {
14160
				function findSelectionEnd(start, end) {
14161
					var walker = new TreeWalker(end);
14162
					for (node = walker.current(); node; node = walker.prev()) {
14163
						if (node.childNodes.length > 1 || node == start) {
14164
							return node;
14165
						}
14166
					}
14167
				};
14168
 
14169
				// Adjust selection so that a end container with a end offset of zero is not included in the selection
14170
				// as this isn't visible to the user.
14171
				var rng = ed.selection.getRng();
14172
				var start = rng.startContainer;
14173
				var end = rng.endContainer;
14174
 
14175
				if (start != end && rng.endOffset == 0) {
14176
					var newEnd = findSelectionEnd(start, end);
14177
					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
14178
 
14179
					rng.setEnd(newEnd, endOffset);
14180
				}
14181
 
14182
				return rng;
14183
			}
14184
 
14185
			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
14186
				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
14187
 
14188
				// find the index of the first child list.
14189
				each(node.childNodes, function(n, index) {
14190
					if (n.nodeName === "UL" || n.nodeName === "OL") {
14191
						listIndex = index;
14192
						list = n;
14193
						return false;
14194
					}
14195
				});
14196
 
14197
				// get the index of the bookmarks
14198
				each(node.childNodes, function(n, index) {
14199
					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
14200
						if (n.id == bookmark.id + "_start") {
14201
							startIndex = index;
14202
						} else if (n.id == bookmark.id + "_end") {
14203
							endIndex = index;
14204
						}
14205
					}
14206
				});
14207
 
14208
				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
14209
				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
14210
					each(tinymce.grep(node.childNodes), process);
14211
					return 0;
14212
				} else {
14213
					currentWrapElm = wrapElm.cloneNode(FALSE);
14214
 
14215
					// create a list of the nodes on the same side of the list as the selection
14216
					each(tinymce.grep(node.childNodes), function(n, index) {
14217
						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
14218
							nodes.push(n);
14219
							n.parentNode.removeChild(n);
14220
						}
14221
					});
14222
 
14223
					// insert the wrapping element either before or after the list.
14224
					if (startIndex < listIndex) {
14225
						node.insertBefore(currentWrapElm, list);
14226
					} else if (startIndex > listIndex) {
14227
						node.insertBefore(currentWrapElm, list.nextSibling);
14228
					}
14229
 
14230
					// add the new nodes to the list.
14231
					newWrappers.push(currentWrapElm);
14232
 
14233
					each(nodes, function(node) {
14234
						currentWrapElm.appendChild(node);
14235
					});
14236
 
14237
					return currentWrapElm;
14238
				}
14239
			};
14240
 
14241
			function applyRngStyle(rng, bookmark) {
14242
				var newWrappers = [], wrapName, wrapElm;
14243
 
14244
				// Setup wrapper element
14245
				wrapName = format.inline || format.block;
14246
				wrapElm = dom.create(wrapName);
14247
				setElementFormat(wrapElm);
14248
 
14249
				rangeUtils.walk(rng, function(nodes) {
14250
					var currentWrapElm;
14251
 
14252
					function process(node) {
14253
						var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
14254
 
14255
						// Stop wrapping on br elements
14256
						if (isEq(nodeName, 'br')) {
14257
							currentWrapElm = 0;
14258
 
14259
							// Remove any br elements when we wrap things
14260
							if (format.block)
14261
								dom.remove(node);
14262
 
14263
							return;
14264
						}
14265
 
14266
						// If node is wrapper type
14267
						if (format.wrapper && matchNode(node, name, vars)) {
14268
							currentWrapElm = 0;
14269
							return;
14270
						}
14271
 
14272
						// Can we rename the block
14273
						if (format.block && !format.wrapper && isTextBlock(nodeName)) {
14274
							node = dom.rename(node, wrapName);
14275
							setElementFormat(node);
14276
							newWrappers.push(node);
14277
							currentWrapElm = 0;
14278
							return;
14279
						}
14280
 
14281
						// Handle selector patterns
14282
						if (format.selector) {
14283
							// Look for matching formats
14284
							each(formatList, function(format) {
14285
								// Check collapsed state if it exists
14286
								if ('collapsed' in format && format.collapsed !== isCollapsed) {
14287
									return;
14288
								}
14289
 
14290
								if (dom.is(node, format.selector) && !isCaretNode(node)) {
14291
									setElementFormat(node, format);
14292
									found = true;
14293
								}
14294
							});
14295
 
14296
							// Continue processing if a selector match wasn't found and a inline element is defined
14297
							if (!format.inline || found) {
14298
								currentWrapElm = 0;
14299
								return;
14300
							}
14301
						}
14302
 
14303
						// Is it valid to wrap this item
14304
						if (isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
14305
								!(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)) {
14306
							// Start wrapping
14307
							if (!currentWrapElm) {
14308
								// Wrap the node
14309
								currentWrapElm = wrapElm.cloneNode(FALSE);
14310
								node.parentNode.insertBefore(currentWrapElm, node);
14311
								newWrappers.push(currentWrapElm);
14312
							}
14313
 
14314
							currentWrapElm.appendChild(node);
14315
						} else if (nodeName == 'li' && bookmark) {
14316
							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
14317
							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
14318
						} else {
14319
							// Start a new wrapper for possible children
14320
							currentWrapElm = 0;
14321
 
14322
							each(tinymce.grep(node.childNodes), process);
14323
 
14324
							// End the last wrapper
14325
							currentWrapElm = 0;
14326
						}
14327
					};
14328
 
14329
					// Process siblings from range
14330
					each(nodes, process);
14331
				});
14332
 
14333
				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
14334
				if (format.wrap_links === false) {
14335
					each(newWrappers, function(node) {
14336
						function process(node) {
14337
							var i, currentWrapElm, children;
14338
 
14339
							if (node.nodeName === 'A') {
14340
								currentWrapElm = wrapElm.cloneNode(FALSE);
14341
								newWrappers.push(currentWrapElm);
14342
 
14343
								children = tinymce.grep(node.childNodes);
14344
								for (i = 0; i < children.length; i++)
14345
									currentWrapElm.appendChild(children[i]);
14346
 
14347
								node.appendChild(currentWrapElm);
14348
							}
14349
 
14350
							each(tinymce.grep(node.childNodes), process);
14351
						};
14352
 
14353
						process(node);
14354
					});
14355
				}
14356
 
14357
				// Cleanup
14358
				each(newWrappers, function(node) {
14359
					var childCount;
14360
 
14361
					function getChildCount(node) {
14362
						var count = 0;
14363
 
14364
						each(node.childNodes, function(node) {
14365
							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
14366
								count++;
14367
						});
14368
 
14369
						return count;
14370
					};
14371
 
14372
					function mergeStyles(node) {
14373
						var child, clone;
14374
 
14375
						each(node.childNodes, function(node) {
14376
							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
14377
								child = node;
14378
								return FALSE; // break loop
14379
							}
14380
						});
14381
 
14382
						// If child was found and of the same type as the current node
14383
						if (child && matchName(child, format)) {
14384
							clone = child.cloneNode(FALSE);
14385
							setElementFormat(clone);
14386
 
14387
							dom.replace(clone, node, TRUE);
14388
							dom.remove(child, 1);
14389
						}
14390
 
14391
						return clone || node;
14392
					};
14393
 
14394
					childCount = getChildCount(node);
14395
 
14396
					// Remove empty nodes but only if there is multiple wrappers and they are not block
14397
					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
14398
					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
14399
						dom.remove(node, 1);
14400
						return;
14401
					}
14402
 
14403
					if (format.inline || format.wrapper) {
14404
						// Merges the current node with it's children of similar type to reduce the number of elements
14405
						if (!format.exact && childCount === 1)
14406
							node = mergeStyles(node);
14407
 
14408
						// Remove/merge children
14409
						each(formatList, function(format) {
14410
							// Merge all children of similar type will move styles from child to parent
14411
							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
14412
							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
14413
							each(dom.select(format.inline, node), function(child) {
14414
								var parent;
14415
 
14416
								// When wrap_links is set to false we don't want
14417
								// to remove the format on children within links
14418
								if (format.wrap_links === false) {
14419
									parent = child.parentNode;
14420
 
14421
									do {
14422
										if (parent.nodeName === 'A')
14423
											return;
14424
									} while (parent = parent.parentNode);
14425
								}
14426
 
14427
								removeFormat(format, vars, child, format.exact ? child : null);
14428
							});
14429
						});
14430
 
14431
						// Remove child if direct parent is of same type
14432
						if (matchNode(node.parentNode, name, vars)) {
14433
							dom.remove(node, 1);
14434
							node = 0;
14435
							return TRUE;
14436
						}
14437
 
14438
						// Look for parent with similar style format
14439
						if (format.merge_with_parents) {
14440
							dom.getParent(node.parentNode, function(parent) {
14441
								if (matchNode(parent, name, vars)) {
14442
									dom.remove(node, 1);
14443
									node = 0;
14444
									return TRUE;
14445
								}
14446
							});
14447
						}
14448
 
14449
						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
14450
						if (node && format.merge_siblings !== false) {
14451
							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
14452
							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
14453
						}
14454
					}
14455
				});
14456
			};
14457
 
14458
			if (format) {
14459
				if (node) {
14460
					rng = dom.createRng();
14461
 
14462
					rng.setStartBefore(node);
14463
					rng.setEndAfter(node);
14464
 
14465
					applyRngStyle(expandRng(rng, formatList));
14466
				} else {
14467
					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14468
						// Obtain selection node before selection is unselected by applyRngStyle()
14469
						var curSelNode = ed.selection.getNode();
14470
 
14471
						// Apply formatting to selection
14472
						ed.selection.setRng(adjustSelectionToVisibleSelection());
14473
						bookmark = selection.getBookmark();
14474
						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
14475
 
14476
						// Colored nodes should be underlined so that the color of the underline matches the text color.
14477
						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
14478
							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
14479
							processUnderlineAndColor(curSelNode);
14480
						}
14481
 
14482
						selection.moveToBookmark(bookmark);
14483
						selection.setRng(moveStart(selection.getRng(TRUE)));
14484
						ed.nodeChanged();
14485
					} else
14486
						performCaretAction('apply', name, vars);
14487
				}
14488
			}
14489
		};
14490
 
14491
		function remove(name, vars, node) {
14492
			var formatList = get(name), format = formatList[0], bookmark, i, rng;
14493
			function moveStart(rng) {
14494
				var container = rng.startContainer,
14495
					offset = rng.startOffset,
14496
					walker, node, nodes, tmpNode;
14497
 
14498
				// Convert text node into index if possible
14499
				if (container.nodeType == 3 && offset >= container.nodeValue.length - 1) {
14500
					container = container.parentNode;
14501
					offset = nodeIndex(container) + 1;
14502
				}
14503
 
14504
				// Move startContainer/startOffset in to a suitable node
14505
				if (container.nodeType == 1) {
14506
					nodes = container.childNodes;
14507
					container = nodes[Math.min(offset, nodes.length - 1)];
14508
					walker = new TreeWalker(container);
14509
 
14510
					// If offset is at end of the parent node walk to the next one
14511
					if (offset > nodes.length - 1)
14512
						walker.next();
14513
 
14514
					for (node = walker.current(); node; node = walker.next()) {
14515
						if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
14516
							// IE has a "neat" feature where it moves the start node into the closest element
14517
							// we can avoid this by inserting an element before it and then remove it after we set the selection
14518
							tmpNode = dom.create('a', null, INVISIBLE_CHAR);
14519
							node.parentNode.insertBefore(tmpNode, node);
14520
 
14521
							// Set selection and remove tmpNode
14522
							rng.setStart(node, 0);
14523
							selection.setRng(rng);
14524
							dom.remove(tmpNode);
14525
 
14526
							return;
14527
						}
14528
					}
14529
				}
14530
			};
14531
 
14532
			// Merges the styles for each node
14533
			function process(node) {
14534
				var children, i, l;
14535
 
14536
				// Grab the children first since the nodelist might be changed
14537
				children = tinymce.grep(node.childNodes);
14538
 
14539
				// Process current node
14540
				for (i = 0, l = formatList.length; i < l; i++) {
14541
					if (removeFormat(formatList[i], vars, node, node))
14542
						break;
14543
				}
14544
 
14545
				// Process the children
14546
				if (format.deep) {
14547
					for (i = 0, l = children.length; i < l; i++)
14548
						process(children[i]);
14549
				}
14550
			};
14551
 
14552
			function findFormatRoot(container) {
14553
				var formatRoot;
14554
 
14555
				// Find format root
14556
				each(getParents(container.parentNode).reverse(), function(parent) {
14557
					var format;
14558
 
14559
					// Find format root element
14560
					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
14561
						// Is the node matching the format we are looking for
14562
						format = matchNode(parent, name, vars);
14563
						if (format && format.split !== false)
14564
							formatRoot = parent;
14565
					}
14566
				});
14567
 
14568
				return formatRoot;
14569
			};
14570
 
14571
			function wrapAndSplit(format_root, container, target, split) {
14572
				var parent, clone, lastClone, firstClone, i, formatRootParent;
14573
 
14574
				// Format root found then clone formats and split it
14575
				if (format_root) {
14576
					formatRootParent = format_root.parentNode;
14577
 
14578
					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
14579
						clone = parent.cloneNode(FALSE);
14580
 
14581
						for (i = 0; i < formatList.length; i++) {
14582
							if (removeFormat(formatList[i], vars, clone, clone)) {
14583
								clone = 0;
14584
								break;
14585
							}
14586
						}
14587
 
14588
						// Build wrapper node
14589
						if (clone) {
14590
							if (lastClone)
14591
								clone.appendChild(lastClone);
14592
 
14593
							if (!firstClone)
14594
								firstClone = clone;
14595
 
14596
							lastClone = clone;
14597
						}
14598
					}
14599
 
14600
					// Never split block elements if the format is mixed
14601
					if (split && (!format.mixed || !isBlock(format_root)))
14602
						container = dom.split(format_root, container);
14603
 
14604
					// Wrap container in cloned formats
14605
					if (lastClone) {
14606
						target.parentNode.insertBefore(lastClone, target);
14607
						firstClone.appendChild(target);
14608
					}
14609
				}
14610
 
14611
				return container;
14612
			};
14613
 
14614
			function splitToFormatRoot(container) {
14615
				return wrapAndSplit(findFormatRoot(container), container, container, true);
14616
			};
14617
 
14618
			function unwrap(start) {
14619
				var node = dom.get(start ? '_start' : '_end'),
14620
					out = node[start ? 'firstChild' : 'lastChild'];
14621
 
14622
				// If the end is placed within the start the result will be removed
14623
				// So this checks if the out node is a bookmark node if it is it
14624
				// checks for another more suitable node
14625
				if (isBookmarkNode(out))
14626
					out = out[start ? 'firstChild' : 'lastChild'];
14627
 
14628
				dom.remove(node, true);
14629
 
14630
				return out;
14631
			};
14632
 
14633
			function removeRngStyle(rng) {
14634
				var startContainer, endContainer;
14635
 
14636
				rng = expandRng(rng, formatList, TRUE);
14637
 
14638
				if (format.split) {
14639
					startContainer = getContainer(rng, TRUE);
14640
					endContainer = getContainer(rng);
14641
 
14642
					if (startContainer != endContainer) {
14643
						// Wrap start/end nodes in span element since these might be cloned/moved
14644
						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
14645
						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
14646
 
14647
						// Split start/end
14648
						splitToFormatRoot(startContainer);
14649
						splitToFormatRoot(endContainer);
14650
 
14651
						// Unwrap start/end to get real elements again
14652
						startContainer = unwrap(TRUE);
14653
						endContainer = unwrap();
14654
					} else
14655
						startContainer = endContainer = splitToFormatRoot(startContainer);
14656
 
14657
					// Update range positions since they might have changed after the split operations
14658
					rng.startContainer = startContainer.parentNode;
14659
					rng.startOffset = nodeIndex(startContainer);
14660
					rng.endContainer = endContainer.parentNode;
14661
					rng.endOffset = nodeIndex(endContainer) + 1;
14662
				}
14663
 
14664
				// Remove items between start/end
14665
				rangeUtils.walk(rng, function(nodes) {
14666
					each(nodes, function(node) {
14667
						process(node);
14668
 
14669
						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
14670
						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
14671
							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
14672
						}
14673
					});
14674
				});
14675
			};
14676
 
14677
			// Handle node
14678
			if (node) {
14679
				rng = dom.createRng();
14680
				rng.setStartBefore(node);
14681
				rng.setEndAfter(node);
14682
				removeRngStyle(rng);
14683
				return;
14684
			}
14685
 
14686
			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
14687
				bookmark = selection.getBookmark();
14688
				removeRngStyle(selection.getRng(TRUE));
14689
				selection.moveToBookmark(bookmark);
14690
 
14691
				// 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
14692
				if (format.inline && match(name, vars, selection.getStart())) {
14693
					moveStart(selection.getRng(true));
14694
				}
14695
 
14696
				ed.nodeChanged();
14697
			} else
14698
				performCaretAction('remove', name, vars);
14699
		};
14700
 
14701
		function toggle(name, vars, node) {
14702
			var fmt = get(name);
14703
 
14704
			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
14705
				remove(name, vars, node);
14706
			else
14707
				apply(name, vars, node);
14708
		};
14709
 
14710
		function matchNode(node, name, vars, similar) {
14711
			var formatList = get(name), format, i, classes;
14712
 
14713
			function matchItems(node, format, item_name) {
14714
				var key, value, items = format[item_name], i;
14715
 
14716
				// Custom match
14717
				if (format.onmatch) {
14718
					return format.onmatch(node, format, item_name);
14719
				}
14720
 
14721
				// Check all items
14722
				if (items) {
14723
					// Non indexed object
14724
					if (items.length === undefined) {
14725
						for (key in items) {
14726
							if (items.hasOwnProperty(key)) {
14727
								if (item_name === 'attributes')
14728
									value = dom.getAttrib(node, key);
14729
								else
14730
									value = getStyle(node, key);
14731
 
14732
								if (similar && !value && !format.exact)
14733
									return;
14734
 
14735
								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
14736
									return;
14737
							}
14738
						}
14739
					} else {
14740
						// Only one match needed for indexed arrays
14741
						for (i = 0; i < items.length; i++) {
14742
							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
14743
								return format;
14744
						}
14745
					}
14746
				}
14747
 
14748
				return format;
14749
			};
14750
 
14751
			if (formatList && node) {
14752
				// Check each format in list
14753
				for (i = 0; i < formatList.length; i++) {
14754
					format = formatList[i];
14755
 
14756
					// Name name, attributes, styles and classes
14757
					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
14758
						// Match classes
14759
						if (classes = format.classes) {
14760
							for (i = 0; i < classes.length; i++) {
14761
								if (!dom.hasClass(node, classes[i]))
14762
									return;
14763
							}
14764
						}
14765
 
14766
						return format;
14767
					}
14768
				}
14769
			}
14770
		};
14771
 
14772
		function match(name, vars, node) {
14773
			var startNode, i;
14774
 
14775
			function matchParents(node) {
14776
				// Find first node with similar format settings
14777
				node = dom.getParent(node, function(node) {
14778
					return !!matchNode(node, name, vars, true);
14779
				});
14780
 
14781
				// Do an exact check on the similar format element
14782
				return matchNode(node, name, vars);
14783
			};
14784
 
14785
			// Check specified node
14786
			if (node)
14787
				return matchParents(node);
14788
 
14789
			// Check pending formats
14790
			if (selection.isCollapsed()) {
14791
				for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14792
					if (pendingFormats.apply[i].name == name)
14793
						return true;
14794
				}
14795
 
14796
				for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14797
					if (pendingFormats.remove[i].name == name)
14798
						return false;
14799
				}
14800
 
14801
				return matchParents(selection.getNode());
14802
			}
14803
 
14804
			// Check selected node
14805
			node = selection.getNode();
14806
			if (matchParents(node))
14807
				return TRUE;
14808
 
14809
			// Check start node if it's different
14810
			startNode = selection.getStart();
14811
			if (startNode != node) {
14812
				if (matchParents(startNode))
14813
					return TRUE;
14814
			}
14815
 
14816
			return FALSE;
14817
		};
14818
 
14819
		function matchAll(names, vars) {
14820
			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
14821
 
14822
			// If the selection is collapsed then check pending formats
14823
			if (selection.isCollapsed()) {
14824
				for (ni = 0; ni < names.length; ni++) {
14825
					// If the name is to be removed, then stop it from being added
14826
					for (i = pendingFormats.remove.length - 1; i >= 0; i--) {
14827
						name = names[ni];
14828
 
14829
						if (pendingFormats.remove[i].name == name) {
14830
							checkedMap[name] = true;
14831
							break;
14832
						}
14833
					}
14834
				}
14835
 
14836
				// If the format is to be applied
14837
				for (i = pendingFormats.apply.length - 1; i >= 0; i--) {
14838
					for (ni = 0; ni < names.length; ni++) {
14839
						name = names[ni];
14840
 
14841
						if (!checkedMap[name] && pendingFormats.apply[i].name == name) {
14842
							checkedMap[name] = true;
14843
							matchedFormatNames.push(name);
14844
						}
14845
					}
14846
				}
14847
			}
14848
 
14849
			// Check start of selection for formats
14850
			startElement = selection.getStart();
14851
			dom.getParent(startElement, function(node) {
14852
				var i, name;
14853
 
14854
				for (i = 0; i < names.length; i++) {
14855
					name = names[i];
14856
 
14857
					if (!checkedMap[name] && matchNode(node, name, vars)) {
14858
						checkedMap[name] = true;
14859
						matchedFormatNames.push(name);
14860
					}
14861
				}
14862
			});
14863
 
14864
			return matchedFormatNames;
14865
		};
14866
 
14867
		function canApply(name) {
14868
			var formatList = get(name), startNode, parents, i, x, selector;
14869
 
14870
			if (formatList) {
14871
				startNode = selection.getStart();
14872
				parents = getParents(startNode);
14873
 
14874
				for (x = formatList.length - 1; x >= 0; x--) {
14875
					selector = formatList[x].selector;
14876
 
14877
					// Format is not selector based, then always return TRUE
14878
					if (!selector)
14879
						return TRUE;
14880
 
14881
					for (i = parents.length - 1; i >= 0; i--) {
14882
						if (dom.is(parents[i], selector))
14883
							return TRUE;
14884
					}
14885
				}
14886
			}
14887
 
14888
			return FALSE;
14889
		};
14890
 
14891
		// Expose to public
14892
		tinymce.extend(this, {
14893
			get : get,
14894
			register : register,
14895
			apply : apply,
14896
			remove : remove,
14897
			toggle : toggle,
14898
			match : match,
14899
			matchAll : matchAll,
14900
			matchNode : matchNode,
14901
			canApply : canApply
14902
		});
14903
 
14904
		// Private functions
14905
 
14906
		function matchName(node, format) {
14907
			// Check for inline match
14908
			if (isEq(node, format.inline))
14909
				return TRUE;
14910
 
14911
			// Check for block match
14912
			if (isEq(node, format.block))
14913
				return TRUE;
14914
 
14915
			// Check for selector match
14916
			if (format.selector)
14917
				return dom.is(node, format.selector);
14918
		};
14919
 
14920
		function isEq(str1, str2) {
14921
			str1 = str1 || '';
14922
			str2 = str2 || '';
14923
 
14924
			str1 = '' + (str1.nodeName || str1);
14925
			str2 = '' + (str2.nodeName || str2);
14926
 
14927
			return str1.toLowerCase() == str2.toLowerCase();
14928
		};
14929
 
14930
		function getStyle(node, name) {
14931
			var styleVal = dom.getStyle(node, name);
14932
 
14933
			// Force the format to hex
14934
			if (name == 'color' || name == 'backgroundColor')
14935
				styleVal = dom.toHex(styleVal);
14936
 
14937
			// Opera will return bold as 700
14938
			if (name == 'fontWeight' && styleVal == 700)
14939
				styleVal = 'bold';
14940
 
14941
			return '' + styleVal;
14942
		};
14943
 
14944
		function replaceVars(value, vars) {
14945
			if (typeof(value) != "string")
14946
				value = value(vars);
14947
			else if (vars) {
14948
				value = value.replace(/%(\w+)/g, function(str, name) {
14949
					return vars[name] || str;
14950
				});
14951
			}
14952
 
14953
			return value;
14954
		};
14955
 
14956
		function isWhiteSpaceNode(node) {
14957
			return node && node.nodeType === 3 && /^([\s\r\n]+|)$/.test(node.nodeValue);
14958
		};
14959
 
14960
		function wrap(node, name, attrs) {
14961
			var wrapper = dom.create(name, attrs);
14962
 
14963
			node.parentNode.insertBefore(wrapper, node);
14964
			wrapper.appendChild(node);
14965
 
14966
			return wrapper;
14967
		};
14968
 
14969
		function expandRng(rng, format, remove) {
14970
			var startContainer = rng.startContainer,
14971
				startOffset = rng.startOffset,
14972
				endContainer = rng.endContainer,
14973
				endOffset = rng.endOffset, sibling, lastIdx, leaf;
14974
 
14975
			// This function walks up the tree if there is no siblings before/after the node
14976
			function findParentContainer(container, child_name, sibling_name, root) {
14977
				var parent, child;
14978
 
14979
				root = root || dom.getRoot();
14980
 
14981
				for (;;) {
14982
					// Check if we can move up are we at root level or body level
14983
					parent = container.parentNode;
14984
 
14985
					// Stop expanding on block elements or root depending on format
14986
					if (parent == root || (!format[0].block_expand && isBlock(parent)))
14987
						return container;
14988
 
14989
					for (sibling = parent[child_name]; sibling && sibling != container; sibling = sibling[sibling_name]) {
14990
						if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
14991
							return container;
14992
 
14993
						if (sibling.nodeType == 3 && !isWhiteSpaceNode(sibling))
14994
							return container;
14995
					}
14996
 
14997
					container = container.parentNode;
14998
				}
14999
 
15000
				return container;
15001
			};
15002
 
15003
			// This function walks down the tree to find the leaf at the selection.
15004
			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15005
			function findLeaf(node, offset) {
15006
				if (offset === undefined)
15007
					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15008
				while (node && node.hasChildNodes()) {
15009
					node = node.childNodes[offset];
15010
					if (node)
15011
						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15012
				}
15013
				return { node: node, offset: offset };
15014
			}
15015
 
15016
			// If index based start position then resolve it
15017
			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15018
				lastIdx = startContainer.childNodes.length - 1;
15019
				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15020
 
15021
				if (startContainer.nodeType == 3)
15022
					startOffset = 0;
15023
			}
15024
 
15025
			// If index based end position then resolve it
15026
			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15027
				lastIdx = endContainer.childNodes.length - 1;
15028
				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15029
 
15030
				if (endContainer.nodeType == 3)
15031
					endOffset = endContainer.nodeValue.length;
15032
			}
15033
 
15034
			// Exclude bookmark nodes if possible
15035
			if (isBookmarkNode(startContainer.parentNode))
15036
				startContainer = startContainer.parentNode;
15037
 
15038
			if (isBookmarkNode(startContainer))
15039
				startContainer = startContainer.nextSibling || startContainer;
15040
 
15041
			if (isBookmarkNode(endContainer.parentNode)) {
15042
				endOffset = dom.nodeIndex(endContainer);
15043
				endContainer = endContainer.parentNode;
15044
			}
15045
 
15046
			if (isBookmarkNode(endContainer) && endContainer.previousSibling) {
15047
				endContainer = endContainer.previousSibling;
15048
				endOffset = endContainer.length;
15049
			}
15050
 
15051
			if (format[0].inline) {
15052
				// Avoid applying formatting to a trailing space.
15053
				leaf = findLeaf(endContainer, endOffset);
15054
				if (leaf.node) {
15055
					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
15056
						leaf = findLeaf(leaf.node.previousSibling);
15057
 
15058
					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
15059
							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
15060
 
15061
						if (leaf.offset > 1) {
15062
							endContainer = leaf.node;
15063
							endContainer.splitText(leaf.offset - 1);
15064
						} else if (leaf.node.previousSibling) {
15065
							endContainer = leaf.node.previousSibling;
15066
						}
15067
					}
15068
				}
15069
			}
15070
 
15071
			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
15072
			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
15073
			// This will reduce the number of wrapper elements that needs to be created
15074
			// Move start point up the tree
15075
			if (format[0].inline || format[0].block_expand) {
15076
				startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15077
				endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15078
			}
15079
 
15080
			// Expand start/end container to matching selector
15081
			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
15082
				function findSelectorEndPoint(container, sibling_name) {
15083
					var parents, i, y, curFormat;
15084
 
15085
					if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
15086
						container = container[sibling_name];
15087
 
15088
					parents = getParents(container);
15089
					for (i = 0; i < parents.length; i++) {
15090
						for (y = 0; y < format.length; y++) {
15091
							curFormat = format[y];
15092
 
15093
							// If collapsed state is set then skip formats that doesn't match that
15094
							if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
15095
								continue;
15096
 
15097
							if (dom.is(parents[i], curFormat.selector))
15098
								return parents[i];
15099
						}
15100
					}
15101
 
15102
					return container;
15103
				};
15104
 
15105
				// Find new startContainer/endContainer if there is better one
15106
				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
15107
				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
15108
			}
15109
 
15110
			// Expand start/end container to matching block element or text node
15111
			if (format[0].block || format[0].selector) {
15112
				function findBlockEndPoint(container, sibling_name, sibling_name2) {
15113
					var node;
15114
 
15115
					// Expand to block of similar type
15116
					if (!format[0].wrapper)
15117
						node = dom.getParent(container, format[0].block);
15118
 
15119
					// Expand to first wrappable block element or any block element
15120
					if (!node)
15121
						node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
15122
 
15123
					// Exclude inner lists from wrapping
15124
					if (node && format[0].wrapper)
15125
						node = getParents(node, 'ul,ol').reverse()[0] || node;
15126
 
15127
					// Didn't find a block element look for first/last wrappable element
15128
					if (!node) {
15129
						node = container;
15130
 
15131
						while (node[sibling_name] && !isBlock(node[sibling_name])) {
15132
							node = node[sibling_name];
15133
 
15134
							// Break on BR but include it will be removed later on
15135
							// we can't remove it now since we need to check if it can be wrapped
15136
							if (isEq(node, 'br'))
15137
								break;
15138
						}
15139
					}
15140
 
15141
					return node || container;
15142
				};
15143
 
15144
				// Find new startContainer/endContainer if there is better one
15145
				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
15146
				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
15147
 
15148
				// Non block element then try to expand up the leaf
15149
				if (format[0].block) {
15150
					if (!isBlock(startContainer))
15151
						startContainer = findParentContainer(startContainer, 'firstChild', 'nextSibling');
15152
 
15153
					if (!isBlock(endContainer))
15154
						endContainer = findParentContainer(endContainer, 'lastChild', 'previousSibling');
15155
				}
15156
			}
15157
 
15158
			// Setup index for startContainer
15159
			if (startContainer.nodeType == 1) {
15160
				startOffset = nodeIndex(startContainer);
15161
				startContainer = startContainer.parentNode;
15162
			}
15163
 
15164
			// Setup index for endContainer
15165
			if (endContainer.nodeType == 1) {
15166
				endOffset = nodeIndex(endContainer) + 1;
15167
				endContainer = endContainer.parentNode;
15168
			}
15169
 
15170
			// Return new range like object
15171
			return {
15172
				startContainer : startContainer,
15173
				startOffset : startOffset,
15174
				endContainer : endContainer,
15175
				endOffset : endOffset
15176
			};
15177
		}
15178
 
15179
		function removeFormat(format, vars, node, compare_node) {
15180
			var i, attrs, stylesModified;
15181
 
15182
			// Check if node matches format
15183
			if (!matchName(node, format))
15184
				return FALSE;
15185
 
15186
			// Should we compare with format attribs and styles
15187
			if (format.remove != 'all') {
15188
				// Remove styles
15189
				each(format.styles, function(value, name) {
15190
					value = replaceVars(value, vars);
15191
 
15192
					// Indexed array
15193
					if (typeof(name) === 'number') {
15194
						name = value;
15195
						compare_node = 0;
15196
					}
15197
 
15198
					if (!compare_node || isEq(getStyle(compare_node, name), value))
15199
						dom.setStyle(node, name, '');
15200
 
15201
					stylesModified = 1;
15202
				});
15203
 
15204
				// Remove style attribute if it's empty
15205
				if (stylesModified && dom.getAttrib(node, 'style') == '') {
15206
					node.removeAttribute('style');
15207
					node.removeAttribute('data-mce-style');
15208
				}
15209
 
15210
				// Remove attributes
15211
				each(format.attributes, function(value, name) {
15212
					var valueOut;
15213
 
15214
					value = replaceVars(value, vars);
15215
 
15216
					// Indexed array
15217
					if (typeof(name) === 'number') {
15218
						name = value;
15219
						compare_node = 0;
15220
					}
15221
 
15222
					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
15223
						// Keep internal classes
15224
						if (name == 'class') {
15225
							value = dom.getAttrib(node, name);
15226
							if (value) {
15227
								// Build new class value where everything is removed except the internal prefixed classes
15228
								valueOut = '';
15229
								each(value.split(/\s+/), function(cls) {
15230
									if (/mce\w+/.test(cls))
15231
										valueOut += (valueOut ? ' ' : '') + cls;
15232
								});
15233
 
15234
								// We got some internal classes left
15235
								if (valueOut) {
15236
									dom.setAttrib(node, name, valueOut);
15237
									return;
15238
								}
15239
							}
15240
						}
15241
 
15242
						// IE6 has a bug where the attribute doesn't get removed correctly
15243
						if (name == "class")
15244
							node.removeAttribute('className');
15245
 
15246
						// Remove mce prefixed attributes
15247
						if (MCE_ATTR_RE.test(name))
15248
							node.removeAttribute('data-mce-' + name);
15249
 
15250
						node.removeAttribute(name);
15251
					}
15252
				});
15253
 
15254
				// Remove classes
15255
				each(format.classes, function(value) {
15256
					value = replaceVars(value, vars);
15257
 
15258
					if (!compare_node || dom.hasClass(compare_node, value))
15259
						dom.removeClass(node, value);
15260
				});
15261
 
15262
				// Check for non internal attributes
15263
				attrs = dom.getAttribs(node);
15264
				for (i = 0; i < attrs.length; i++) {
15265
					if (attrs[i].nodeName.indexOf('_') !== 0)
15266
						return FALSE;
15267
				}
15268
			}
15269
 
15270
			// Remove the inline child if it's empty for example <b> or <span>
15271
			if (format.remove != 'none') {
15272
				removeNode(node, format);
15273
				return TRUE;
15274
			}
15275
		};
15276
 
15277
		function removeNode(node, format) {
15278
			var parentNode = node.parentNode, rootBlockElm;
15279
 
15280
			if (format.block) {
15281
				if (!forcedRootBlock) {
15282
					function find(node, next, inc) {
15283
						node = getNonWhiteSpaceSibling(node, next, inc);
15284
 
15285
						return !node || (node.nodeName == 'BR' || isBlock(node));
15286
					};
15287
 
15288
					// Append BR elements if needed before we remove the block
15289
					if (isBlock(node) && !isBlock(parentNode)) {
15290
						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
15291
							node.insertBefore(dom.create('br'), node.firstChild);
15292
 
15293
						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
15294
							node.appendChild(dom.create('br'));
15295
					}
15296
				} else {
15297
					// Wrap the block in a forcedRootBlock if we are at the root of document
15298
					if (parentNode == dom.getRoot()) {
15299
						if (!format.list_block || !isEq(node, format.list_block)) {
15300
							each(tinymce.grep(node.childNodes), function(node) {
15301
								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
15302
									if (!rootBlockElm)
15303
										rootBlockElm = wrap(node, forcedRootBlock);
15304
									else
15305
										rootBlockElm.appendChild(node);
15306
								} else
15307
									rootBlockElm = 0;
15308
							});
15309
						}
15310
					}
15311
				}
15312
			}
15313
 
15314
			// Never remove nodes that isn't the specified inline element if a selector is specified too
15315
			if (format.selector && format.inline && !isEq(format.inline, node))
15316
				return;
15317
 
15318
			dom.remove(node, 1);
15319
		};
15320
 
15321
		function getNonWhiteSpaceSibling(node, next, inc) {
15322
			if (node) {
15323
				next = next ? 'nextSibling' : 'previousSibling';
15324
 
15325
				for (node = inc ? node : node[next]; node; node = node[next]) {
15326
					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
15327
						return node;
15328
				}
15329
			}
15330
		};
15331
 
15332
		function isBookmarkNode(node) {
15333
			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
15334
		};
15335
 
15336
		function mergeSiblings(prev, next) {
15337
			var marker, sibling, tmpSibling;
15338
 
15339
			function compareElements(node1, node2) {
15340
				// Not the same name
15341
				if (node1.nodeName != node2.nodeName)
15342
					return FALSE;
15343
 
15344
				function getAttribs(node) {
15345
					var attribs = {};
15346
 
15347
					each(dom.getAttribs(node), function(attr) {
15348
						var name = attr.nodeName.toLowerCase();
15349
 
15350
						// Don't compare internal attributes or style
15351
						if (name.indexOf('_') !== 0 && name !== 'style')
15352
							attribs[name] = dom.getAttrib(node, name);
15353
					});
15354
 
15355
					return attribs;
15356
				};
15357
 
15358
				function compareObjects(obj1, obj2) {
15359
					var value, name;
15360
 
15361
					for (name in obj1) {
15362
						// Obj1 has item obj2 doesn't have
15363
						if (obj1.hasOwnProperty(name)) {
15364
							value = obj2[name];
15365
 
15366
							// Obj2 doesn't have obj1 item
15367
							if (value === undefined)
15368
								return FALSE;
15369
 
15370
							// Obj2 item has a different value
15371
							if (obj1[name] != value)
15372
								return FALSE;
15373
 
15374
							// Delete similar value
15375
							delete obj2[name];
15376
						}
15377
					}
15378
 
15379
					// Check if obj 2 has something obj 1 doesn't have
15380
					for (name in obj2) {
15381
						// Obj2 has item obj1 doesn't have
15382
						if (obj2.hasOwnProperty(name))
15383
							return FALSE;
15384
					}
15385
 
15386
					return TRUE;
15387
				};
15388
 
15389
				// Attribs are not the same
15390
				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
15391
					return FALSE;
15392
 
15393
				// Styles are not the same
15394
				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
15395
					return FALSE;
15396
 
15397
				return TRUE;
15398
			};
15399
 
15400
			// Check if next/prev exists and that they are elements
15401
			if (prev && next) {
15402
				function findElementSibling(node, sibling_name) {
15403
					for (sibling = node; sibling; sibling = sibling[sibling_name]) {
15404
						if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
15405
							return node;
15406
 
15407
						if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
15408
							return sibling;
15409
					}
15410
 
15411
					return node;
15412
				};
15413
 
15414
				// If previous sibling is empty then jump over it
15415
				prev = findElementSibling(prev, 'previousSibling');
15416
				next = findElementSibling(next, 'nextSibling');
15417
 
15418
				// Compare next and previous nodes
15419
				if (compareElements(prev, next)) {
15420
					// Append nodes between
15421
					for (sibling = prev.nextSibling; sibling && sibling != next;) {
15422
						tmpSibling = sibling;
15423
						sibling = sibling.nextSibling;
15424
						prev.appendChild(tmpSibling);
15425
					}
15426
 
15427
					// Remove next node
15428
					dom.remove(next);
15429
 
15430
					// Move children into prev node
15431
					each(tinymce.grep(next.childNodes), function(node) {
15432
						prev.appendChild(node);
15433
					});
15434
 
15435
					return prev;
15436
				}
15437
			}
15438
 
15439
			return next;
15440
		};
15441
 
15442
		function isTextBlock(name) {
15443
			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
15444
		};
15445
 
15446
		function getContainer(rng, start) {
15447
			var container, offset, lastIdx;
15448
 
15449
			container = rng[start ? 'startContainer' : 'endContainer'];
15450
			offset = rng[start ? 'startOffset' : 'endOffset'];
15451
 
15452
			if (container.nodeType == 1) {
15453
				lastIdx = container.childNodes.length - 1;
15454
 
15455
				if (!start && offset)
15456
					offset--;
15457
 
15458
				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
15459
			}
15460
 
15461
			return container;
15462
		};
15463
 
15464
		function performCaretAction(type, name, vars) {
15465
			var i, currentPendingFormats = pendingFormats[type],
15466
				otherPendingFormats = pendingFormats[type == 'apply' ? 'remove' : 'apply'];
15467
 
15468
			function hasPending() {
15469
				return pendingFormats.apply.length || pendingFormats.remove.length;
15470
			};
15471
 
15472
			function resetPending() {
15473
				pendingFormats.apply = [];
15474
				pendingFormats.remove = [];
15475
			};
15476
 
15477
			function perform(caret_node) {
15478
				// Apply pending formats
15479
				each(pendingFormats.apply.reverse(), function(item) {
15480
					apply(item.name, item.vars, caret_node);
15481
 
15482
					// Colored nodes should be underlined so that the color of the underline matches the text color.
15483
					if (item.name === 'forecolor' && item.vars.value)
15484
						processUnderlineAndColor(caret_node.parentNode);
15485
				});
15486
 
15487
				// Remove pending formats
15488
				each(pendingFormats.remove.reverse(), function(item) {
15489
					remove(item.name, item.vars, caret_node);
15490
				});
15491
 
15492
				dom.remove(caret_node, 1);
15493
				resetPending();
15494
			};
15495
 
15496
			// Check if it already exists then ignore it
15497
			for (i = currentPendingFormats.length - 1; i >= 0; i--) {
15498
				if (currentPendingFormats[i].name == name)
15499
					return;
15500
			}
15501
 
15502
			currentPendingFormats.push({name : name, vars : vars});
15503
 
15504
			// Check if it's in the other type, then remove it
15505
			for (i = otherPendingFormats.length - 1; i >= 0; i--) {
15506
				if (otherPendingFormats[i].name == name)
15507
					otherPendingFormats.splice(i, 1);
15508
			}
15509
 
15510
			// Pending apply or remove formats
15511
			if (hasPending()) {
15512
				ed.getDoc().execCommand('FontName', false, 'mceinline');
15513
				pendingFormats.lastRng = selection.getRng();
15514
 
15515
				// IE will convert the current word
15516
				each(dom.select('font,span'), function(node) {
15517
					var bookmark;
15518
 
15519
					if (isCaretNode(node)) {
15520
						bookmark = selection.getBookmark();
15521
						perform(node);
15522
						selection.moveToBookmark(bookmark);
15523
						ed.nodeChanged();
15524
					}
15525
				});
15526
 
15527
				// Only register listeners once if we need to
15528
				if (!pendingFormats.isListening && hasPending()) {
15529
					pendingFormats.isListening = true;
15530
					function performPendingFormat(node, textNode) {
15531
						var rng = dom.createRng();
15532
						perform(node);
15533
 
15534
						rng.setStart(textNode, textNode.nodeValue.length);
15535
						rng.setEnd(textNode, textNode.nodeValue.length);
15536
						selection.setRng(rng);
15537
						ed.nodeChanged();
15538
					}
15539
					var enterKeyPressed = false;
15540
 
15541
					each('onKeyDown,onKeyUp,onKeyPress,onMouseUp'.split(','), function(event) {
15542
						ed[event].addToTop(function(ed, e) {
15543
							if (e.keyCode==13 && !e.shiftKey) {
15544
								enterKeyPressed = true;
15545
								return;
15546
							}
15547
							// Do we have pending formats and is the selection moved has moved
15548
							if (hasPending() && !tinymce.dom.RangeUtils.compareRanges(pendingFormats.lastRng, selection.getRng())) {
15549
								var foundCaret = false;
15550
								each(dom.select('font,span'), function(node) {
15551
									var textNode, rng;
15552
 
15553
									// Look for marker
15554
									if (isCaretNode(node)) {
15555
										foundCaret = true;
15556
										textNode = node.firstChild;
15557
 
15558
										// Find the first text node within node
15559
										while (textNode && textNode.nodeType != 3)
15560
											textNode = textNode.firstChild;
15561
 
15562
										if (textNode)
15563
											performPendingFormat(node, textNode);
15564
										else
15565
											dom.remove(node);
15566
									}
15567
								});
15568
 
15569
								// no caret - so we are
15570
								if (enterKeyPressed && !foundCaret) {
15571
									var node = selection.getNode();
15572
									var textNode = node;
15573
 
15574
									// Find the first text node within node
15575
									while (textNode && textNode.nodeType != 3)
15576
										textNode = textNode.firstChild;
15577
									if (textNode) {
15578
										node=textNode.parentNode;
15579
										while (!isBlock(node)){
15580
											node=node.parentNode;
15581
										}
15582
										performPendingFormat(node, textNode);
15583
									}
15584
								}
15585
 
15586
								// Always unbind and clear pending styles on keyup
15587
								if (e.type == 'keyup' || e.type == 'mouseup') {
15588
									resetPending();
15589
									enterKeyPressed=false;
15590
								}
15591
							}
15592
						});
15593
					});
15594
				}
15595
			}
15596
		};
15597
	};
15598
})(tinymce);
15599
 
15600
tinymce.onAddEditor.add(function(tinymce, ed) {
15601
	var filters, fontSizes, dom, settings = ed.settings;
15602
 
15603
	if (settings.inline_styles) {
15604
		fontSizes = tinymce.explode(settings.font_size_style_values);
15605
 
15606
		function replaceWithSpan(node, styles) {
15607
			tinymce.each(styles, function(value, name) {
15608
				if (value)
15609
					dom.setStyle(node, name, value);
15610
			});
15611
 
15612
			dom.rename(node, 'span');
15613
		};
15614
 
15615
		filters = {
15616
			font : function(dom, node) {
15617
				replaceWithSpan(node, {
15618
					backgroundColor : node.style.backgroundColor,
15619
					color : node.color,
15620
					fontFamily : node.face,
15621
					fontSize : fontSizes[parseInt(node.size) - 1]
15622
				});
15623
			},
15624
 
15625
			u : function(dom, node) {
15626
				replaceWithSpan(node, {
15627
					textDecoration : 'underline'
15628
				});
15629
			},
15630
 
15631
			strike : function(dom, node) {
15632
				replaceWithSpan(node, {
15633
					textDecoration : 'line-through'
15634
				});
15635
			}
15636
		};
15637
 
15638
		function convert(editor, params) {
15639
			dom = editor.dom;
15640
 
15641
			if (settings.convert_fonts_to_spans) {
15642
				tinymce.each(dom.select('font,u,strike', params.node), function(node) {
15643
					filters[node.nodeName.toLowerCase()](ed.dom, node);
15644
				});
15645
			}
15646
		};
15647
 
15648
		ed.onPreProcess.add(convert);
15649
		ed.onSetContent.add(convert);
15650
 
15651
		ed.onInit.add(function() {
15652
			ed.selection.onSetContent.add(convert);
15653
		});
15654
	}
15655
});
15656