(function(punymce) {
	var DOMUtils, Engine, Control, Editor, Selection, Dispatcher, Event, Serializer, I18n; // Shorten class names
	var pageDOM, isIE, isGecko, isOpera, isWebKit, isOldWebKit, ua; // Global objects

	// Browser checks
	ua = navigator.userAgent;
	punymce.isOpera = isOpera = window['opera'] && opera.buildNumber;
	punymce.isWebKit = isWebKit = /WebKit/.test(ua);
	punymce.isOldWebKit = isOldWebKit = isWebKit && !window.getSelection().getRangeAt;
	punymce.isIE = isIE = !isWebKit && !isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(navigator.appName);
	punymce.isGecko = isGecko = !isWebKit && /Gecko/.test(ua);

	// Plugins namespace
	punymce.plugins = {};

	// Global functions

	function is(o, t) {
		o = typeof(o);

		if (!t)
			return o != 'undefined';

		return o == t;
	};

	function each(o, cb, s) {
		var n;

		if (!o)
			return 0;

		s = !s ? o : s;

		if (is(o.length)) {
			// Indexed arrays, needed for Safari
			for (n=0; n<o.length; n++) {
				if (cb.call(s, o[n], n, o) === false)
					return 0;
			}
		} else {
			// Hashtables
			for (n in o) {
				if (o.hasOwnProperty(n)) {
					if (cb.call(s, o[n], n, o) === false)
						return 0;
				}
			}
		}

		return 1;
	};

	function extend(o, e) {
		each(e, function(v, n) {
			o[n] = v;
		});

		return o;
	};

	// Store them in API as well for plugin use
	extend(punymce, {
		is : is,
		each : each,
		extend : extend
	});

	// English language pack
	punymce.I18n = I18n = {
		bold : 'Bold (Ctrl+B)',
		italic : 'Italic (Ctrl+I)',
		underline : 'Underline (Ctrl+U)',
		strike : 'Striketrough',
		ul : 'Insert unordered list',
		ol : 'Insert ordered list',
		indent : 'Indent',
		outdent : 'Outdent',
		left : 'Align left',
		center : 'Align center',
		right : 'Align right',
		style : 'Font style',
		removeformat : 'Remove format',
		increasefontsize : 'Increase text size',
		decreasefontsize : 'Decrease text size'
	};

	// Static DOM class
	punymce.DOMUtils = DOMUtils = function(d) {
		this.files = [];

		// Fix IE6SP2 flicker
		try {d.execCommand('BackgroundImageCache', 0, 1);} catch (e) {}

		// Public methods
		extend(this, {
			get : function(e) {
				return e && (!e.nodeType && !e.location ? d.getElementById(e) : e);
			},

			add : function(p, n, a, h) {
				var t = this, e;

				e = d.createElement(n);

				each(a, function(v, n) {
					t.setAttr(e, n, v);
				});

				if (h) {
					if (h.nodeType)
						e.appendChild(h);
					else
						e.innerHTML = h;
				}

				return p ? p.appendChild(e) : e;
			},

			create : function(n, a, h) {
				return this.add(0, n, a, h);
			},

			setAttr : function(e, n, v) {
				e = this.get(e);

				if (!e)
					return 0;

				if (n == "style") {
					e.setAttribute('mce_style', v);
					e.style.cssText = v;
				}

				if (n == "class")
					e.className = v;

				if (v != null && v != "")
					e.setAttribute(n, '' + v);
				else
					e.removeAttribute(n);

				return 1;
			},

			getAttr : function(e, n, dv) {
				var v;

				e = this.get(e);

				if (!e)
					return false;

				if (!is(dv))
					dv = "";

				// Try the mce variant for these
				if (/^(src|href|style)$/.test(n)) {
					v = this.getAttr(e, "mce_" + n);

					if (v)
						return v;
				}

				v = e.getAttribute(n, 2);

				if (n == "class" && !v)
					v = e.className;

				if (n == "style" && !v)
					v = e.style.cssText;
				else if (!v) {
					v = e.attributes[n];
					v = v && is(v.nodeValue) ? v.nodeValue : v;
				}

				// Remove Apple and WebKit stuff
				if (isWebKit && n == "class" && v)
					v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');

				return (v && v != "") ? '' + v : dv;
			},

			select : function(k, n, f) {
				var o = [];

				n = this.get(n);
				n = !n ? d : n;

				each(k.split(','), function(v) {
					each(n.getElementsByTagName(v), function(v) {
						if (!f || f(v))
							o.push(v);
					});
				});

				return o;
			},

			getPos : function(n, cn) {
				var l = 0, t = 0, p, r, d;

				n = this.get(n);

				// IE specific method (less quirks in IE6)
				if (punymce.isIE && n) {
					r = n.getBoundingClientRect();
					d = document;
					n = d.compatMode == 'CSS1Compat' ? d.documentElement : d.body;

					return { x : r.left + (n.scrollLeft || 0), y : r.top + (n.scrollTop || 0) };
				}

				while (n && n != cn) {
					l += n.offsetLeft || 0;
					t += n.offsetTop || 0;
					n = n.offsetParent;
				}

				return {x : l, y : t};
			},

			loadCSS : function(u) {
				var t = this;

				if (u) {
					each(u.split(','), function(u) {
						var l = -1;
	
						each(t.files, function(c, i) {
							if (c == u) {
								l = i;
								return false;
							}
						});
	
						if (l != -1)
							return;
	
						t.files.push(u);
	
						if (!d.createStyleSheet)
							t.add(t.select('head')[0], 'link', {rel : 'stylesheet', href : u});
						else
							d.createStyleSheet(u);
					});
				}
			},

			addClass : function(e, c, b) {
				var o;

				e = this.get(e);

				if (!e)
					return 0;

				o = this.removeClass(e, c);

				return e.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c;
			},

			removeClass : function(e, c) {
				e = this.get(e);

				if (!e)
					return 0;

				c = e.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g"), ' ');

				return e.className = c != ' ' ? c : '';
			},

			hasClass : function(n, c) {
				n = this.get(n);

				return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
			},

			getParent : function(n, f, r) {
				while (n) {
					if (n == r)
						return null;

					if (f(n))
						return n;

					n = n.parentNode;
				}

				return null;
			},

			keep : function(h) {
				// Convert strong and em to b and i in FF since it can't handle them
				if (isGecko) {
					h = h.replace(/<(\/?)strong>|<strong( [^>]+)>/gi, '<$1b$2>');
					h = h.replace(/<(\/?)em>|<em( [^>]+)>/gi, '<$1i$2>');
					h = h.replace(/<(\/?)del|<del( [^>]+)>/gi, '<$1strike$2>');
				}

				// Store away src and href in mce_src and mce_href since browsers mess them up
				h = h.replace(/ (src|href|style)=\"([^\"]+)\"/gi, ' $1="$2" mce_$1="$2"');

				return h;
			}
		});
	};

	// Global DOM instance
	punymce.DOM = pageDOM = new DOMUtils(document);

	// Static Event class
	punymce.Event = Event = {
		events : [],
		inits : [],
		unloads : [],

		add : function(o, n, f, s) {
			var cb, t = this, el = t.events;

			o = pageDOM.get(o);

			// Setup event callback
			cb = function(e) {
				e = e || window.event;

				// Patch in target in IE it's W3C valid
				if (e && !e.target && isIE)
					e.target = e.srcElement;

				if (!s)
					return f(e);

				return f.call(s, e);
			};

			if (n == 'unload') {
				t.unloads.push(cb);
				return cb;
			}

			if (n == 'init') {
				if (t._init)
					cb();
				else
					t.inits.push(cb);

				return cb;
			}

			// Store away listener reference
			el.push({
				obj : o,
				name : n,
				func : f,
				cfunc : cb,
				scope : s
			});

			t._add(o, n, cb);

			// Cleanup memory leaks in IE
			if (isIE && el.length == 1)
				Event._add(window, 'unload', t._unload, Event);

			return cb;
		},

		remove : function(o, n, f) {
			var t = this, a = t.events;

			o = pageDOM.get(o);

			each(a, function(e, i) {
				if (e.obj == o && e.name == n && (!f || e.func == f)) {
					a.splice(i, 1);
					t._remove(o, n, e.cfunc);
					return false;
				}
			});
		},

		cancel : function(e) {
			this.stop(e);
			return this.prevent(e);
		},

		stop : function(e) {
			if (e.stopPropagation)
				e.stopPropagation();
			else
				e.cancelBubble = true;

			return false;
		},

		prevent : function(e) {
			if (e.preventDefault)
				e.preventDefault();
			else
				e.returnValue = false;

			return false;
		},

		_unload : function() {
			var t = Event;

			each(t.events, function(e) {
				t._remove(e.obj, e.name, e.cfunc);
			});

			t._remove(window, 'unload', t._unload);
			t.events = [];

			each(t.unloads, function(e) {
				e();
			});
		},

		_add : function(o, n, f) {
			if (o.attachEvent)
				o.attachEvent('on' + n, f);
			else if (o.addEventListener)
				o.addEventListener(n, f, false);
			else
				o['on' + n] = f;
		},

		_remove : function(o, n, f) {
			if (o.detachEvent)
				o.detachEvent('on' + n, f);
			else if (o.removeEventListener)
				o.removeEventListener(n, f, false);
			else
				o['on' + n] = null;
		},

		_pageInit : function() {
			var e = Event;

			e._remove(window, 'DOMContentLoaded', e._pageInit);
			e._init = true;

			each(e.inits, function(c) {
				c();
			});

			e.inits = [];
		},

		_wait : function() {
			var t;

			if (isIE && document.location.protocol != 'https:') {
				// Fake DOMContentLoaded on IE when not running under HTTPs
				document.write('<script id=__ie_onload defer src=javascript:void(0)><\/script>');
				pageDOM.get("__ie_onload").onreadystatechange = function() {
					if (this.readyState == "complete") {
						Event._pageInit();
						pageDOM.get("__ie_onload").onreadystatechange = null; // Prevent leak
					}
				};
			} else {
				Event._add(window, 'DOMContentLoaded', Event._pageInit, Event);

				if (isIE || isWebKit) {
					t = setInterval(function() {
						if (/loaded|complete/.test(document.readyState)) {
							clearInterval(t);
							Event._pageInit();
						}
					}, 10);
				}
			}
		}
	};

	punymce.Dispatcher = Dispatcher = function(ds) {
		var cbl = [];

		if (!ds)
			ds = this;

		// Public methods
		extend(this, {
			add : function(cb, s) {
				cbl.push({cb : cb, scope : !s ? ds : s});

				return cb;
			},

			remove : function(cb) {
				each(cbl, function(c, i) {
					if (cb == c.cb)
						cbl.splice(i, 1);

					return false;
				});

				return cb;
			},

			dispatch : function() {
				var s, a = arguments;

				each(cbl, function(c) {
					return s = c.cb.apply(c.scope, a);
				});

				return s;
			}
		});
	};

	punymce.Editor = Editor = function(e) {
		var s, DOM, t = this;

		// Setup baseURL
		if (!punymce.baseURL) {
			each(pageDOM.select('script'), function(n) {
				if (/puny_mce/g.test('' + n.src))
					punymce.baseURL = n.src.replace(/^(.+)\/puny_mce.+$/, '$1/');
			});
		}

		// Default settings
		this.settings = s = extend({
			content_css : punymce.baseURL + '/css/content.css',
			editor_css : punymce.baseURL + '/css/editor.css',
			width : 0,
			height : 0,
			min_width : 260,
			min_height : 50,
			max_width : 800,
			max_height : 600,
			entities : 'raw',
			spellcheck : 0,
			resize : true,
			plugins : '',

			styles : [
				{ title : 'H1', cls : 'h1', cmd : 'FormatBlock', val : '<h1>' },
				{ title : 'H2', cls : 'h2', cmd : 'FormatBlock', val : '<h2>' },
				{ title : 'H3', cls : 'h3', cmd : 'FormatBlock', val : '<h3>' },
				{ title : 'Pre', cls : 'pre', cmd : 'FormatBlock', val : '<pre>' },
				{ title : 'Times', cls : 'times', cmd : 'FontName', val : 'Times'},
				{ title : 'Arial', cls : 'arial', cmd : 'FontName', val : 'Arial'},
				{ title : 'Courier', cls : 'courier', cmd : 'FontName', val : 'Courier'}
			],

			toolbar : 'bold,italic,underline,strike,increasefontsize,decreasefontsize,ul,ol,indent,outdent,left,center,right,style,removeformat'
		}, e);

		// Default tools
		t.tools = {
			bold : {cmd : 'Bold', title : I18n.bold},
			italic : {cmd : 'Italic', title : I18n.italic},
			underline : {cmd : 'Underline', title : I18n.underline},
			strike : {cmd : 'Strikethrough', title : I18n.strike},
			ul : {cmd : 'InsertUnorderedList', title : I18n.ul},
			ol : {cmd : 'InsertOrderedList', title : I18n.ol},
			indent : {cmd : 'Indent', title : I18n.indent},
			outdent : {cmd : 'Outdent', title : I18n.outdent},
			left : {cmd : 'JustifyLeft', title : I18n.left},
			center : {cmd : 'JustifyCenter', title : I18n.center},
			right : {cmd : 'JustifyRight', title : I18n.right},
			style : {cmd : 'mceStyle', title : I18n.style},
			removeformat : {cmd : 'RemoveFormat', title : I18n.removeformat},
			increasefontsize : {cmd : 'IncreaseFontSize', title : I18n.increasefontsize},
			decreasefontsize : {cmd : 'DecreaseFontSize', title : I18n.decreasefontsize}
		};

		pageDOM.loadCSS(s.editor_css);

		// Default commands
		this.commands = {
			mceStyle : function(u, v, e) {
				var n, t = this, s = t.settings, id = s.id, p = pageDOM.getPos(e.target), cb;

				if (t.hideMenu)
					return t.hideMenu();

				function hide(e) {
					t.hideMenu = null;
					Event.remove(document, 'click', hide);
					Event.remove(t.getDoc(), 'click', hide);
					pageDOM.get(id + '_mstyle').style.display = 'none';
				};

				n = pageDOM.get(id + '_mstyle');
				if (!n) {
					n = pageDOM.get(id + '_t');
					n = pageDOM.add(document.body, 'div', {id : id + '_mstyle', 'class' : 'punymce_style punymce'});
					each(s.styles, function(r) {
						Event.add(pageDOM.add(n, 'a', {href : '#', 'class' : r.cls}, r.title), 'mousedown', function(e) {
							hide.call(t);

							t.execCommand(r.cmd, r.ui, r.val);

							// Opera looses focus (could not be placed in execCommand)
							if (isOpera)
								t.getIfr().focus();

							return Event.cancel(e);
						});
					});
				}

				t.hideMenu = hide;
				Event.add(document, 'click', hide, t);
				Event.add(t.getDoc(), 'click', hide, t);

				s = n.style;
				s.left = p.x + 'px';
				s.top = (p.y + e.target.clientHeight + 2) + 'px';
				s.display = 'block';
			},

			FormatBlock : function(u, v, e) {
				// Gecko can't handle <> correctly on some elements
				if (isGecko && /<(div|blockquote|code|dt|dd|dl|samp)>/gi.test(v))
					v = v.replace(/[^a-z]/gi, '');

				t.getDoc().execCommand("FormatBlock", 0, v);
			},

			CreateLink : function(u, v) {
				var dom = t.dom, k = 'javascript:mox();';

				t.getDoc().execCommand("CreateLink", 0, k);
				each(dom.select('A'), function(n) {
					if (dom.getAttr(n, 'href') == k) {
						dom.setAttr(n, 'href', v);
						dom.setAttr(n, 'mce_href', v);
					}
				});
			},

			mceFontSizeDelta : function(u, v) {
				var d = t.getDoc(), cv, sp, fo, s = t.selection;

				cv = parseInt(d.queryCommandValue('FontSize') || 3);

				// WebKit returns pixel value, convert it to a size value
				if (isWebKit) {
					each([10, 13, 16, 18, 24, 32, 48], function(c, i) {
						if (cv == c) {
							cv = i + 1;
							return false;
						}
					});
				}

				if (cv + v <= 1)
					return;

				d.execCommand('FontSize', false, cv + v);
			},

			IncreaseFontSize : function() {
				t.execCommand('mceFontSizeDelta', 0, 1);
			},

			DecreaseFontSize : function() {
				t.execCommand('mceFontSizeDelta', 0, -1);
			},

			Indent : function(u, v, e) {
				t.getDoc().execCommand("Indent", 0, 0);

				if (isIE) {
					// IE adds strange ltr and margin right when making a blockquote
					t.dom.getParent(t.selection.getNode(), function(n) {
						if (n.nodeName == 'BLOCKQUOTE')
							n.dir = n.style.marginRight = '';
					});
				}
			},

			mceSetClass : function(u, v, e) {
				var s = t.selection;

				// Wrap it
				if (is(v, 'string')) {
					v = {
						element : 'span',
						'class' : v
					};
				}

				if (s.isCollapsed())
					t.dom.setAttr(s.getNode(), 'class', v['class']);
				else
					s.setContent('<' + v.element + ' class="' + v['class'] + '">' + s.getContent() + '</' + v.element + '>');
			},

			RemoveFormat : function(u, v, e) {
				var s = t.selection;

				t.getDoc().execCommand('RemoveFormat', u, v);

				// Extra format removal for IE 6
				if (isIE) {
					v = s.getContent();

					v = v.replace(/ (class|style)=\"[^\"]+\"/g, '');
					v = v.replace(/<\/?(font|strong|em|b|i|u|strike)>/g, '');
					v = v.replace(/<(font|strong|em|b|i|u|strike) [^>]+>/g, '');

					s.setContent(v);
				}
			}
		};

		// Private methods

		function startResize(e) {
			var c, p, w, h;

			// Measure container
			c = pageDOM.get(s.id + '_c');
			w = c.clientWidth - 2;
			h = c.clientHeight - 2;

			// Setup placeholder
			p = pageDOM.get(s.id + '_p');
			p.style.width = w + 'px';
			p.style.height = h + 'px';

			// Replace with placeholder
			c.style.display = 'none';
			p.style.display = 'block';

			// Create internal resize obj
			t.resize = {
				x : e.screenX,
				y : e.screenY,
				w : w,
				h : h
			};

			// Start listening
			Event.add(document, 'mousemove', resizeMove, this);
			Event.add(document, 'mouseup', endResize, this);

			t.onResizeStart.dispatch(t, e, w, h);
		};

		function resizeMove(e) {
			var r = t.resize, p, w, h;

			// Calc delta values
			r.dx = e.screenX - r.x;
			r.dy = e.screenY - r.y;

			// Boundery fix box
			w = Math.max(s.min_width, r.w + r.dx);
			h = Math.max(s.min_height, r.h + r.dy);
			w = Math.min(s.max_width, w);
			h = Math.min(s.max_height, h);

			// Resize placeholder
			p = pageDOM.get(s.id + '_p');
			p.style.width = w + 'px';
			p.style.height = h + 'px';

			return Event.cancel(e);
		};

		function endResize(e) {
			var r = t.resize;

			// Stop listening
			Event.remove(document, 'mousemove', resizeMove, this);
			Event.remove(document, 'mouseup', endResize, this);

			// Replace with editor
			pageDOM.get(s.id + '_c').style.display = '';
			pageDOM.get(s.id + '_p').style.display = 'none';

			// Resize it to new size
			t.resizeBy(r.dx, r.dy);

			t.onResizeEnd.dispatch(t, e, r.dx, r.dy);
		};

		function setup() {
			var e = pageDOM.get(s.id), d = t.getDoc();

			// Design mode needs to be added here Ctrl+A will fail otherwise
			if (isGecko)
				t.getDoc().designMode = 'On';

			// Setup body
			d.open();
			d.write('<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body id="punymce"></body></html>');
			d.close();

			t.dom = DOM = new DOMUtils(t.getDoc());
			DOM.loadCSS(s.content_css);

			t.onPreInit.dispatch(t);

			if (!s.spellcheck)
				t.getBody().spellcheck = 0;

			// Add node change handlers
			Event.add(t.getDoc(), 'mouseup', t.nodeChanged, t);
			Event.add(t.getDoc(), 'keyup', t.nodeChanged, t );

			// Add focus event
			Event.add(isGecko ? t.getDoc() : t.getWin(), 'focus', function(e) {
				var ed;

				if ((ed = punymce.focusEditor) != null)
					ed.onBlur.dispatch(ed, t);

				t.onFocus.dispatch(t, ed);
				punymce.focusEditor = t;
			}, this);

			// IE fired load event twice if designMode is set
			if (!isIE)
				t.getDoc().designMode = 'On';
			else
				t.getBody().contentEditable = true;

			t.setContent(is(e.value) ? e.value : e.innerHTML, {load : true});

			pageDOM.get(s.id + '_c').style.display = t.orgDisplay;
			pageDOM.get(s.id).style.display = 'none';

			t.onInit.dispatch(t);
		};

		// Public fields
		extend(this, {
			serializer : new Serializer(t),
			selection : new Selection(t),
			plugins : []
		});

		// Add events
		each(['onPreInit', 'onInit', 'onFocus', 'onBlur', 'onResizeStart', 'onResizeEnd', 'onPreProcess', 'onPostProcess', 'onSetContent', 'onBeforeGetContent', 'onGetContent', 'onNodeChange'], function(e) {
			t[e] = new Dispatcher(t);
		});

		// Public methods
		extend(this, {
			init : function() {
				var e = pageDOM.get(s.id), pe = e.parentNode, w, h, ht, ul, n, r, f, sta = ['bold', 'italic', 'underline', 'left', 'center', 'right'];

				// Create plugins
				each(s.plugins.split(','), function(p) {
					if (p)
						t.plugins.push(new punymce.plugins[p](t));
				});

				// Handle common states
				t.onNodeChange.add(function() {
					each(sta, function(n) {
						var f;

						try {
							f = t.getDoc().queryCommandState(t.tools[n].cmd) ? pageDOM.addClass : pageDOM.removeClass;
							f.call(pageDOM, s.id + '_' + n, 'active');
						} catch (ex) {
							// Might fail
						}
					});
				});

				// Setup numeric entities
				if (s.entities == 'numeric') {
					t.onGetContent.add(function(ed, o) {
						if (o.format == 'html') {
							o.content = o.content.replace(/[\u007E-\uFFFF]/g, function(a) {
								return '&#' + a.charCodeAt(0) + ';';
							});
						}
					});
				}

				w = !s.width ? e.offsetWidth : s.width;
				h = !s.height ? e.offsetHeight : s.height;
				t.orgDisplay = e.style.display;
				e.style.display = 'none';

				// Add submit handlers
				if (e.form) {
					f = e.form;

					// Piggy back
					t._submit = f.submit;
					f.submit = function() {
						var e = pageDOM.get(s.id), f = e.form;
						t.save();
						f.submit = t._submit;
						f.submit();
					};

					// Prevent IE from memory leaking
					Event.add(0, 'unload', function() {
						var f = pageDOM.get(s.id).form;
						t._submit = t.submit = null;
					});

					// Submit event
					Event.add(f, 'submit', t.save, t);
				}

				// Setup UI table, could not use DOM since IE couldn't add the table this method is smaller in size anyway
				ht = '<div id="' + s.id + '_w" class="punymce"><table id="' + s.id + '_c" class="punymce"><tr class="mceToolbar">';
				ht += '<td id="' + s.id + '_t"></td></tr><tr class="mceBody"><td></div><div id="' + s.id + '_b" class="mceBody">';

				if (s.resize)
					ht += '<div id="' + s.id + '_r" class="mceResize"></div>';
	
				ht += '</td></tr></table>';
				ht += '<div id="' + s.id + '_p" class="mcePlaceholder"></div></div>';

				if (!e.insertAdjacentHTML) {
					r = e.ownerDocument.createRange();
					r.setStartBefore(e);
					pe.insertBefore(r.createContextualFragment(ht), e);
				} else
					e.insertAdjacentHTML("beforeBegin", ht);

				// Add tools to toolbar
				n = pageDOM.get(s.id + '_t');
				ul = pageDOM.add(n, 'ul', {id : s.id + '_tb', 'class' : 'punymce'});

				each(s.toolbar.split(','), function(v) {
					var to = t.tools[v];

					n = pageDOM.add(pageDOM.add(ul, 'li', {id : s.id + '_' + v, 'class' : v}), 'a', {'href' : 'javascript:void(0);', 'class' : v, title : to.title, tabIndex : -1, onmousedown : 'return false;'});

					Event.add(n, 'click', function(e) {
						if (!pageDOM.hasClass(e.target.parentNode, 'disabled'))
							t.execCommand(to.cmd, 0, 0, e);

						return Event.cancel(e);
					});
				});

				// Add iframe to body
				n = pageDOM.get(s.id + '_b');

				// Create iframe
				n = pageDOM.add(n, 'iframe', {
					id : s.id + "_f",
					src : 'javascript:""', // Workaround for HTTPs warning in IE6/7
					frameBorder : '0',
					'class' : 'punymce',
					style : 'width:' + w + 'px;height:' + h + 'px'
				});

				t.resizeTo(w, h);

				// WebKit needs to be loaded this way to force it in to quirksmode to get <b> instead of <span>
				if (isWebKit) {
					Event.add(n, 'load', setup, t);
					n.src = punymce.baseURL + 'blank.htm';
				} else
					setup();

				// Add resize event
				if (s.resize) {
					Event.add(s.id + '_r', 'mousedown', function(e) {
						return startResize(e, s.id);
					}, this);
				}

				ul = f = e = n = null; // Prevent IE memory leak
			},

			getSize : function() {
				var e = pageDOM.get(s.id + '_f');

				return {
					w : e.clientWidth,
					h : e.clientHeight
				};
			},

			resizeTo : function(w, h) {
				var st = pageDOM.get(s.id + '_f').style;

				// Fix size
				w = Math.max(s.min_width, w);
				h = Math.max(s.min_height, h);
				w = Math.min(s.max_width, w);
				h = Math.min(s.max_height, h);

				// Store away size
				t.width = w;
				t.height = h;

				// Resize container
				st.width = w + 'px';
				st.height = h + 'px';
			},

			resizeBy : function(w, h) {
				var r = t.getSize();

				t.resizeTo(r.w + w, r.h + h);
			},

			show : function() {
				pageDOM.get(s.id + '_w').style.display = 'block';
				pageDOM.get(s.id).style.display = 'none';
				t.load();
			},

			hide : function() {
				// Fixed bug where IE has a blinking cursor left from the editor
				if (isIE)
					t.execCommand('SelectAll');

				pageDOM.get(s.id + '_w').style.display = 'none';
				pageDOM.get(s.id).style.display = t.orgDisplay;
				t.save();
			},

			load : function() {
				var e = pageDOM.get(s.id);

				t.setContent(is(e.value) ? e.value : e.innerHTML, {load : true});
			},

			save : function() {
				var e = pageDOM.get(s.id), h = t.getContent({save : true});

				if (/TEXTAREA|INPUT/.test(e.nodeName))
					e.value = h;
				else
					e.innerHTML = h;
			},

			setUseCSS : function(s) {
				var d = t.getDoc(), e;

				try {
					// Try new Gecko method
					d.execCommand("styleWithCSS", 0, false);
				} catch (e) {
					try {
						// Use old
						d.execCommand("useCSS", 0, true);
					} catch (e) {
						// Ignore
					}
				}
			},

			execCommand : function(c, u, v, e) {
				var cl = t.commands, s;

				t.getWin().focus();
				t.setUseCSS(0);

				if (cl[c])
					s = cl[c].call(t, u, v, e);
				else
					s = t.getDoc().execCommand(c, u, v);

				if (s !== false)
					t.nodeChanged();
			},

			getContent : function(o) {
				var h;

				o = o || {};
				o.format = o.format || 'html';
				t.onBeforeGetContent.dispatch(this, o);
				h = t.serializer.serialize(t.getBody(), o);
				h = h.replace(/^\s*|\s*$/g, '');
				o.content = h;
				t.onGetContent.dispatch(this, o);

				return o.content;
			},

			setContent : function(h, o) {
				o = o || {};
				o.content = h;
				t.onSetContent.dispatch(this, o);
				h = o.content;
				h = pageDOM.keep(h);

				t.getBody().innerHTML = h;

				if (o.format != "raw") {
					t.setContent(h = t.getContent(o), {format : 'raw'});
				} else
					t.getBody().innerHTML = h;

				return h;
			},

			getIfr : function() {
				return pageDOM.get(s.id + "_f");
			},

			getWin : function() {
				return t.getIfr().contentWindow;
			},

			getDoc : function() {
				return t.getWin().document;
			},

			getBody : function() {
				return t.getDoc().body;
			},

			nodeChanged : function() {
				t.setUseCSS(0);
				t.onNodeChange.dispatch(t, t.selection.getNode());
			}
		});

		// Call init when page loads
		Event.add(window, 'init', t.init, this);
	};

	punymce.Selection = Selection = function(ed) {
		var t = this;

		// Public methods
		extend(t, {
			getContent : function(o) {
				var h, r = t.getRng(), e = document.createElement("body");

				o = o || {};

				if (t.isCollapsed())
					return '';

				if (r.cloneContents)
					e.appendChild(r.cloneContents());
				else if (is(r.item) || is(r.htmlText))
					e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
				else
					e.innerHTML = r.toString();

				if (o.format != "raw") {
					o.content = h;
					ed.serializer.serialize(e, o);
					o.content = o.content.replace(/^\s*|\s*$/g, '');
					ed.onGetContent.dispatch(h, o);
					h = o.content;
				} else
					h = e.innerHTML;

				return h;
			},

			getText : function() {
				var r = t.getRng(), s = t.getSel();

				if (isOldWebKit)
					return s;

				return t.isCollapsed() ? '' : r.text || s.toString();
			},

			setContent : function(h, o) {
				var r = t.getRng(), b, c, r, s;

				o = o || {format : 'raw'};
				h = pageDOM.keep(h);

				if (o.format != "raw") {
					o.content = h;
					h = ed.onSetContent.dispatch(this, o);
					h = o.content;
					b = ed.dom.create('body');
					b.innerHTML = h;
				}

				if (r.insertNode) {
					r.deleteContents();
					r.insertNode(r.createContextualFragment(h + '<span id="__caret">_</span>'));

					// Move to caret marker
					c = ed.dom.get('__caret');

					// Make sure we wrap it compleatly, Opera fails with a simple select call
					r = ed.getDoc().createRange();
					r.setStartBefore(c);
					r.setEndBefore(c);
					s = t.getSel();
					s.removeAllRanges();
					s.addRange(r);

					// Remove the caret
					c.parentNode.removeChild(c);
				} else {
					// Handle text and control range
					if (r.pasteHTML)
						r.pasteHTML(h);
					else
						r.item(0).outerHTML = h;
				}
			},

			select : function(n, c) {
				var r = t.getRng(), s = t.getSel();

				if (r.moveToElementText) {
					try {
						r.moveToElementText(n);
						r.select();
					} catch (ex) {
						// Throws illigal agrument in IE some times
					}
				} else if (s.addRange) {
					c ? r.selectNodeContents(n) : r.selectNode(n);
					s.removeAllRanges();
					s.addRange(r);
				} else
					s.setBaseAndExtent(n, 0, n, 1);

				return n;
			},

			isCollapsed : function() {
				var r = t.getRng();

				if (r.item)
					return false;

				return r.boundingWidth == 0 || t.getSel().isCollapsed;
			},

			collapse : function(b) {
				var r = t.getRng(), s = t.getSel();

				if (r.select) {
					r.collapse(b);
					r.select();
				} else {
					if (b)
						s.collapseToStart();
					else
						s.collapseToEnd();
				}
			},

			getSel : function() {
				var w = ed.getWin();

				return w.getSelection ? w.getSelection() : ed.getDoc().selection;
			},

			getRng : function() {
				var s = t.getSel(), d = ed.getDoc(), r, rb, ra, di;

				if (!s)
					return null;

				try {
					return s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : null);
				} catch (e) {
					// IE bug when used in frameset
					return d.body.createTextRange();
				}
			},

			setRng : function(r) {
				var s = t.getSel();

				s.removeAllRanges();
				s.addRange(r);
			},

			setNode : function(n) {
				t.setContent(ed.dom.create('div', null, n).innerHTML);
			},

			getNode : function() {
				var r = t.getRng(), s = t.getSel(), e;

				if (!isIE) {
					if (r) {
						e = r.commonAncestorContainer;

						// Handle selection a image or other control like element such as anchors
						if (!r.collapsed) {
							if (r.startContainer == r.endContainer) {
								if (r.startOffset - r.endOffset < 2) {
									if (r.startContainer.hasChildNodes())
										e = r.startContainer.childNodes[r.startOffset];
								}
							}
						}
					}

					return pageDOM.getParent(e, function(n) {
						return n.nodeType == 1;
					});
				}

				return r.item ? r.item(0) : r.parentElement();
			}
		});
	};

	punymce.Serializer = Serializer = function(ed) {
		var xml, key = 0, s;

		// Get XML document
		function getXML() {
			var i = document.implementation;

			if (!i || !i.createDocument) {
				// Try IE objects
				try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
				try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
			} else
				return i.createDocument('', '', null);
		};

		xml = getXML();

		// Default settings
		this.settings = s = extend({
			valid_nodes : 0,
			invalid_nodes : /(BODY)/,
			valid_attrs : 0,
			node_filter : 0,
			root_node : 0,
			pi : 0,
			invalid_attrs : /(^mce_|^_moz_|^contenteditable$)/,
			closed : /(BR|HR|INPUT|META|IMG)/
		}, ed.settings.serializer);

		// Returns only attribites that have values not all attributes in IE
		function getIEAtts(n) {
			var o = [];

			n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
				o.push({specified : 1, nodeName : b});
			});

			return o;
		};

		// Private methods
		function serializeNode(n, xn) {
			var hc, el, cn, i, l, a, at, no, v;

			if ((!s.valid_nodes || s.valid_nodes.test(n.nodeName)) && (!s.invalid_nodes || !s.invalid_nodes.test(n.nodeName)) && (!s.node_filter || s.node_filter(n, xn))) {
				switch (n.nodeType) {
					case 1: // Element
						// Fix IE content duplication (DOM can have multiple copies of the same node)
						if (isIE) {
							if (n.mce_serialized == key)
								return;

							n.mce_serialized = key;
						}

						hc = n.hasChildNodes();
						el = xml.createElement(n.nodeName.toLowerCase());

						// Add attributes
						at = isIE ? getIEAtts(n) : n.attributes;
						for (i=at.length-1; i>-1; i--) {
							no = at[i];

							if (no.specified) {
								a = no.nodeName.toLowerCase();

								if (s.invalid_attrs && s.invalid_attrs.test(a))
									continue;

								if (s.valid_attrs && !s.valid_attrs.test(a))
									continue;

								v = pageDOM.getAttr(n, a);

								if (v !== '')
									el.setAttribute(a, v);
							}
						}

						if (!hc && !s.closed.test(n.nodeName))
							el.appendChild(xml.createTextNode(""));

						xn = xn.appendChild(el);
						break;

					case 3: // Text
						return xn.appendChild(xml.createTextNode(n.nodeValue));

					case 8: // Comment
						return xn.appendChild(xml.createComment(n.nodeValue));
				}
			} else if (n.nodeType == 1)
				hc = n.hasChildNodes();

			if (hc) {
				cn = n.firstChild;

				while (cn) {
					serializeNode(cn, xn);
					cn = cn.nextSibling;
				}
			}
		};

		// Public methods
		extend(this, {
			serialize : function(n, o) {
				var h;

				key = '' + (parseInt(key) + 1);

				if (xml.firstChild)
					xml.removeChild(xml.firstChild);

				n = o.node = n.cloneNode(1);

				// Call preprocess
				ed.onPreProcess.dispatch(this, o);

				// Serialize HTML DOM into XML DOM
				serializeNode(n, xml.appendChild(xml.createElement("html")));
				h = xml.xml || new XMLSerializer().serializeToString(xml);

				// Remove PI
				if (!s.pi)
					h = h.replace(/<\?[^?]+\?>/g, '');

				// Remove root element
				if (!s.root_node)
					h = h.replace(/<html>|<\/html>/g, '');

				// Call post process
				o.content = h;
				ed.onPostProcess.dispatch(this, o);

				return o.content;
			}
		});
	};

	// Wait for DOM loaded
	Event._wait();

	// Expose punymce
	window.punymce = punymce;
})({});

(function(punymce) {
	punymce.plugins.TextColor = function(ed) {
		var 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';
		var DOM = punymce.DOM, Event = punymce.Event, each = punymce.each, extend = punymce.extend, s;

		if (!ed.settings.textcolor || ed.settings.textcolor.skip_css)
			DOM.loadCSS(punymce.baseURL + '/plugins/textcolor/css/editor.css');

		s = extend({
			colors : colors
		}, ed.settings.textcolor);

		extend(ed.commands, {
			mceColor : function(u, v, e) {
				var n, t = this, id = ed.settings.id, p = DOM.getPos(e.target), co, cb;

				if (ed.hideMenu)
					return ed.hideMenu();

				function hide(e) {
					ed.hideMenu = null;
					Event.remove(document, 'click', hide);
					Event.remove(ed.getDoc(), 'click', hide);
					DOM.get(id + '_mcolor').style.display = 'none';
					return 1;
				};

				n = DOM.get(id + '_mcolor');
				if (!n) {
					n = DOM.get(id + '_t');
					n = DOM.add(document.body, 'div', {id : id + '_mcolor', 'class' : 'punymce_color punymce'});
					n = DOM.add(n, 'table', {'class' : 'punymce'});
					n = DOM.add(n, 'tbody');
					co = 8;
					each(s.colors.split(','), function(c) {
						if (co == 8) {
							r = DOM.add(n, 'tr');
							co = 0;
						}

						co++;

						Event.add(DOM.add(DOM.add(r, 'td'), 'a', {href : '#', style : 'background:#' + c}), 'mousedown', function(e) {
							hide.call(t);

							ed.execCommand('forecolor', 0, '#' + c);

							return Event.cancel(e);
						});
					});
				}

				Event.add(document, 'click', hide, t);
				Event.add(ed.getDoc(), 'click', hide, t);
				ed.hideMenu = hide;

				s = DOM.get(id + '_mcolor').style;
				s.left = p.x + 'px';
				s.top = (p.y + e.target.clientHeight + 2) + 'px';
				s.display = 'block';
			}
		});

		extend(ed.tools, {
			textcolor : {cmd : 'mceColor', title : punymce.I18n.textcolor}
		});
	};

	// English i18n strings
	punymce.extend(punymce.I18n, {
		textcolor : 'Text color'
	});
})(punymce);


punymce.plugins.Paste = function(ed) {
	ed.onPaste = new punymce.Dispatcher(ed);

	ed.onInit.add(function() {
		// Add paste event
		punymce.Event.add(ed.getBody(), 'paste', function(e) {
			ed.onPaste.dispatch(e, ed);
		}, this);

		// Add paste event
		punymce.Event.add(ed.getDoc(), 'keydown', function(e) {
			// Fake onpaste event in Opera and Gecko (ctrl+v or shift+insert)
			if ((e.ctrlKey && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45)) {
				setTimeout(function() {
					ed.onPaste.dispatch(e, ed);
				}, 10);
			}
		}, this);
	});
};


(function(punymce) {
	punymce.plugins.Image = function(ed) {
		var I18n = punymce.I18n;

		if (!ed.settings.image || ed.settings.image.skip_css)
			punymce.DOM.loadCSS(punymce.baseURL + '/plugins/image/css/editor.css');

		// Add image command
		punymce.extend(ed.commands, {
			mceInsertImage : function(u, v, e) {
				var tx, f = ed.selection.getNode();

				tx = prompt(I18n.entersrc, f.nodeName == "IMG" ? ed.dom.getAttr(f, "src") : "");
				if (tx)
					ed.selection.setNode(ed.dom.create('img', {src : tx}));
			}
		});

		// Add tools
		punymce.extend(ed.tools, {
			image : {cmd : 'mceInsertImage', title : I18n.insertimage}
		});
	};

	// English i18n strings
	punymce.extend(punymce.I18n, {
		insertimage : 'Insert image',
		entersrc : 'Enter the URL of the image'
	});
})(punymce);


punymce.plugins.ExternalLink = function(ed) {
    var I18n = punymce.I18n;

    if (!ed.settings.link || ed.settings.link.skip_css)
        punymce.DOM.loadCSS(punymce.baseURL + '/plugins/link/css/editor.css');

    ed.annotationLink = ed.annotationLink || {};

    // Add image command
    punymce.extend(ed.commands, {
        mceInsertLink: function(u, v, e) {
            var url, selectedNode = ed.selection.getNode();

            if (!this.annotationLink.$dialog) {
                this.annotationLink.$dialog = $('<div />').appendTo('body');

                this.annotationLink.$annotationLinkRadio = $('<input />')
                    .attr('type', 'radio')
                    .attr('name', 'linkType')
                    .attr('checked', true)
                    .val('annotationLink');
                $('<label>Reference database</label>')
                    .prepend(this.annotationLink.$annotationLinkRadio)
                    .appendTo(this.annotationLink.$dialog);
                this.annotationLink.$annotationLink = $('<input />');
                $('<p />')
                    .append(this.annotationLink.$annotationLink)
                    .appendTo(this.annotationLink.$dialog);
                this.annotationLink.$annotationLink.externalLink({type: 'annotation'});

                this.annotationLink.$webLinkRadio = $('<input />')
                    .attr('type', 'radio')
                    .attr('name', 'linkType')
                    .val('webLink');
                $('<label>Website URL</label>')
                    .prepend(this.annotationLink.$webLinkRadio)
                    .appendTo(this.annotationLink.$dialog);
                this.annotationLink.$webLink = $('<input />');
                $('<p />')
                    .append(this.annotationLink.$webLink)
                    .appendTo(this.annotationLink.$dialog);

                this.annotationLink.$dialog.dialog({
                    title: 'Add link',
                    modal: true,
                    height: 200,
                    width: 400,
                    buttons: [{
                        title: 'Cancel',
                        priority: 'secondary',
                        click: function() {
                            $(this).dialog('close');
                        }
                    }, {
                        title: 'Add link',
                        priority: 'primary',
                        hotkey: $.ui.keyCode.ENTER,
                        click: function() {

                            var selectedNode = ed.selection.getNode();

                            if (ed.annotationLink.$annotationLinkRadio.attr('checked')) {
                                if (selectedNode.nodeName === 'A') {
                                    selectedNode.parentNode.insertBefore(
                                        document.createTextNode(
                                            ' ' + ed.annotationLink.$annotationLink.val() + ' '
                                        ),
                                        selectedNode
                                    );
                                    selectedNode.parentNode.removeChild(selectedNode);
                                } else {
                                    ed.selection.setContent(' ' + ed.annotationLink.$annotationLink.val() + ' ');
                                }

                            } else if (ed.annotationLink.$webLinkRadio.attr('checked')) {
                                ed.execCommand('CreateLink', 0, ed.annotationLink.$webLink.val());
                            }

                            $(this).dialog('close');
                        }
                    }]
                });
            }

            url = selectedNode.nodeName == 'A' ? ed.dom.getAttr(selectedNode, 'href') : ''
            this.annotationLink.$webLink.val(url);
            this.annotationLink.$dialog.dialog('open');
        }
    });

    // Add tools
    punymce.extend(ed.tools, {
        link : {cmd : 'mceInsertLink', title : I18n.link},
        unlink : {cmd : 'Unlink', title : I18n.unlink}
    });
};

// English i18n strings
punymce.extend(punymce.I18n, {
    link: 'Insert link',
    unlink: 'Unlink'
});

/* vim: set sts=4 sw=4: */


(function(punymce) {
	punymce.plugins.Emoticons = function(ed) {
		var Event = punymce.Event, each, extend, isIE, isGecko, st, emoReg, DOM, h;

		each = punymce.each;
		extend = punymce.extend;
		isIE = punymce.isIE;
		isGecko = punymce.isGecko;
		DOM = punymce.DOM;

		// Default settings
		this.settings = st = extend({
			emoticons : {
				happy : [':)', '=)'],
				unhappy : [':|', '=|'],
				sad : [':(','=('],
				grin : [':D', '=D'],
				surprised : [':o',':O','=o', '=O'],
				wink : [';)'],
				halfhappy : [':/', '=/'],
				tounge : [':P', ':p', '=P', '=p'],
				lol : [],
				mad : [],
				rolleyes : [],
				cool : []
			},
			row_length : 4,
			trans_img : punymce.baseURL + 'plugins/emoticons/img/trans.gif',
			skip_css : 0,
			auto_convert : 1
		}, ed.settings.emoticons);

		if (!st.skip_css)
			DOM.loadCSS(punymce.baseURL + '/plugins/emoticons/css/editor.css');

		// Build regexp from emoticons
		emoReg = '';
		each(st.emoticons, function(v) {
			each(v, function(v) {
				if (emoReg.length != 0)
					emoReg += '|';

				emoReg += v.replace(/([^a-zA-Z0-9])/g, '\\$1');
			});
		});

		emoReg = new RegExp(emoReg, 'g');

		// Add commands
		extend(ed.commands, {
			mceEmoticons : function(u, v, e) {
				var n, t = this, id = ed.settings.id, p = DOM.getPos(e.target), co, cb;

				if (ed.hideMenu)
					return ed.hideMenu();

				function hide(e) {
					ed.hideMenu = null;
					Event.remove(document, 'click', hide);
					Event.remove(ed.getDoc(), 'click', hide);
					DOM.get(id + '_memoticons').style.display = 'none';
					return 1;
				};

				n = DOM.get(id + '_memoticons');
				if (!n) {
					n = DOM.get(id + '_t');
					n = DOM.add(document.body, 'div', {id : id + '_memoticons', 'class' : 'punymce_emoticons punymce'});
					n = DOM.add(n, 'table', {'class' : 'punymce'});
					n = DOM.add(n, 'tbody');
					co = st.row_length;
					each(st.emoticons, function(c, k) {
						if (co == st.row_length) {
							r = DOM.add(n, 'tr');
							co = 0;
						}

						co++;

						Event.add(DOM.add(DOM.add(r, 'td'), 'a', {href : '#', 'class' : 'emoticon ' + k}), 'mousedown', function(e) {
							hide.call(t);

							ed.selection.setNode(ed.dom.create('img', { title : c[0] || k, src : st.trans_img, 'class' : 'emoticon ' + k }));

							return Event.cancel(e);
						});
					});
				}

				Event.add(document, 'click', hide, t);
				Event.add(ed.getDoc(), 'click', hide, t);
				ed.hideMenu = hide;

				s = DOM.get(id + '_memoticons').style;
				s.left = p.x + 'px';
				s.top = (p.y + e.target.clientHeight + 2) + 'px';
				s.display = 'block';
			}
		});

		function find(e) {
			var c;

			each(st.emoticons, function(v, k) {
				each(v, function(v) {
					if (v == e) {
						c = k;
						return false;
					}
				});

				return !c;
			});

			return c;
		};

		ed.onPreProcess.add(function(se, o) {
			var nl = o.node.getElementsByTagName('img'), a = [];

			each(nl, function(n) {
				a.push(n);
			});

			each(a, function(n) {
				var c = ed.dom.getAttr(n, 'class');

				if (c && c.indexOf('emoticon') != -1) {
					n.parentNode.replaceChild(ed.getDoc().createTextNode(n.getAttribute('title')), n);
				}
			});
		});

		ed.onSetContent.add(function(ed, o) {
			var ar = [];

			// Store away all tags and URL parts (://)
			h = o.content.replace(/(<\/?[^>]+>|:\/\/)/g, function(a) {
				return a.replace(emoReg, function(a) {
					var c = find(a);

					if (c) {
						ar.push(a);
						return '¤' + ar.length + '¤';
					}

					return a;
				});
			});

			// Replace emoticons in remaining text nodes
			h = h.replace(emoReg, function(a) {
				return '<img src="' + st.trans_img + '" title="' + a + '" class="emoticon ' + find(a) + '" />';
			});

			// Restore attribs
			h = h.replace(/¤([^¤]+)¤/g, function(a, b) {
				return ar[parseInt(b) - 1];
			});

			o.content = h;
		});

		ed.onInit.add(function() {
			var DOM = ed.dom;

			if (!st.skip_css)
				DOM.loadCSS(punymce.baseURL + '/plugins/emoticons/css/content.css');

			// Disable selection on emoticons in IE
			Event.add(ed.getDoc(), 'controlselect', function(e) {
				if (DOM.getAttr(e.target, 'class').indexOf('emoticon') != -1)
					return Event.cancel(e);
			});

			if (isGecko) {
				// Disable selection of emoticons in Gecko
				Event.add(ed.getDoc(), 'mousedown', function(e) {
					if (DOM.getAttr(e.target, 'class').indexOf('emoticon') != -1) {
						ed.getDoc().execCommand('enableObjectResizing', false, false);
						return Event.cancel(e);
					} else
						ed.getDoc().execCommand('enableObjectResizing', false, true);
				});

				// Fix for bug: https://bugzilla.mozilla.org/show_bug.cgi?id=392569
				Event.add(ed.getDoc(), 'keydown', function(e) {
					var s = ed.selection, se = s.getSel(), d = ed.getDoc(), r = s.getRng(), sc, so, n, l;

					sc = r.startContainer;
					so = r.startOffset;

					// Element instead of text just pass it though
					if (sc.nodeType == 1)
						return;

					if (sc) {
						// Right key
						if (e.keyCode == 39 && so == sc.nodeValue.length) {
							n = sc.nextSibling;
							if (n && n.nodeName == 'IMG') {
								n = n.nextSibling;

								r = d.createRange();
								r.setStart(n, 0);
								r.setEnd(n, 0);

								se.removeAllRanges();
								se.addRange(r);

								return Event.cancel(e);
							}
						}

						// Left key
						if (e.keyCode == 37 && so == 0) {
							n = sc.previousSibling;
							if (n && n.nodeName == 'IMG') {
								n = n.previousSibling;
								l = n.nodeValue.length;

								r = d.createRange();
								r.setStart(n, l);
								r.setEnd(n, l);

								se.removeAllRanges();
								se.addRange(r);

								return Event.cancel(e);
							}
						}
					}
				});
			}

			Event.add(ed.getDoc(), 'keypress', function(e) {
				var em, cls, lc, lc2, d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng();

				function getStr(pos) {
					var sn, so, eo;

					if (!isIE) {
						sn = r.startContainer;
						so = r.startOffset;
						eo = r.endOffset;

						if (so > 0 && sn.nodeType == 3)
							return sn.nodeValue.substring(Math.max(0, so + pos), eo);
					} else {
						r = r.duplicate();
						r.moveStart('character', pos);

						return r.text;
					}
				};

				// Check if Safari 2.x
				if (punymce.isOldWebKit || !st.auto_convert)
					return true;

				// If http/ftp
				if (/(ttp|ftp):/i.test(getStr(-4)))
					return;

				lc = getStr(-1);
				em = lc + String.fromCharCode(e.charCode || e.keyCode);
				emoReg.lastIndex = 0;

				if (!lc || !emoReg.test(em) || lc2 == '/')
					return;

				if (cls = find(em)) {
					if (!isIE) {
						r.setStart(r.startContainer, r.startOffset - 1);
						s.removeAllRanges();
						s.addRange(r);
					} else {
						r = ed.selection.getRng();
						r.moveStart('character', -1);
						r.select();
					}

					ed.selection.setNode(DOM.create('img', { id : 'emoticon', title : em, src : st.trans_img, 'class' : 'emoticon ' + cls }));

					em = DOM.get('emoticon');
					DOM.setAttr(em, 'id', '');
					ed.selection.select(em);
					ed.selection.collapse(0);

					return Event.cancel(e);
				}
			});
		});

		extend(ed.tools, {
			emoticons : {cmd : 'mceEmoticons', title : punymce.I18n.emoticons}
		});
	};

	// English i18n strings
	punymce.extend(punymce.I18n, {
		emoticons : 'Insert emoticon'
	});
})(punymce);


(function(punymce) {
	punymce.plugins.EditSource = function(ed) {
		var DOM = punymce.DOM, extend = punymce.extend, each = punymce.each, isWebKit = punymce.isWebKit;
		var sourceView = 0;

		if (!ed.settings.editsource || ed.settings.editsource.skip_css)
			DOM.loadCSS(punymce.baseURL + '/plugins/editsource/css/editor.css');

		ed.onBeforeGetContent.add(function(ed, o) {
			if (o.save && sourceView)
				ed.setContent(DOM.get(ed.settings.id + '_editsourcearea').value, {load : true});
		});

		// Add commands
		extend(ed.commands, {
			mceEditSource : function(u, v, e) {
				var ta, ifr = ed.getIfr(), id = ed.settings.id, w = ed.width, h = ed.height, f, but = DOM.get(id + '_editsource');

				// Enable source view
				if (!sourceView) {
					DOM.addClass(but, 'active');

					// Disable all buttons
					each(DOM.select('li', id + '_c'), function(n) {
						if (n != but)
							DOM.addClass(n, 'disabled');
					});

					// Hide iframe and view textarea
					ta = DOM.add(ifr.parentNode, 'textarea', {id : id + '_editsourcearea', 'class' : 'editsource', style : 'width:' + w + 'px;height:' + h + 'px;'});
					ta.value = ed.getContent({save : true});
					ta.focus();

					// A spacer element was need since IE 6/7 produces bugs if the container is sized
					// Iframe needs to be hidden IE and FF so that the designMode caret doesn't get shown
					// And on Safari 2.x hiding an iframe will break the iframe
					if (!isWebKit) {
						DOM.add(ifr.parentNode, 'div', {id : id + '_edspacer', 'class' : 'spacer', style : 'width:' + w + 'px;height:' + h + 'px;'});
						ifr.style.display = 'none';
					}

					sourceView = 1;
					return false;
				}

				// Disable source view
				sourceView = 0;
				DOM.removeClass(but, 'active');

				// Show iframe and remove spacer
				if (!isWebKit) {
					ifr.style.display = 'block';
					ta = DOM.get(id + '_edspacer');
					ta.parentNode.removeChild(ta);
				}

				// Remove textarea and set contents
				ta = DOM.get(id + '_editsourcearea');
				ed.setContent(ta.value, {load : true});
				ta.parentNode.removeChild(ta);

				// Enable all buttons
				each(DOM.select('li', id + '_c'), function(n) {
					DOM.removeClass(n, 'disabled');
				});

				return false;
			}
		});

		// Add tools
		extend(ed.tools, {
			editsource : {cmd : 'mceEditSource', title : punymce.I18n.editsource}
		});
	};

	// English i18n strings
	punymce.extend(punymce.I18n, {
		editsource : 'Edit HTML source'
	});
})(punymce);


(function(punymce) {
	punymce.plugins.ForceBlocks = function(ed) {
		var Event = punymce.Event, DOM, t = this;
		var isIE, isGecko, isOpera, isWebKit;
		var each, extend, st;

		isIE = punymce.isIE;
		isGecko = punymce.isGecko;
		isOpera = punymce.isOpera;
		isWebKit = punymce.isWebKit;
		each = punymce.each;
		extend = punymce.extend;

		// Default settings
		this.settings = st = extend({
			element : 'P'
		}, ed.settings.forceblocks);

		ed.onPreInit.add(setup, t);

		if (!isIE) {
			ed.onSetContent.add(function(ed, o) {
				if (o.format == 'html')
					o.content = o.content.replace(/<p>[\s\u00a0]+<\/p>/g, '<p><br /></p>');
			});
		}

		ed.onPostProcess.add(function(ed, o) {
			o.content = o.content.replace(/<p><\/p>/g, '<p>\u00a0</p>');
		});

		function setup() {
			DOM = ed.dom;

			// Force root blocks when typing and when getting output
			Event.add(ed.getDoc(), 'keyup', forceRoots);
			ed.onSetContent.add(forceRoots);
			ed.onBeforeGetContent.add(forceRoots);

			if (!isIE) {
				ed.onPreProcess.add(function(ed, o) {
					each(o.node.getElementsByTagName('br'), function(n) {
						var p = n.parentNode;

						if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) {
							p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n);
						}
					});
				});

				Event.add(ed.getDoc(), 'keypress', function(e) {
					if (e.keyCode == 13 && !e.shiftKey) {
						if (!insertPara(e))
							return Event.cancel(e);
					}
				});

				if (isGecko) {
					Event.add(ed.getDoc(), 'keydown', function(e) {
						if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
							backspaceDelete(e, e.keyCode == 8);
					});
				}
			}
		};

		function getViewPort(w) {
			var d, b;

			d = w.document;
			b = d.compatMode == "CSS1Compat" ? d.documentElement : d.body;

			// Returns viewport size excluding scrollbars
			return {
				x : w.pageXOffset || b.scrollLeft,
				y : w.pageYOffset || b.scrollTop,
				w : w.innerWidth || b.clientWidth,
				h : w.innerHeight || b.clientHeight
			};
		};

		function find(n, t, s) {
			var w = ed.getDoc().createTreeWalker(n, 4, null, false), n, c = -1;

			while (n = w.nextNode()) {
				c++;

				// Index by node
				if (t == 0 && n == s)
					return c;

				// Node by index
				if (t == 1 && c == s)
					return n;
			}

			return -1;
		};

		function forceRoots() {
			var t = this, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
			var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;

			// Fix for bug #1863847
			//if (e && e.keyCode == 13)
			//	return true;

			// Wrap non blocks into blocks
			for (i = nl.length - 1; i >= 0; i--) {
				nx = nl[i];

				// Is text or non block element
				if (nx.nodeType == 3 || (!isBlock(nx) && nx.nodeType != 8)) {
					if (!bl) {
						// Create new block but ignore whitespace
						if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
							// Store selection
							if (si == -2 && r) {
								if (!isIE) {
									// If selection is element then mark it
									if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
										// Save the id of the selected element
										eid = n.getAttribute("id");
										n.setAttribute("id", "__mce");
									} else {
										// If element is inside body, might not be the case in contentEdiable mode
										if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
											so = r.startOffset;
											eo = r.endOffset;
											si = find(b, 0, r.startContainer);
											ei = find(b, 0, r.endContainer);
										}
									}
								} else {
									tr = d.body.createTextRange();
									tr.moveToElementText(b);
									tr.collapse(1);
									bp = tr.move('character', c) * -1;

									tr = r.duplicate();
									tr.collapse(1);
									sp = tr.move('character', c) * -1;

									tr = r.duplicate();
									tr.collapse(0);
									le = (tr.move('character', c) * -1) - sp;

									si = sp - bp;
									ei = le;
								}
							}

							bl = ed.dom.create(st.element);
							bl.appendChild(nx.cloneNode(1));
							nx.parentNode.replaceChild(bl, nx);
						}
					} else {
						if (bl.hasChildNodes())
							bl.insertBefore(nx, bl.firstChild);
						else
							bl.appendChild(nx);
					}
				} else
					bl = null; // Time to create new block
			}

			// Restore selection
			if (si != -2) {
				if (!isIE) {
					bl = b.getElementsByTagName(st.element)[0];
					r = d.createRange();

					// Select last location or generated block
					if (si != -1)
						r.setStart(find(b, 1, si), so);
					else
						r.setStart(bl, 0);

					// Select last location or generated block
					if (ei != -1)
						r.setEnd(find(b, 1, ei), eo);
					else
						r.setEnd(bl, 0);

					if (s) {
						s.removeAllRanges();
						s.addRange(r);
					}
				} else {
					try {
						r = s.createRange();
						r.moveToElementText(b);
						r.collapse(1);
						r.moveStart('character', si);
						r.moveEnd('character', ei);
						r.select();
					} catch (ex) {
						// Ignore
					}
				}
			} else if (!isIE && (n = ed.dom.get('__mce'))) {
				// Restore the id of the selected element
				if (eid)
					n.setAttribute('id', eid);
				else
					n.removeAttribute('id');

				// Move caret before selected element
				r = d.createRange();
				r.setStartBefore(n);
				r.setEndBefore(n);
				se.setRng(r);
			}
		};

		function isBlock(n) {
			return n.nodeType == 1 && /^(H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CODE|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(n.nodeName);
		};

		function getParentBlock(n) {
			return DOM.getParent(n, function(n) {
				return isBlock(n);
			});
		};

		function isEmpty(n) {
			n = n.innerHTML;
			n = n.replace(/<img|hr|table/g, 'd'); // Keep these
			n = n.replace(/<[^>]+>/g, ''); // Remove all tags

			return n.replace(/[ \t\r\n]+/g, '') == '';
		};

		function insertPara(e) {
			var d = ed.getDoc(), s = ed.selection.getSel(), r = ed.selection.getRng(), b = d.body;
			var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, y, ch;

			// Check if Safari 2.x
			if (punymce.isOldWebKit)
				return true;

			// Setup before range
			rb = d.createRange();
			rb.setStart(s.anchorNode, s.anchorOffset);
			rb.collapse(true);

			// Setup after range
			ra = d.createRange();
			ra.setStart(s.focusNode, s.focusOffset);
			ra.collapse(true);

			// Setup start/end points
			dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
			sn = dir ? s.anchorNode : s.focusNode;
			so = dir ? s.anchorOffset : s.focusOffset;
			en = dir ? s.focusNode : s.anchorNode;
			eo = dir ? s.focusOffset : s.anchorOffset;

			// Never use body as start or end node
			sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
			en = en.nodeName == "BODY" ? en.firstChild : en;

			// Get start and end blocks
			sb = getParentBlock(sn);
			eb = getParentBlock(en);
			bn = sb ? sb.nodeName : st.element; // Get block name to create

			// Return inside list use default browser behavior
			if (DOM.getParent(sb, function(n) { return /OL|UL/.test(n.nodeName); }))
				return true;

			// If caption or absolute layers then always generate new blocks within
			if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|static/gi.test(sb.style.position))) {
				bn = st.element;
				sb = null;
			}

			// If caption or absolute layers then always generate new blocks within
			if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|static/gi.test(eb.style.position))) {
				bn = st.element;
				eb = null;
			}

			// Use P instead
			if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(sb.style.cssFloat))) {
				bn = st.element;
				sb = eb = null;
			}

			// Setup new before and after blocks
			bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : d.createElement(bn);
			aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : d.createElement(bn);

			// Remove id from after clone
			aft.id = '';

			// Is header and cursor is at the end, then force paragraph under
			if (/^(H[1-6])$/.test(bn) && sn.nodeValue && so == sn.nodeValue.length)
				aft = d.createElement(st.element);

			// Find start chop node
			n = sc = sn;
			do {
				if (n == b || n.nodeType == 9 || isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
					break;

				sc = n;
			} while ((n = n.previousSibling ? n.previousSibling : n.parentNode));

			// Find end chop node
			n = ec = en;
			do {
				if (n == b || n.nodeType == 9 || isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
					break;

				ec = n;
			} while ((n = n.nextSibling ? n.nextSibling : n.parentNode));

			// Place first chop part into before block element
			if (sc.nodeName == bn)
				rb.setStart(sc, 0);
			else
				rb.setStartBefore(sc);

			rb.setEnd(sn, so);
			bef.appendChild(rb.cloneContents());

			// Place secnd chop part within new block element
			ra.setEndAfter(ec);
			ra.setStart(en, eo);
			aft.appendChild(ra.cloneContents());

			// Create range around everything
			r = d.createRange();
			if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
				r.setStartBefore(sc.parentNode);
			} else {
				if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
					r.setStartBefore(rb.startContainer);
				else
					r.setStart(rb.startContainer, rb.startOffset);
			}

			if (!ec.nextSibling && ec.parentNode.nodeName == bn)
				r.setEndAfter(ec.parentNode);
			else
				r.setEnd(ra.endContainer, ra.endOffset);

			// Delete and replace it with new block elements
			r.deleteContents();

			// Never wrap blocks in blocks
			if (bef.firstChild && bef.firstChild.nodeName == bn)
				bef.innerHTML = bef.firstChild.innerHTML;

			if (aft.firstChild && aft.firstChild.nodeName == bn)
				aft.innerHTML = aft.firstChild.innerHTML;

			// Padd empty blocks
			if (isEmpty(bef))
				bef.innerHTML = isOpera ? ' <br />' : '<br />'; // Extra space for Opera

			if (isEmpty(aft))
				aft.innerHTML = isOpera ? ' <br />' : '<br />'; // Extra space for Opera

			// Opera needs this one backwards
			if (isOpera) {
				r.insertNode(bef);
				r.insertNode(aft);
			} else {
				r.insertNode(aft);
				r.insertNode(bef);
			}

			// Normalize
			aft.normalize();
			bef.normalize();

			// Move cursor and scroll into view
			r = d.createRange();
			r.selectNodeContents(aft);
			r.collapse(1);
			s.removeAllRanges();
			s.addRange(r);
			//aft.scrollIntoView(0);

			// 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
			vp = getViewPort(ed.getWin());
			y = ed.dom.getPos(aft).y;
			ch = aft.clientHeight;

			// Is element within viewport
			if (y < vp.y || y + ch > vp.y + vp.h)
				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

			return false;
		};

		function backspaceDelete(e, bs) {
			var b = ed.getBody(), n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn;

			// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
			// This workaround removes the element by hand and moves the caret to the previous element
			if (sc && isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
				if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
					// Find previous block element
					n = sc;
					while ((n = n.previousSibling) && !isBlock(n)) ;

					if (n) {
						if (sc != b.firstChild) {
							// Find last text node
							w = ed.getDoc().createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
							while (tn = w.nextNode())
								n = tn;

							// Place caret at the end of last text node
							r = ed.getDoc().createRange();
							r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
							r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
							se.getSel().removeAllRanges();
							se.getSel().addRange(r);

							// Remove the target container
							sc.parentNode.removeChild(sc);
						}

						return Event.cancel(e);
					}
				}
			}

			// Gecko generates BR elements here and there, we don't like those so lets remove them
			function handler(e) {
				var pr;

				e = e.target;

				// A new BR was created in a block element, remove it
				if (e && e.parentNode && e.nodeName == 'BR' && (n = getParentBlock(e))) {
					pr = e.previousSibling;

					Event.remove(b, 'DOMNodeInserted', handler);

					// Is there whitespace at the end of the node before then we might need the pesky BR
					// to place the caret at a correct location see bug: #2013943
					if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
						return;

					// Only remove BR elements that got inserted in the middle of the text
					if (e.previousSibling || e.nextSibling)
						e.parentNode.removeChild(e);
				}
			};

			// Listen for new nodes
			Event._add(b, 'DOMNodeInserted', handler);

			// Remove listener
			window.setTimeout(function() {
				Event._remove(b, 'DOMNodeInserted', handler);
			}, 1);
		};
	};
})(punymce);


(function(punymce) {
	punymce.plugins.BBCode = function(ed) {
		// Convert XML into BBCode
		ed.onGetContent.add(function(ed, o) {
			if (o.format == 'bbcode' || o.save) {
				// example: <strong> to [b]
				punymce.each([
					[/<a href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]"],
					[/<font.*?color=\"([^\"]+)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]"],
					[/<img.*?src=\"([^\"]+)\".*?\/>/gi,"[img]$1[/img]"],
					[/<(br\s*\/)>/gi, "\n"],
					[/<(\/?)(strong|b)[^>]*>/gi, "[$1b]"],
					[/<(\/?)(em|i)[^>]*>/gi, "[$1i]"],
					[/<(\/?)u[^>]*>/gi, "[$1u]"],
					[/<(\/?)(code|pre)[^>]*>/gi, "[$1code]"],
					[/<(\/?)(span.*?class=\"quote\")[^>]*>(.*?)<\/span>/gi, "[$1quote]$3[/quote]"],
					[/<p>/gi, ""],
					[/<\/p>/gi, "\n"],
					[/&quot;/gi, "\""],
					[/&lt;/gi, "<"],
					[/&gt;/gi, ">"],
					[/&amp;/gi, "&"],
					[/<[^>]+>/gi, ""]
				], function (v) {
					o.content = o.content.replace(v[0], v[1]);
				});
			}
		});

		ed.onSetContent.add(function(ed, o) {
			if (o.format == 'bbcode' || o.load) {
				// example: [b] to <strong>
				punymce.each([
					[/\n/gi,"<br />"],
					[/\[(\/?)b\]/gi,"<$1strong>"],
					[/\[(\/?)i\]/gi,"<$1em>"],
					[/\[(\/?)u\]/gi,"<$1u>"],
					[/\[(\/?)code\]/gi,"<$1pre>"],
					[/\[url\](.*?)\[\/url\]/gi,"<a href=\"$1\">$1</a>"],
					[/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"<a href=\"$1\">$2</a>"],
					[/\[img\](.*?)\[\/img\]/gi,"<img src=\"$1\" />"],
					[/\[color=(.*?)\](.*?)\[\/color\]/gi,'<font color="$1">$2</font>'],
					[/\[quote.*?\](.*?)\[\/quote\]/gi,'<span class="quote">$1</span>']
				], function (v) {
					o.content = o.content.replace(v[0], v[1]);
				});
			}
		});
	};
})(punymce);


(function(punymce) {
	punymce.plugins.Entities = function(ed) {
		var s, a, re = '', l = {}, i, v, el;

		s = ed.settings;

		// Force named
		s.entities = 'named';
		el = s.entity_list;

		if (!el)
			el = '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ETH,209,Ntilde,210,Ograve,211,Oacute,212,Ocirc,213,Otilde,214,Ouml,215,times,216,Oslash,217,Ugrave,218,Uacute,219,Ucirc,220,Uuml,221,Yacute,222,THORN,223,szlig,224,agrave,225,aacute,226,acirc,227,atilde,228,auml,229,aring,230,aelig,231,ccedil,232,egrave,233,eacute,234,ecirc,235,euml,236,igrave,237,iacute,238,icirc,239,iuml,240,eth,241,ntilde,242,ograve,243,oacute,244,ocirc,245,otilde,246,ouml,247,divide,248,oslash,249,ugrave,250,uacute,251,ucirc,252,uuml,253,yacute,254,thorn,255,yuml,402,fnof,913,Alpha,914,Beta,915,Gamma,916,Delta,917,Epsilon,918,Zeta,919,Eta,920,Theta,921,Iota,922,Kappa,923,Lambda,924,Mu,925,Nu,926,Xi,927,Omicron,928,Pi,929,Rho,931,Sigma,932,Tau,933,Upsilon,934,Phi,935,Chi,936,Psi,937,Omega,945,alpha,946,beta,947,gamma,948,delta,949,epsilon,950,zeta,951,eta,952,theta,953,iota,954,kappa,955,lambda,956,mu,957,nu,958,xi,959,omicron,960,pi,961,rho,962,sigmaf,963,sigma,964,tau,965,upsilon,966,phi,967,chi,968,psi,969,omega,977,thetasym,978,upsih,982,piv,8226,bull,8230,hellip,8242,prime,8243,Prime,8254,oline,8260,frasl,8472,weierp,8465,image,8476,real,8482,trade,8501,alefsym,8592,larr,8593,uarr,8594,rarr,8595,darr,8596,harr,8629,crarr,8656,lArr,8657,uArr,8658,rArr,8659,dArr,8660,hArr,8704,forall,8706,part,8707,exist,8709,empty,8711,nabla,8712,isin,8713,notin,8715,ni,8719,prod,8721,sum,8722,minus,8727,lowast,8730,radic,8733,prop,8734,infin,8736,ang,8743,and,8744,or,8745,cap,8746,cup,8747,int,8756,there4,8764,sim,8773,cong,8776,asymp,8800,ne,8801,equiv,8804,le,8805,ge,8834,sub,8835,sup,8836,nsub,8838,sube,8839,supe,8853,oplus,8855,otimes,8869,perp,8901,sdot,8968,lceil,8969,rceil,8970,lfloor,8971,rfloor,9001,lang,9002,rang,9674,loz,9824,spades,9827,clubs,9829,hearts,9830,diams,338,OElig,339,oelig,352,Scaron,353,scaron,376,Yuml,710,circ,732,tilde,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm,8211,ndash,8212,mdash,8216,lsquo,8217,rsquo,8218,sbquo,8220,ldquo,8221,rdquo,8222,bdquo,8224,dagger,8225,Dagger,8240,permil,8249,lsaquo,8250,rsaquo,8364,euro';

		// Build regex and lookup array
		a = el.split(',');
		for (i=0; i<a.length; i+=2) {
			l[String.fromCharCode(a[i])] = a[i + 1];

			v = parseInt(a[i]).toString(16);
			re += '\\u' + '0000'.substring(v.length) + v;
		}
		re = new RegExp('[' + re + ']', 'g');

		ed.onGetContent.add(function(ed, o) {
			if (o.format == 'html') {
				o.content = o.content.replace(re, function(a) {
					var v;

					if (v = l[a])
						a = '&' + v + ';';

					return a;
				});
			}
		});
	};
})(punymce);


(function(punymce) {
	punymce.plugins.Protect = function(ed) {
		var pr = [], s, p;

		// Default settings
		p = ed.settings.protect || {};
		if (!p.list) {
			p.list = [
				/<(script|noscript|style)[\u0000-\uFFFF]*?<\/(script|noscript|style)>/g
			];
		}

		// Store away things to protect
		ed.onSetContent.add(function(ed, o) {
			punymce.each(p.list, function(re) {
				o.content = o.content.replace(re, function(a) {
					pr.push(a);
					return '<!-- pro:' + (pr.length-1) + ' -->';
				});
			});
		});

		// Restore protected things
		ed.onGetContent.add(function(ed, o) {
			o.content = o.content.replace(/<!-- pro:([0-9]+) -->/g, function(a, b) {
				return pr[parseInt(b)];
			});
		});
	};
})(punymce);


(function(punymce) {
	punymce.plugins.Safari2x = function(ed) {
		var each = punymce.each, Event = punymce.Event, html, setContent, collapse;

		// Is Safari 2.x
		if (!punymce.isOldWebKit)
			return;

		// Fake range on Safari 2.x
		ed.selection.getRng = function() {
			var t = this, s = t.getSel(), d = ed.getDoc(), r, rb, ra, di;

			// Fake range on Safari 2.x
			if (s.anchorNode) {
				r = d.createRange();

				try {
					// Setup before range
					rb = d.createRange();
					rb.setStart(s.anchorNode, s.anchorOffset);
					rb.collapse(1);

					// Setup after range
					ra = d.createRange();
					ra.setStart(s.focusNode, s.focusOffset);
					ra.collapse(1);

					// Setup start/end points by comparing locations
					di = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
					r.setStart(di ? s.anchorNode : s.focusNode, di ? s.anchorOffset : s.focusOffset);
					r.setEnd(di ? s.focusNode : s.anchorNode, di ? s.focusOffset : s.anchorOffset);
				} catch (ex) {
					// Sometimes fails, at least we tried to do it by the book. I hope Safari 2.x will go away soooon
				}
			}

			return r;
		};

		// Fix setContent so it works
		setContent = ed.selection.setContent;
		ed.selection.setContent = function(h, s) {
			var r = this.getRng();

			try {
				setContent.call(this, h, s);
			} catch (ex) {
				// Workaround for Safari 2.x
				b = ed.dom.create('body');
				b.innerHTML = h;

				each(b.childNodes, function(n) {
					r.insertNode(n.cloneNode(true));
				});
			}
		};

		collapse = ed.selection.collapse;
		ed.selection.collapse = function(b) {
			try {
				collapse.call(this, b);
			} catch (ex) {
				// Safari 2.x might fail to collapse
			}
		};

		// Resize is not supported too buggy
		ed.onInit.add(function() {
			punymce.DOM.get(ed.settings.id + '_r').style.display = 'none';
		});

		ed.onPreInit.add(function() {
			Event.add(ed.getDoc(), 'click', function(e) {
				if (e.target.nodeName == "A") {
					ed.selection.select(e.target);
					return Event.cancel(e);
				}
			});

			Event.add(ed.getDoc(), 'keydown', function(e) {
				var s = ed.selection, n, o, r, c;

				if (e.charCode > 32 || e.keyCode == 13) {
					n = ed.dom.getParent(s.getNode(), function(n) {return n.nodeName == 'LI';});

					if (n) {
						o = s.getRng().startOffset;

						// Create new LI on enter
						if (e.keyCode == 13) {
							// Empty list item
							if (!n.hasChildNodes()) {
								// At end of list then use default behavior
								if (!n.nextSibling || n.nextSibling.nodeName != 'LI') {
									r = ed.dom.getParent(s.getNode(), function(n) {return /(UL|OL)/.test(n.nodeName);});
									n.parentNode.removeChild(n);
									s.select(r.nextSibling);
									return;
								}

								// Cancel if in middle of list
								return Event.cancel(e);
							}

							// Insert temp character
							c = ed.getDoc().createTextNode('\u00a0');
							n.appendChild(c);
							//s.getSel().setBaseAndExtent(c, 0, c, 0);
							window.setTimeout(function() {
								var n = s.getNode();

								if (n.firstChild && n.firstChild.nodeValue.charAt(0) == '\u00a0') {
									n.removeChild(n.firstChild);
									s.select(n);
								}
							}, 1);
						} else {
							// Get char and check if it's alpha numeric
							c = String.fromCharCode(e.charCode);
							if (!/^\w$/.test(c))
								return;

							s.setContent(c);
							r = s.getRng();
							s = s.getSel();
							n = s.anchorNode;

							if (n.nodeName == 'LI') {
								s.setBaseAndExtent(n, 1, n, 1);
							} else {
								n = n.nextSibling;
								s.setBaseAndExtent(n, 1, n, 1);
							}

							return Event.cancel(e);
						}
					}
				}
			});
		});

		function wrap(n, a) {
			var d = ed.getDoc(), r, s;

			a = a || {};

			d.execCommand('FontName', false, '_tmp');

			each(ed.dom.select('span'), function(e) {
				if (e.style.fontFamily == '_tmp') {
					r = rename(e, n, a);

					if (!s)
						s = r;
				}
			});

			// Select
			r = r.firstChild;
			ed.selection.getSel().setBaseAndExtent(s, 0, r, r.nodeValue.length);
		};

		function insertList(n) {
			var s = ed.selection, li;

			s.setContent('<' + n + '><li id="_tmp"></li></' + n + '>');
			li = ed.dom.get('_tmp');
			li.id = '';

			s.select(li);
			s.collapse(1);
		};

		function getParentBlock(n) {
			return ed.dom.getParent(n, function(n) {
				return /^(H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CODE|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(n.nodeName);
			});
		};

		function rename(e, n, a) {
			var d = ed.getDoc(), r;

			a = a || {};
			r = d.createElement(n);

			// Copy attributes
			each(e.attributes, function(n) {
				if (n.specified && n.nodeValue)
					r.setAttribute(n.nodeName, n.nodeValue);
			});

			// Add attributes
			each(a, function(v, k) {
				r.setAttribute(k, v);
			});

			// Add children
			each(e.childNodes, function(n) {
				r.appendChild(n.cloneNode(true));
			});

			// Replace old node
			e.parentNode.replaceChild(r, e);

			return r;
		};

		// Fake commands
		punymce.extend(ed.commands, {
			IncreaseFontSize : function() {
				var d = ed.getDoc(), v = parseInt(d.queryCommandValue('FontSize'));

				d.execCommand('FontSize', false, (v + 1) + 'px');
			},

			DecreaseFontSize : function() {
				var d = ed.getDoc(), v = parseInt(d.queryCommandValue('FontSize'));

				if (v > 0)
					d.execCommand('FontSize', false, (v - 1) + 'px');
			},

			Strikethrough : function() {
				wrap('strike');
			},

			CreateLink : function(u, v) {
				wrap('a', {href : v, mce_href : v});
			},

			Unlink : function() {
				var s = ed.selection;

				s.setContent(s.getContent().replace(/(<a[^>]+>|<\/a>)/, ''));
			},

			RemoveFormat : function() {
				var s = ed.selection;

				s.setContent(s.getContent().replace(/(<(span|b|i|strong|em|strike) [^>]+>|<(span|b|i|strong|em|strike)>|<\/(span|b|i|strong|em|strike)>|)/g, ''));
			},

			FormatBlock : function(u, v) {
				var s = ed.selection, n;

				n = getParentBlock(ed.selection.getNode());

				if (n)
					r = rename(n, v.replace(/<|>/g, ''));

				s.select(r);
				s.collapse(1);
			},

			InsertUnorderedList : function() {
				insertList('ul');
			},

			InsertOrderedList : function() {
				insertList('ol');
			},

			Indent : function() {
				var n = getParentBlock(ed.selection.getNode()), v;

				if (n) {
					v = parseInt(n.style.paddingLeft) || 0;
					n.style.paddingLeft = (v + 10) + 'px';
				}
			},

			Outdent : function() {
				var n = getParentBlock(ed.selection.getNode()), v;

				if (n) {
					v = parseInt(n.style.paddingLeft) || 0;

					if (v >= 10)
						n.style.paddingLeft = (v - 10) + 'px';
				}
			}
		});
	};
})(punymce);
