Browse Source

Merge branch 'master' of https://github.com/SimpleMachines/SMF2.1

emanuele 12 years ago
parent
commit
501eceaa83

+ 2 - 2
Sources/ManageLanguages.php

@@ -1050,7 +1050,7 @@ function ModifyLanguage()
 			// Got a new entry?
 			if ($line[0] == '$' && !empty($multiline_cache))
 			{
-				preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s?=\s?(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
+				preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
 				if (!empty($matches[3]))
 				{
 					$entries[$matches[2]] = array(
@@ -1066,7 +1066,7 @@ function ModifyLanguage()
 		// Last entry to add?
 		if ($multiline_cache)
 		{
-			preg_match('~\$(helptxt|txt)\[\'(.+)\'\]\s?=\s?(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
+			preg_match('~\$(helptxt|txt|editortxt)\[\'(.+)\'\]\s?=\s?(.+);~', strtr($multiline_cache, array("\n" => '', "\t" => '')), $matches);
 			if (!empty($matches[3]))
 				$entries[$matches[2]] = array(
 					'type' => $matches[1],

+ 0 - 13
Sources/PersonalMessage.php

@@ -2032,19 +2032,6 @@ function MessagePost2()
 		}
 	}
 
-	// If we came from WYSIWYG then turn it back into BBC regardless.
-	if (!empty($_POST['message_mode']) && isset($_POST['message']))
-	{
-		require_once($sourcedir . '/Subs-Editor.php');
-		$_POST['message'] = html_to_bbc($_POST['message']);
-
-		// We need to unhtml it now as it gets done shortly.
-		$_POST['message'] = un_htmlspecialchars($_POST['message']);
-
-		// We need this in case of errors etc.
-		$_REQUEST['message'] = $_POST['message'];
-	}
-
 	// If your session timed out, show an error, but do allow to re-submit.
 	if (!isset($_REQUEST['xml']) && checkSession('post', '', false) != '')
 		$post_errors[] = 'session_timeout';

+ 5 - 24
Sources/Post.php

@@ -1046,7 +1046,7 @@ function Post($post_errors = array())
 			'post_button' => $context['submit_label'],
 		),
 		// add height and width for the editor
-		'height' => '175px',
+		'height' => '275px',
 		'width' => '100%',
 		// We do XML preview here.
 		'preview_type' => 2,
@@ -1180,19 +1180,9 @@ function Post2()
 		}
 	}
 
-	// If we came from WYSIWYG then turn it back into BBC regardless.
-	if (!empty($_REQUEST['message_mode']) && isset($_REQUEST['message']))
-	{
-		require_once($sourcedir . '/Subs-Editor.php');
-
-		$_REQUEST['message'] = html_to_bbc($_REQUEST['message']);
-
-		// We need to unhtml it now as it gets done shortly.
-		$_REQUEST['message'] = un_htmlspecialchars($_REQUEST['message']);
-
-		// We need this for everything else.
-		$_POST['message'] = $_REQUEST['message'];
-	}
+	// Previewing? Go back to start.
+	if (isset($_REQUEST['preview']))
+		return Post();
 
 	// Prevent double submission of this form.
 	checkSubmitOnce('check');
@@ -2744,16 +2734,7 @@ function QuoteFast()
 		if (!empty($modSettings['removeNestedQuotes']))
 			$row['body'] = preg_replace(array('~\n?\[quote.*?\].+?\[/quote\]\n?~is', '~^\n~', '~\[/quote\]~'), '', $row['body']);
 
-		// Make the body HTML if need be.
-		if (!empty($_REQUEST['mode']))
-		{
-			require_once($sourcedir . '/Subs-Editor.php');
-			$row['body'] = strtr($row['body'], array('<' => '#smlt#', '>' => '#smgt#', '&' => '#smamp#'));
-			$row['body'] = bbc_to_html($row['body']);
-			$lb = '<br />';
-		}
-		else
-			$lb = "\n";
+		$lb = "\n";
 
 		// Add a quote string on the front and end.
 		$context['quote']['xml'] = '[quote author=' . $row['poster_name'] . ' link=topic=' . $row['id_topic'] . '.msg' . (int) $_REQUEST['quote'] . '#msg' . (int) $_REQUEST['quote'] . ' date=' . $row['poster_time'] . ']' . $lb . $row['body'] . $lb . '[/quote]';

+ 1 - 1
Sources/QueryString.php

@@ -571,7 +571,7 @@ function JavaScriptEscape($string)
 		'\\' => '\\\\',
 		'\'' => '\\\'',
 		'</' => '<\' + \'/',
-		'script' => 'scri\'+\'pt',
+		'<script' => '<scri\'+\'pt',
 		'<body>' => '<bo\'+\'dy>',
 		'<a href' => '<a hr\'+\'ef',
 		$scripturl => '\' + smf_scripturl + \'',

+ 274 - 237
Sources/Subs-Editor.php

@@ -18,47 +18,21 @@ if (!defined('SMF'))
 	die('Hacking attempt...');
 
 /**
- * At the moment this is only used for returning WYSIWYG data.
- */
-function EditorMain()
-{
-	global $context, $smcFunc;
-
-	checkSession('get');
-
-	if (!isset($_REQUEST['view']) || !isset($_REQUEST['message']))
-		fatal_lang_error('no_access', false);
-
-	$context['sub_template'] = 'sendbody';
-
-	$context['view'] = (int) $_REQUEST['view'];
-
-	// Return the right thing for the mode.
-	if ($context['view'])
-	{
-		$_REQUEST['message'] = strtr($_REQUEST['message'], array('#smcol#' => ';', '#smlt#' => '&lt;', '#smgt#' => '&gt;', '#smamp#' => '&amp;'));
-		$context['message'] = bbc_to_html($_REQUEST['message']);
-	}
-	else
-	{
-		$_REQUEST['message'] = un_htmlspecialchars($_REQUEST['message']);
-		$_REQUEST['message'] = strtr($_REQUEST['message'], array('#smcol#' => ';', '#smlt#' => '&lt;', '#smgt#' => '&gt;', '#smamp#' => '&amp;'));
-
-		$context['message'] = html_to_bbc($_REQUEST['message']);
-	}
-
-	$context['message'] = $smcFunc['htmlspecialchars']($context['message']);
-}
-
-/**
+ * !!!Compatibility!!!
+ * Since we changed the editor we don't need it any more, but let's keep it if any mod wants to use it
  * Convert only the BBC that can be edited in HTML mode for the editor.
+ * 
  * @param string $text
+ * @param boolean $compat_mode if true will convert the text, otherwise not (default false)
  * @return string
  */
-function bbc_to_html($text)
+function bbc_to_html($text, $compat_mode = false)
 {
 	global $modSettings, $smcFunc;
 
+	if (!$compat_mode)
+		return $text;
+
 	// Turn line breaks back into br's.
 	$text = strtr($text, array("\r" => '', "\n" => '<br />'));
 
@@ -108,6 +82,10 @@ function bbc_to_html($text)
 }
 
 /**
+ * !!!Compatibility!!!
+ * This is no more needed, but to avoid break mods let's keep it
+ * Run it it shouldn't even hurt either, so let's not bother remove it
+ * 
  * The harder one - wysiwyg to BBC!
  * 
  * @param string $text
@@ -869,6 +847,9 @@ function html_to_bbc($text)
 }
 
 /**
+ * !!!Compatibility!!!
+ * This is no more needed, but to avoid break mods let's keep it
+ * 
  * Returns an array of attributes associated with a tag.
  * 
  * @param string $text
@@ -929,84 +910,7 @@ function fetchTagAttributes($text)
 }
 
 /**
- * Retrieves a list of message icons.
- * - Based on the settings, the array will either contain a list of default
- *   message icons or a list of custom message icons retrieved from the database.
- * - The board_id is needed for the custom message icons (which can be set for
- *   each board individually).
- * 
- * @param int $board_id
- * @return array
- */
-function getMessageIcons($board_id)
-{
-	global $modSettings, $context, $txt, $settings, $smcFunc;
-
-	if (empty($modSettings['messageIcons_enable']))
-	{
-		loadLanguage('Post');
-
-		$icons = array(
-			array('value' => 'xx', 'name' => $txt['standard']),
-			array('value' => 'thumbup', 'name' => $txt['thumbs_up']),
-			array('value' => 'thumbdown', 'name' => $txt['thumbs_down']),
-			array('value' => 'exclamation', 'name' => $txt['excamation_point']),
-			array('value' => 'question', 'name' => $txt['question_mark']),
-			array('value' => 'lamp', 'name' => $txt['lamp']),
-			array('value' => 'smiley', 'name' => $txt['icon_smiley']),
-			array('value' => 'angry', 'name' => $txt['icon_angry']),
-			array('value' => 'cheesy', 'name' => $txt['icon_cheesy']),
-			array('value' => 'grin', 'name' => $txt['icon_grin']),
-			array('value' => 'sad', 'name' => $txt['icon_sad']),
-			array('value' => 'wink', 'name' => $txt['icon_wink']),
-			array('value' => 'poll', 'name' => $txt['icon_poll']),
-		);
-
-		foreach ($icons as $k => $dummy)
-		{
-			$icons[$k]['url'] = $settings['images_url'] . '/post/' . $dummy['value'] . '.png';
-			$icons[$k]['is_last'] = false;
-		}
-	}
-	// Otherwise load the icons, and check we give the right image too...
-	else
-	{
-		if (($temp = cache_get_data('posting_icons-' . $board_id, 480)) == null)
-		{
-			$request = $smcFunc['db_query']('select_message_icons', '
-				SELECT title, filename
-				FROM {db_prefix}message_icons
-				WHERE id_board IN (0, {int:board_id})',
-				array(
-					'board_id' => $board_id,
-				)
-			);
-			$icon_data = array();
-			while ($row = $smcFunc['db_fetch_assoc']($request))
-				$icon_data[] = $row;
-			$smcFunc['db_free_result']($request);
-
-			$icons = array();
-			foreach ($icon_data as $icon)
-			{
-				$icons[$icon['filename']] = array(
-					'value' => $icon['filename'],
-					'name' => $icon['title'],
-					'url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $icon['filename'] . '.png') ? 'images_url' : 'default_images_url'] . '/post/' . $icon['filename'] . '.png',
-					'is_last' => false,
-				);
-			}
-
-			cache_put_data('posting_icons-' . $board_id, $icons, 480);
-		}
-		else
-			$icons = $temp;
-	}
-
-	return array_values($icons);
-}
-
-/**
+ * !!!Compatibility!!!
  * Attempt to clean up illegal BBC caused by browsers like Opera which don't obey the rules
  * @param string $text
  * @return string
@@ -1400,6 +1304,7 @@ function legalise_bbc($text)
 }
 
 /**
+ * !!!Compatibility!!!
  * A help function for legalise_bbc for sorting arrays based on length.
  * @param string $a
  * @param string $b
@@ -1410,6 +1315,124 @@ function sort_array_length($a, $b)
 	return strlen($a) < strlen($b) ? 1 : -1;
 }
 
+/**
+ * Creates the javascript code for localization of the editor (SCEditor)
+ */
+function loadLocale()
+{
+	global $context, $txt, $editortxt, $modSettings;
+
+	loadLanguage('Editor');
+
+	$context['template_layers'] = array();
+	// Lets make sure we aren't going to output anything nasty.
+	@ob_end_clean();
+	if (!empty($modSettings['enableCompressedOutput']))
+		@ob_start('ob_gzhandler');
+	else
+		@ob_start();
+
+	// If we don't have any locale better avoit broken js
+	if (empty($txt['lang_locale']))
+		die();
+
+	$file_data = '(function ($) {
+	\'use strict\';
+
+	$.sceditor.locale[' . javaScriptEscape($txt['lang_locale']) . '] = {';
+	foreach ($editortxt as $key => $val)
+		$file_data .= '
+		' . javaScriptEscape($key) . ': ' . javaScriptEscape($val) . ',';
+
+	$file_data .= '
+		dateFormat: "day.month.year"
+	}
+})(jQuery);';
+
+	// Make sure they know what type of file we are.
+	header('Content-Type: text/javascript');
+	echo $file_data;
+	obExit(false);
+}
+
+/**
+ * Retrieves a list of message icons.
+ * - Based on the settings, the array will either contain a list of default
+ *   message icons or a list of custom message icons retrieved from the database.
+ * - The board_id is needed for the custom message icons (which can be set for
+ *   each board individually).
+ * 
+ * @param int $board_id
+ * @return array
+ */
+function getMessageIcons($board_id)
+{
+	global $modSettings, $context, $txt, $settings, $smcFunc;
+
+	if (empty($modSettings['messageIcons_enable']))
+	{
+		loadLanguage('Post');
+
+		$icons = array(
+			array('value' => 'xx', 'name' => $txt['standard']),
+			array('value' => 'thumbup', 'name' => $txt['thumbs_up']),
+			array('value' => 'thumbdown', 'name' => $txt['thumbs_down']),
+			array('value' => 'exclamation', 'name' => $txt['excamation_point']),
+			array('value' => 'question', 'name' => $txt['question_mark']),
+			array('value' => 'lamp', 'name' => $txt['lamp']),
+			array('value' => 'smiley', 'name' => $txt['icon_smiley']),
+			array('value' => 'angry', 'name' => $txt['icon_angry']),
+			array('value' => 'cheesy', 'name' => $txt['icon_cheesy']),
+			array('value' => 'grin', 'name' => $txt['icon_grin']),
+			array('value' => 'sad', 'name' => $txt['icon_sad']),
+			array('value' => 'wink', 'name' => $txt['icon_wink']),
+			array('value' => 'poll', 'name' => $txt['icon_poll']),
+		);
+
+		foreach ($icons as $k => $dummy)
+		{
+			$icons[$k]['url'] = $settings['images_url'] . '/post/' . $dummy['value'] . '.png';
+			$icons[$k]['is_last'] = false;
+		}
+	}
+	// Otherwise load the icons, and check we give the right image too...
+	else
+	{
+		if (($temp = cache_get_data('posting_icons-' . $board_id, 480)) == null)
+		{
+			$request = $smcFunc['db_query']('select_message_icons', '
+				SELECT title, filename
+				FROM {db_prefix}message_icons
+				WHERE id_board IN (0, {int:board_id})',
+				array(
+					'board_id' => $board_id,
+				)
+			);
+			$icon_data = array();
+			while ($row = $smcFunc['db_fetch_assoc']($request))
+				$icon_data[] = $row;
+			$smcFunc['db_free_result']($request);
+
+			$icons = array();
+			foreach ($icon_data as $icon)
+			{
+				$icons[$icon['filename']] = array(
+					'value' => $icon['filename'],
+					'name' => $icon['title'],
+					'url' => $settings[file_exists($settings['theme_dir'] . '/images/post/' . $icon['filename'] . '.png') ? 'images_url' : 'default_images_url'] . '/post/' . $icon['filename'] . '.png',
+					'is_last' => false,
+				);
+			}
+
+			cache_put_data('posting_icons-' . $board_id, $icons, 480);
+		}
+		else
+			$icons = $temp;
+	}
+
+	return array_values($icons);
+}
+
 /**
  * Compatibility function - used in 1.1 for showing a post box.
  * 
@@ -1450,16 +1473,18 @@ function create_control_richedit($editorOptions)
 		$context['html_headers'] .= '
 		<script type="text/javascript"><!-- // --><![CDATA[
 			var smf_smileys_url = \'' . $settings['smileys_url'] . '\';
-			var oEditorStrings= {
-				wont_work: \'' . addcslashes($txt['rich_edit_wont_work'], "'") . '\',
-				func_disabled: \'' . addcslashes($txt['rich_edit_function_disabled'], "'") . '\',
-				prompt_text_email: \'' . addcslashes($txt['prompt_text_email'], "'") . '\',
-				prompt_text_ftp: \'' . addcslashes($txt['prompt_text_ftp'], "'") . '\',
-				prompt_text_url: \'' . addcslashes($txt['prompt_text_url'], "'") . '\',
-				prompt_text_img: \'' . addcslashes($txt['prompt_text_img'], "'") . '\'
-			}
+			var bbc_quote_from = \'' . addcslashes($txt['quote_from'], "'") . '\';
+			var bbc_quote = \'' . addcslashes($txt['quote'], "'") . '\';
+			var bbc_search_on = \'' . addcslashes($txt['search_on'], "'") . '\';
 		// ]]></script>
-		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/editor.js?alp21"></script>';
+		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/editor.js?alp21"></script>
+		<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/jquery.sceditor.css" type="text/css" media="all" />
+		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/jquery.sceditor.js"></script>
+		<script type="text/javascript" src="' . $settings['default_theme_url'] . '/scripts/jquery.sceditor.bbcode.js"></script>';
+
+		if (!empty($txt['lang_locale']) && $txt['lang_locale'] != 'en_US')
+			$context['html_headers'] .= '
+		<script type="text/javascript" src="' . $scripturl . '?action=loadeditorlocale"></script>';
 
 		$context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
 		if ($context['show_spellchecking'])
@@ -1473,16 +1498,6 @@ function create_control_richedit($editorOptions)
 		<form name="spell_form" id="spell_form" method="post" accept-charset="' . $context['character_set'] . '" target="spellWindow" action="' . $scripturl . '?action=spellcheck">
 			<input type="hidden" name="spellstring" value="" />
 		</form>';
-
-			// Also make sure that spell check works with rich edit.
-			$context['html_headers'] .= '
-		<script type="text/javascript"><!-- // --><![CDATA[
-		function spellCheckDone()
-		{
-			for (i = 0; i < smf_editorArray.length; i++)
-				setTimeout("smf_editorArray[" + i + "].spellCheckEnd()", 150);
-		}
-		// ]]></script>';
 		}
 	}
 
@@ -1490,17 +1505,18 @@ function create_control_richedit($editorOptions)
 	$context['controls']['richedit'][$editorOptions['id']] = array(
 		'id' => $editorOptions['id'],
 		'value' => $editorOptions['value'],
-		'rich_value' => bbc_to_html($editorOptions['value']),
+		'rich_value' => $editorOptions['value'], // 2.0 editor compatibility
 		'rich_active' => empty($modSettings['disable_wysiwyg']) && (!empty($options['wysiwyg_default']) || !empty($editorOptions['force_rich']) || !empty($_REQUEST[$editorOptions['id'] . '_mode'])),
 		'disable_smiley_box' => !empty($editorOptions['disable_smiley_box']),
 		'columns' => isset($editorOptions['columns']) ? $editorOptions['columns'] : 60,
-		'rows' => isset($editorOptions['rows']) ? $editorOptions['rows'] : 12,
+		'rows' => isset($editorOptions['rows']) ? $editorOptions['rows'] : 18,
 		'width' => isset($editorOptions['width']) ? $editorOptions['width'] : '70%',
-		'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '150px',
+		'height' => isset($editorOptions['height']) ? $editorOptions['height'] : '250px',
 		'form' => isset($editorOptions['form']) ? $editorOptions['form'] : 'postmodify',
 		'bbc_level' => !empty($editorOptions['bbc_level']) ? $editorOptions['bbc_level'] : 'full',
 		'preview_type' => isset($editorOptions['preview_type']) ? (int) $editorOptions['preview_type'] : 1,
 		'labels' => !empty($editorOptions['labels']) ? $editorOptions['labels'] : array(),
+		'locale' => !empty($txt['lang_locale']) && substr($txt['lang_locale'], 0, 5) != 'en_US' ? $txt['lang_locale'] : '',
 	);
 
 	// Switch between default images and back... mostly in case you don't have an PersonalMessage template, but do have a Post template.
@@ -1519,8 +1535,7 @@ function create_control_richedit($editorOptions)
 	if (empty($context['bbc_tags']))
 	{
 		// The below array makes it dead easy to add images to this control. Add it to the array and everything else is done for you!
-		$context['bbc_tags'] = array();
-		$context['bbc_tags'][] = array(
+		/*
 			array(
 				'image' => 'bold',
 				'code' => 'b',
@@ -1528,178 +1543,114 @@ function create_control_richedit($editorOptions)
 				'after' => '[/b]',
 				'description' => $txt['bold'],
 			),
+		*/
+		$context['bbc_tags'] = array();
+		$context['bbc_tags'][] = array(
+			array(
+				'code' => 'bold',
+				'description' => $txt['bold'],
+			),
 			array(
-				'image' => 'italicize',
-				'code' => 'i',
-				'before' => '[i]',
-				'after' => '[/i]',
+				'code' => 'italic',
 				'description' => $txt['italic'],
 			),
 			array(
-				'image' => 'underline',
-				'code' => 'u',
-				'before' => '[u]',
-				'after' => '[/u]',
+				'code' => 'underline',
 				'description' => $txt['underline']
 			),
 			array(
-				'image' => 'strike',
-				'code' => 's',
-				'before' => '[s]',
-				'after' => '[/s]',
+				'code' => 'strike',
 				'description' => $txt['strike']
 			),
 			array(),
 			array(
-				'image' => 'pre',
 				'code' => 'pre',
-				'before' => '[pre]',
-				'after' => '[/pre]',
 				'description' => $txt['preformatted']
 			),
 			array(
-				'image' => 'left',
 				'code' => 'left',
-				'before' => '[left]',
-				'after' => '[/left]',
 				'description' => $txt['left_align']
 			),
 			array(
-				'image' => 'center',
 				'code' => 'center',
-				'before' => '[center]',
-				'after' => '[/center]',
 				'description' => $txt['center']
 			),
 			array(
-				'image' => 'right',
 				'code' => 'right',
-				'before' => '[right]',
-				'after' => '[/right]',
 				'description' => $txt['right_align']
 			),
 		);
 		$context['bbc_tags'][] = array(
 			array(
-				'image' => 'flash',
 				'code' => 'flash',
-				'before' => '[flash=200,200]',
-				'after' => '[/flash]',
 				'description' => $txt['flash']
 			),
 			array(
-				'image' => 'img',
-				'code' => 'img',
-				'before' => '[img]',
-				'after' => '[/img]',
+				'code' => 'image',
 				'description' => $txt['image']
 			),
 			array(
-				'image' => 'url',
-				'code' => 'url',
-				'before' => '[url]',
-				'after' => '[/url]',
+				'code' => 'link',
 				'description' => $txt['hyperlink']
 			),
 			array(
-				'image' => 'email',
 				'code' => 'email',
-				'before' => '[email]',
-				'after' => '[/email]',
 				'description' => $txt['insert_email']
 			),
 			array(
-				'image' => 'ftp',
 				'code' => 'ftp',
-				'before' => '[ftp]',
-				'after' => '[/ftp]',
 				'description' => $txt['ftp']
 			),
 			array(),
 			array(
-				'image' => 'glow',
 				'code' => 'glow',
-				'before' => '[glow=red,2,300]',
-				'after' => '[/glow]',
 				'description' => $txt['glow']
 			),
 			array(
-				'image' => 'shadow',
 				'code' => 'shadow',
-				'before' => '[shadow=red,left]',
-				'after' => '[/shadow]',
 				'description' => $txt['shadow']
 			),
 			array(
-				'image' => 'move',
 				'code' => 'move',
-				'before' => '[move]',
-				'after' => '[/move]',
 				'description' => $txt['marquee']
 			),
 			array(),
 			array(
-				'image' => 'sup',
-				'code' => 'sup',
-				'before' => '[sup]',
-				'after' => '[/sup]',
+				'code' => 'superscript',
 				'description' => $txt['superscript']
 			),
 			array(
-				'image' => 'sub',
-				'code' => 'sub',
-				'before' => '[sub]',
-				'after' => '[/sub]',
+				'code' => 'subscript',
 				'description' => $txt['subscript']
 			),
 			array(
-				'image' => 'tele',
 				'code' => 'tt',
-				'before' => '[tt]',
-				'after' => '[/tt]',
 				'description' => $txt['teletype']
 			),
 			array(),
 			array(
-				'image' => 'table',
 				'code' => 'table',
-				'before' => '[table]\n[tr]\n[td]',
-				'after' => '[/td]\n[/tr]\n[/table]',
 				'description' => $txt['table']
 			),
 			array(
-				'image' => 'code',
 				'code' => 'code',
-				'before' => '[code]',
-				'after' => '[/code]',
 				'description' => $txt['bbc_code']
 			),
 			array(
-				'image' => 'quote',
 				'code' => 'quote',
-				'before' => '[quote]',
-				'after' => '[/quote]',
 				'description' => $txt['bbc_quote']
 			),
 			array(),
 			array(
-				'image' => 'list',
-				'code' => 'list',
-				'before' => '[list]\n[li]',
-				'after' => '[/li]\n[li][/li]\n[/list]',
+				'code' => 'bulletlist',
 				'description' => $txt['list_unordered']
 			),
 			array(
-				'image' => 'orderlist',
-				'code' => 'orderlist',
-				'before' => '[list type=decimal]\n[li]',
-				'after' => '[/li]\n[li][/li]\n[/list]',
+				'code' => 'orderedlist',
 				'description' => $txt['list_ordered']
 			),
 			array(
-				'image' => 'hr',
-				'code' => 'hr',
-				'before' => '[hr]',
+				'code' => 'horizontalrule',
 				'description' => $txt['horizontal_rule']
 			),
 		);
@@ -1712,21 +1663,122 @@ function create_control_richedit($editorOptions)
 		{
 			$context['bbc_tags'][count($context['bbc_tags']) - 1][] = array();
 			$context['bbc_tags'][count($context['bbc_tags']) - 1][] = array(
-				'image' => 'unformat',
 				'code' => 'unformat',
-				'before' => '',
 				'description' => $txt['unformat_text'],
 			);
 			$context['bbc_tags'][count($context['bbc_tags']) - 1][] = array(
-				'image' => 'toggle',
 				'code' => 'toggle',
-				'before' => '',
 				'description' => $txt['toggle_view'],
 			);
 		}
 
+		// Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this.
+		$disabled_tags = array();
+		if (!empty($modSettings['disabledBBC']))
+			$disabled_tags = explode(',', $modSettings['disabledBBC']);
+		if (empty($modSettings['enableEmbeddedFlash']))
+			$disabled_tags[] = 'flash';
+
+		foreach ($disabled_tags as $tag)
+		{
+			if ($tag == 'list')
+			{
+				$context['disabled_tags']['bulletlist'] = true;
+				$context['disabled_tags']['orderedlist'] = true;
+			}
+			elseif ($tag == 'b')
+				$context['disabled_tags']['bold'] = true;
+			elseif ($tag == 'i')
+				$context['disabled_tags']['italic'] = true;
+			elseif ($tag == 'i')
+				$context['disabled_tags']['underline'] = true;
+			elseif ($tag == 'i')
+				$context['disabled_tags']['strike'] = true;
+			elseif ($tag == 'img')
+				$context['disabled_tags']['image'] = true;
+			elseif ($tag == 'url')
+				$context['disabled_tags']['link'] = true;
+			elseif ($tag == 'sup')
+				$context['disabled_tags']['superscript'] = true;
+			elseif ($tag == 'sub')
+				$context['disabled_tags']['subscript'] = true;
+			elseif ($tag == 'hr')
+				$context['disabled_tags']['horizontalrule'] = true;
+
+			$context['disabled_tags'][trim($tag)] = true;
+		}
+
+		$bbcodes_styles = '';
+		$context['bbcodes_hanlders'] = '';
+		$context['bbc_toolbar'] = array();
 		foreach ($context['bbc_tags'] as $row => $tagRow)
-			$context['bbc_tags'][$row][count($tagRow) - 1]['isLast'] = true;
+		{
+			if (!isset($context['bbc_toolbar'][$row]))
+				$context['bbc_toolbar'][$row] = array();
+			$tagsRow = array();
+			foreach ($tagRow as $tag)
+			{
+			 if (!empty($tag))
+			 {
+					if (empty($context['disabled_tags'][$tag['code']]))
+					{
+						$tagsRow[] = $tag['code'];
+						if (isset($tag['image']))
+							$bbcodes_styles .= '
+			.sceditor-button-' . $tag['code'] . ' div {
+				background: url(\'' . $settings['default_theme_url'] . '/images/bbc/' . $tag['image'] . '.png\');
+			}';
+						if (isset($tag['before']))
+						{
+							$context['bbcodes_hanlders'] = '
+				$.sceditor.setCommand(
+					' . javaScriptEscape($tag['code']) . ',
+					function () {
+						this.wysiwygEditorInsertHtml(' . javaScriptEscape($tag['before']) . (isset($tag['after']) ? ', ' . javaScriptEscape($tag['after']) : '') . ');
+					},
+					' . javaScriptEscape($tag['description']) . ',
+					null,
+					[' . javaScriptEscape($tag['before']) . (isset($tag['after']) ? ', ' . javaScriptEscape($tag['after']) : '') . ']
+				);';
+						}
+					}
+				}
+				else
+				{
+					$context['bbc_toolbar'][$row][] = implode(',', $tagsRow);
+					$tagsRow = array();
+				}
+			}
+
+			if ($row == 0)
+			{
+				$context['bbc_toolbar'][$row][] = implode(',', $tagsRow);
+				$tagsRow = array();
+				if (!isset($context['disabled_tags']['font']))
+					$tagsRow[] = 'font';
+				if (!isset($context['disabled_tags']['size']))
+					$tagsRow[] = 'size';
+				if (!isset($context['disabled_tags']['color']))
+					$tagsRow[] = 'color';
+			}
+			elseif ($row == 1 && empty($modSettings['disable_wysiwyg']))
+			{
+				$tmp = array();
+				$tagsRow[] = 'removeformat';
+				$tagsRow[] = 'source';
+				if (!empty($tmp))
+				{
+					$tagsRow[] = '|' . implode(',', $tmp);
+				}
+			}
+
+			if (!empty($tagsRow))
+				$context['bbc_toolbar'][$row][] = implode(',', $tagsRow);
+		}
+		if (!empty($bbcodes_styles))
+			$context['html_headers'] .= '
+		<style type="text/css">' . $bbcodes_styles . '
+		</style>';
 	}
 
 	// Initialize smiley array... if not loaded before.
@@ -1865,21 +1917,6 @@ function create_control_richedit($editorOptions)
 	// Set a flag so the sub template knows what to do...
 	$context['show_bbc'] = !empty($modSettings['enableBBC']) && !empty($settings['show_bbc']);
 
-	// Generate a list of buttons that shouldn't be shown - this should be the fastest way to do this.
-	$disabled_tags = array();
-	if (!empty($modSettings['disabledBBC']))
-		$disabled_tags = explode(',', $modSettings['disabledBBC']);
-	if (empty($modSettings['enableEmbeddedFlash']))
-		$disabled_tags[] = 'flash';
-
-	foreach ($disabled_tags as $tag)
-	{
-		if ($tag == 'list')
-			$context['disabled_tags']['orderlist'] = true;
-
-		$context['disabled_tags'][trim($tag)] = true;
-	}
-
 	// Switch the URLs back... now we're back to whatever the main sub template is.  (like folder in PersonalMessage.)
 	if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'defaults' && isset($settings['default_template']))
 	{

+ 1 - 1
Sources/Subs-Post.php

@@ -1482,7 +1482,7 @@ function SpellCheck()
 	// Construct a bit of Javascript code.
 	$context['spell_js'] = '
 		var txt = {"done": "' . $txt['spellcheck_done'] . '"};
-		var mispstr = window.opener.document.forms[spell_formname][spell_fieldname].value;
+		var mispstr = window.opener.spellCheckGetText(spell_fieldname);
 		var misps = Array(';
 
 	// Get all the words (Javascript already separated them).

+ 64 - 200
Themes/default/GenericControls.template.php

@@ -13,244 +13,108 @@
 // This function displays all the stuff you get with a richedit box - BBC, smileys etc.
 function template_control_richedit($editor_id, $smileyContainer = null, $bbcContainer = null)
 {
-	global $context, $settings, $options, $txt, $modSettings, $scripturl;
+	global $context, $settings, $modSettings;
 
 	$editor_context = &$context['controls']['richedit'][$editor_id];
 
 	echo '
 		<div>
-			<div style="width: 98.8%;">
-				<div>
-					<textarea class="editor" name="', $editor_id, '" id="', $editor_id, '" rows="', $editor_context['rows'], '" cols="600" onselect="storeCaret(this);" onclick="storeCaret(this);" onkeyup="storeCaret(this);" onchange="storeCaret(this);" tabindex="', $context['tabindex']++, '" style="height: ', $editor_context['height'], '; ', isset($context['post_error']['no_message']) || isset($context['post_error']['long_message']) ? 'border: 1px solid red;' : '', '">', $editor_context['value'], '</textarea>
-				</div>
-				<div id="', $editor_id, '_resizer" class="richedit_resize"></div>
-			</div>
+			<textarea class="editor" name="', $editor_id, '" id="', $editor_id, '" rows="', $editor_context['rows'], '" cols="600" onselect="storeCaret(this);" onclick="storeCaret(this);" onkeyup="storeCaret(this);" onchange="storeCaret(this);" tabindex="', $context['tabindex']++, '" style="height: ', $editor_context['height'], '; ', isset($context['post_error']['no_message']) || isset($context['post_error']['long_message']) ? 'border: 1px solid red;' : '', '">', $editor_context['value'], '</textarea>
 		</div>
 		<input type="hidden" name="', $editor_id, '_mode" id="', $editor_id, '_mode" value="0" />
-		<script type="text/javascript"><!-- // --><![CDATA[';
+		<script type="text/javascript"><!-- // --><![CDATA[
+			$(document).ready(function() {
+				', !empty($context['bbcodes_hanlders']) ? $context['bbcodes_hanlders'] : '', '
+
+				$("#', $editor_id, '").sceditorBBCodePlugin({
+					style: "', $settings['default_theme_url'], '/css/jquery.sceditor.default.css",
+					emoticonsCompat: true,
+					supportedWysiwyg: ((is_ie5up && !is_ie50) || is_ff || is_opera95up || is_safari || is_chrome),',
+					!empty($editor_context['locale']) ? '
+					locale: \'' . $editor_context['locale'] . '\',' : '', '
+					colors: "black,red,yellow,pink,green,orange,purple,blue,beige,brown,teal,navy,maroon,limegreen,white"';
 
 		// Show the smileys.
 		if ((!empty($context['smileys']['postform']) || !empty($context['smileys']['popup'])) && !$editor_context['disable_smiley_box'] && $smileyContainer !== null)
 		{
-			echo '
-				var oSmileyBox_', $editor_id, ' = new smc_SmileyBox({
-					sUniqueId: ', JavaScriptEscape('smileyBox_' . $editor_id), ',
-					sContainerDiv: ', JavaScriptEscape($smileyContainer), ',
-					sClickHandler: ', JavaScriptEscape('oEditorHandle_' . $editor_id . '.insertSmiley'), ',
-					oSmileyLocations: {';
-
+			echo ',
+					emoticons:
+					{';
+			$countLocations = count($context['smileys']);
 			foreach ($context['smileys'] as $location => $smileyRows)
 			{
-				echo '
-						', $location, ': [';
+				$countLocations--;
+				if ($location == 'postform')
+					echo '
+						dropdown:
+						{';
+				elseif ($location == 'popup')
+					echo '
+						popup:
+						{';
+
+				$numRows = count($smileyRows);
 				foreach ($smileyRows as $smileyRow)
 				{
-					echo '
-							[';
 					foreach ($smileyRow['smileys'] as $smiley)
+					{
 						echo '
-								{
-									sCode: ', JavaScriptEscape($smiley['code']), ',
-									sSrc: ', JavaScriptEscape($settings['smileys_url'] . '/' . $smiley['filename']), ',
-									sDescription: ', JavaScriptEscape($smiley['description']), '
-								}', empty($smiley['isLast']) ? ',' : '';
-
-				echo '
-							]', empty($smileyRow['isLast']) ? ',' : '';
+								', JavaScriptEscape($smiley['code']), ': ', JavaScriptEscape($settings['smileys_url'] . '/' . $smiley['filename']), empty($smiley['isLast']) ? ',' : '';
+					}
+					if (empty($smileyRow['isLast']) && $numRows != 1)
+						echo ',
+						\'\': \'\',';
 				}
 				echo '
-						]', $location === 'postform' ? ',' : '';
+						}', $countLocations != 0 ? ',' : '';
 			}
 			echo '
-					},
-					sSmileyBoxTemplate: ', JavaScriptEscape('
-						%smileyRows% %moreSmileys%
-					'), ',
-					sSmileyRowTemplate: ', JavaScriptEscape('
-						<div>%smileyRow%</div>
-					'), ',
-					sSmileyTemplate: ', JavaScriptEscape('
-						<img src="%smileySource%" align="bottom" alt="%smileyDescription%" title="%smileyDescription%" id="%smileyId%" />
-					'), ',
-					sMoreSmileysTemplate: ', JavaScriptEscape('
-						<a href="#" id="%moreSmileysId%">[' . (!empty($context['smileys']['postform']) ? $txt['more_smileys'] : $txt['more_smileys_pick']) . ']</a>
-					'), ',
-					sMoreSmileysLinkId: ', JavaScriptEscape('moreSmileys_' . $editor_id), ',
-					sMoreSmileysPopupTemplate: ', JavaScriptEscape('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-						<html>
-							<head>
-								<title>' . $txt['more_smileys_title'] . '</title>
-								<link rel="stylesheet" type="text/css" href="' . $settings['theme_url'] . '/css/index' . $context['theme_variant'] . '.css?alp21" />
-							</head>
-							<body id="help_popup">
-								<div class="padding windowbg">
-									<div class="cat_bar">
-										<h3 class="catbg">
-											' . $txt['more_smileys_pick'] . '
-										</h3>
-									</div>
-									<div class="padding">
-										%smileyRows%
-									</div>
-									<div class="smalltext centertext">
-										<a href="#" id="%moreSmileysCloseLinkId%">' . $txt['more_smileys_close_window'] . '</a>
-									</div>
-								</div>
-							</body>
-						</html>'), '
-				});';
+					}';
 		}
 
 		if ($context['show_bbc'] && $bbcContainer !== null)
 		{
-			echo '
-				var oBBCBox_', $editor_id, ' = new smc_BBCButtonBox({
-					sUniqueId: ', JavaScriptEscape('BBCBox_' . $editor_id), ',
-					sContainerDiv: ', JavaScriptEscape($bbcContainer), ',
-					sButtonClickHandler: ', JavaScriptEscape('oEditorHandle_' . $editor_id . '.handleButtonClick'), ',
-					sSelectChangeHandler: ', JavaScriptEscape('oEditorHandle_' . $editor_id . '.handleSelectChange'), ',
-					aButtonRows: [';
-
-			// Here loop through the array, printing the images/rows/separators!
-			foreach ($context['bbc_tags'] as $i => $buttonRow)
+			echo ',
+					toolbar: "emoticon,';
+			$count_tags = count($context['bbc_tags']);
+			foreach ($context['bbc_toolbar'] as $i => $buttonRow)
 			{
-				echo '
-						[';
-				foreach ($buttonRow as $tag)
-				{
-					// Is there a "before" part for this bbc button? If not, it can't be a button!!
-					if (isset($tag['before']))
-						echo '
-							{
-								sType: \'button\',
-								bEnabled: ', empty($context['disabled_tags'][$tag['code']]) ? 'true' : 'false', ',
-								sImage: ', file_exists($settings['theme_dir'] . '/images/bbc/' . $tag['image'] . '.png') ? JavaScriptEscape($settings['images_url'] . '/bbc/' . $tag['image'] . '.png') : JavaScriptEscape($settings['images_url'] . '/bbc/' . $tag['image'] . '.gif'), ',
-								sCode: ', JavaScriptEscape($tag['code']), ',
-								sBefore: ', JavaScriptEscape($tag['before']), ',
-								sAfter: ', isset($tag['after']) ? JavaScriptEscape($tag['after']) : 'null', ',
-								sDescription: ', JavaScriptEscape($tag['description']), '
-							}', empty($tag['isLast']) ? ',' : '';
+				echo implode('|', $buttonRow);
+				$count_tags--;
+				if (!empty($count_tags))
+					echo '||';
+			}
 
-					// Must be a divider then.
-					else
-						echo '
-							{
-								sType: \'divider\'
-							}', empty($tag['isLast']) ? ',' : '';
-				}
+			echo '",';
+		}
+		else
+			echo ',
+					toolbar: "emoticon,source",';
 
-				// Add the select boxes to the first row.
-				if ($i == 0)
+		echo '
+				});
+				$("#', $editor_id, '").data("sceditor").createPermanentDropDown();
+				$(".sceditor-container").width("100%").height("100%");', 
+				$editor_context['rich_active'] ? '' : '
+				$("#' . $editor_id . '").data("sceditor").setTextMode();', '
+				if (!((is_ie5up && !is_ie50) || is_ff || is_opera95up || is_safari || is_chrome))
 				{
-					// Show the font drop down...
-					if (!isset($context['disabled_tags']['font']))
-						echo ',
-							{
-								sType: \'select\',
-								sName: \'sel_face\',
-								oOptions: {
-									\'\': ', JavaScriptEscape($txt['font_face']), ',
-									\'courier\': \'Courier\',
-									\'arial\': \'Arial\',
-									\'arial black\': \'Arial Black\',
-									\'impact\': \'Impact\',
-									\'verdana\': \'Verdana\',
-									\'times new roman\': \'Times New Roman\',
-									\'georgia\': \'Georgia\',
-									\'andale mono\': \'Andale Mono\',
-									\'trebuchet ms\': \'Trebuchet MS\',
-									\'comic sans ms\': \'Comic Sans MS\'
-								}
-							}';
-
-					// Font sizes anyone?
-					if (!isset($context['disabled_tags']['size']))
-						echo ',
-							{
-								sType: \'select\',
-								sName: \'sel_size\',
-								oOptions: {
-									\'\': ', JavaScriptEscape($txt['font_size']), ',
-									\'1\': \'8pt\',
-									\'2\': \'10pt\',
-									\'3\': \'12pt\',
-									\'4\': \'14pt\',
-									\'5\': \'18pt\',
-									\'6\': \'24pt\',
-									\'7\': \'36pt\'
-								}
-							}';
-
-					// Print a drop down list for all the colors we allow!
-					if (!isset($context['disabled_tags']['color']))
-						echo ',
-							{
-								sType: \'select\',
-								sName: \'sel_color\',
-								oOptions: {
-									\'\': ', JavaScriptEscape($txt['change_color']), ',
-									\'black\': ', JavaScriptEscape($txt['black']), ',
-									\'red\': ', JavaScriptEscape($txt['red']), ',
-									\'yellow\': ', JavaScriptEscape($txt['yellow']), ',
-									\'pink\': ', JavaScriptEscape($txt['pink']), ',
-									\'green\': ', JavaScriptEscape($txt['green']), ',
-									\'orange\': ', JavaScriptEscape($txt['orange']), ',
-									\'purple\': ', JavaScriptEscape($txt['purple']), ',
-									\'blue\': ', JavaScriptEscape($txt['blue']), ',
-									\'beige\': ', JavaScriptEscape($txt['beige']), ',
-									\'brown\': ', JavaScriptEscape($txt['brown']), ',
-									\'teal\': ', JavaScriptEscape($txt['teal']), ',
-									\'navy\': ', JavaScriptEscape($txt['navy']), ',
-									\'maroon\': ', JavaScriptEscape($txt['maroon']), ',
-									\'limegreen\': ', JavaScriptEscape($txt['lime_green']), ',
-									\'white\': ', JavaScriptEscape($txt['white']), '
-								}
-							}';
+					$("#' . $editor_id . '").data("sceditor").setTextMode();
+					$(".sceditor-button-source").hide();
 				}
-				echo '
-						]', $i == count($context['bbc_tags']) - 1 ? '' : ',';
-			}
-			echo '
-					],
-					sButtonTemplate: ', JavaScriptEscape('
-						<img id="%buttonId%" src="%buttonSrc%" align="bottom" width="23" height="22" alt="%buttonDescription%" title="%buttonDescription%" />
-					'), ',
-					sButtonBackgroundImage: ', JavaScriptEscape($settings['images_url'] . '/bbc/bbc_bg.png'), ',
-					sButtonBackgroundImageHover: ', JavaScriptEscape($settings['images_url'] . '/bbc/bbc_hoverbg.png'), ',
-					sActiveButtonBackgroundImage: ', JavaScriptEscape($settings['images_url'] . '/bbc/bbc_hoverbg.png'), ',
-					sDividerTemplate: ', JavaScriptEscape('
-						<img src="' . $settings['images_url'] . '/bbc/divider.png" alt="|" style="margin: 0 3px;" />
-					'), ',
-					sSelectTemplate: ', JavaScriptEscape('
-						<select name="%selectName%" id="%selectId%" style="margin-bottom: 1ex; font-size: x-small;">
-							%selectOptions%
-						</select>
-					'), ',
-					sButtonRowTemplate: ', JavaScriptEscape('
-						<div>%buttonRow%</div>
-					'), '
-				});';
-		}
+			});';
 
-		// Now it's all drawn out we'll actually setup the box.
+		// Now for backward compatibility let's collect few infos in the good ol' style
 		echo '
 				var oEditorHandle_', $editor_id, ' = new smc_Editor({
-					sSessionId: smf_session_id,
-					sSessionVar: smf_session_var,
-					sFormId: ', JavaScriptEscape($editor_context['form']), ',
 					sUniqueId: ', JavaScriptEscape($editor_id), ',
-					bRTL: ', $txt['lang_rtl'] ? 'true' : 'false', ',
-					bWysiwyg: ', $editor_context['rich_active'] ? 'true' : 'false', ',
-					sText: ', JavaScriptEscape($editor_context['rich_active'] ? $editor_context['rich_value'] : ''), ',
 					sEditWidth: ', JavaScriptEscape($editor_context['width']), ',
 					sEditHeight: ', JavaScriptEscape($editor_context['height']), ',
 					bRichEditOff: ', empty($modSettings['disable_wysiwyg']) ? 'false' : 'true', ',
-					oSmileyBox: ', !empty($context['smileys']['postform']) && !$editor_context['disable_smiley_box'] && $smileyContainer !== null ? 'oSmileyBox_' . $editor_id : 'null', ',
-					oBBCBox: ', $context['show_bbc'] && $bbcContainer !== null ? 'oBBCBox_' . $editor_id : 'null', '
+					oSmileyBox: null,
+					oBBCBox: null
 				});
-				smf_editorArray[smf_editorArray.length] = oEditorHandle_', $editor_id, ';';
-
-		echo '
+				smf_editorArray[smf_editorArray.length] = oEditorHandle_', $editor_id, ';
 			// ]]></script>';
 }
 

+ 12 - 17
Themes/default/Post.template.php

@@ -345,17 +345,6 @@ function template_main()
 	}
 
 	// Show the actual posting area...
-	if ($context['show_bbc'])
-	{
-		echo '
-					<div id="bbcBox_message"></div>';
-	}
-
-	// What about smileys?
-	if (!empty($context['smileys']['postform']) || !empty($context['smileys']['popup']))
-		echo '
-					<div id="smileyBox_message"></div>';
-
 	echo '
 					', template_control_richedit($context['post_box_name'], 'smileyBox_message', 'bbcBox_message');
 
@@ -579,8 +568,8 @@ function template_main()
 						if (textFields[i] in document.forms.postmodify)
 						{
 							// Handle the WYSIWYG editor.
-							if (textFields[i] == ', JavaScriptEscape($context['post_box_name']), ' && ', JavaScriptEscape('oEditorHandle_' . $context['post_box_name']), ' in window && oEditorHandle_', $context['post_box_name'], '.bRichTextEnabled)
-								x[x.length] = \'message_mode=1&\' + textFields[i] + \'=\' + oEditorHandle_', $context['post_box_name'], '.getText(false).replace(/&#/g, \'&#38;#\').php_to8bit().php_urlencode();
+							if (textFields[i] == ', JavaScriptEscape($context['post_box_name']), ' && $("#', $context['post_box_name'], '").data("sceditor") != undefined)
+								x[x.length] = textFields[i] + \'=\' + $("#', $context['post_box_name'], '").data("sceditor").getText().replace(/&#/g, \'&#38;#\').php_to8bit().php_urlencode();
 							else
 								x[x.length] = textFields[i] + \'=\' + document.forms.postmodify[textFields[i]].value.replace(/&#/g, \'&#38;#\').php_to8bit().php_urlencode();
 						}
@@ -841,17 +830,23 @@ function template_main()
 			function insertQuoteFast(messageid)
 			{
 				if (window.XMLHttpRequest)
-					getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + \'action=quotefast;quote=\' + messageid + \';xml;pb=', $context['post_box_name'], ';mode=\' + (oEditorHandle_', $context['post_box_name'], '.bRichTextEnabled ? 1 : 0), onDocReceived);
+					getXMLDocument(smf_prepareScriptUrl(smf_scripturl) + \'action=quotefast;quote=\' + messageid + \';xml;pb=', $context['post_box_name'], ';mode=0\', onDocReceived);
 				else
-					reqWin(smf_prepareScriptUrl(smf_scripturl) + \'action=quotefast;quote=\' + messageid + \';pb=', $context['post_box_name'], ';mode=\' + (oEditorHandle_', $context['post_box_name'], '.bRichTextEnabled ? 1 : 0), 240, 90);
+					reqWin(smf_prepareScriptUrl(smf_scripturl) + \'action=quotefast;quote=\' + messageid + \';pb=', $context['post_box_name'], ';mode=0\', 240, 90);
+
 				return true;
 			}
 			function onDocReceived(XMLDoc)
 			{
 				var text = \'\';
+
 				for (var i = 0, n = XMLDoc.getElementsByTagName(\'quote\')[0].childNodes.length; i < n; i++)
 					text += XMLDoc.getElementsByTagName(\'quote\')[0].childNodes[i].nodeValue;
-				oEditorHandle_', $context['post_box_name'], '.insertText(text, false, true);
+				$("#', $context['post_box_name'], '").data("sceditor").InsertText(text);
+			}
+			function onReceiveOpener(text)
+			{
+				$("#', $context['post_box_name'], '").data("sceditor").InsertText(text);
 			}
 		// ]]></script>';
 	}
@@ -972,7 +967,7 @@ function template_quotefast()
 			if (\'opera\' in window)
 				quote = quote.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, \'"\').replace(/&amp;/g, "&");
 
-			window.opener.oEditorHandle_', $context['post_box_name'], '.InsertText(quote);
+			window.opener.onReceiveOpener(quote);
 
 			window.focus();
 			setTimeout("window.close();", 400);';

+ 289 - 0
Themes/default/css/jquery.sceditor.css

@@ -0,0 +1,289 @@
+/**
+ * SCEditor v1.3.1
+ * http://www.samclarke.com/2011/07/sceditor/
+ *
+ * Copyright (C) 2011, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * Icons by Mark James (http://www.famfamfam.com/lab/icons/silk/)
+ * Licensed under the Creative Commons CC-BY license (http://creativecommons.org/licenses/by/3.0/)
+ */
+
+/*
+	spritemapper.output_image = minified/jquery.sceditor.min.png
+	spritemapper.output_css = minified/jquery.sceditor.min.css
+*/
+.sceditor-container {
+	position: relative;
+	overflow: hidden;
+	border: 1px solid #aaa;
+	background: #fff;
+
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	-opera-border-radius: 4px;
+	-khtml-border-radius: 4px;
+	border-radius: 4px;
+}
+
+.sceditor-container, .sceditor-container div,
+div.sceditor-dropdown, div.sceditor-dropdown div {
+	padding: 0;
+	margin: 0;
+	z-index: 3;
+}
+	.sceditor-container iframe, .sceditor-container textarea {
+		border: 0;
+		outline: none;
+		font-family: Verdana, Arial, Helvetica, sans-serif;
+		font-size: 13px;
+		color: #111;
+		padding: 0;
+		margin: 5px;
+		resize: none;
+	}
+	div.sceditor-resize-cover {
+		position: absolute;
+		top: 0;
+		left: 0;
+		background: #000;
+		width: 3000px;
+		height: 6000px;
+		z-index: 2;
+		filter: alpha(opacity=30);
+		opacity: 0.3;
+	}
+	div.sceditor-grip {
+		background: #000;
+		overflow: hidden;
+		width: 10px;
+		height: 10px;
+		cursor: pointer;
+		position: absolute;
+		bottom: 0;
+		right: 0;
+		z-index: 3;
+		background: url('../images/bbc/icons/grip.png');
+	}
+	div.sceditor-dropdown {
+		position: absolute;
+		margin: 22px 0 0 1px;
+		border: 1px solid #bbb;
+		background: #fff;
+		color: #222;
+		font-size: 13px;
+		z-index: 6;
+	}
+		div.sceditor-dropdown form { margin: 0; }
+		div.sceditor-dropdown label {
+			display:block;
+			font-weight: bold;
+			font-size: .95em;
+			margin: .65em 0 .15em;
+		}
+		div.sceditor-dropdown .button {	margin: .5em 0 0; }
+		.sceditor-pastetext textarea { border: 1px solid #bbb; width: 20em; }
+		div.sceditor-inserttable, div.sceditor-insertimage,
+		div.sceditor-pastetext, div.sceditor-insertlink,
+		div.sceditor-insertemail {
+			padding: 5px;
+		}
+		div.sceditor-smileyPopup {
+			margin: 0;
+		}
+		.sceditor-insertemoticon img, .sceditor-smileyPopup img {
+			cursor: pointer;
+			margin: 2px;
+		}
+		.sceditor-more, .sceditor-smileyPopup {
+			display: inline;
+			text-align: center;
+			cursor: pointer;
+			padding: 2px 0;
+		}
+		.sceditor-more:hover { background: #eee; }
+		.sceditor-fontsize-option, .sceditor-font-option {
+			display: block;
+			padding: 4px 6px;
+			cursor: pointer;
+			font-size: 14px;
+			text-decoration: none;
+			color: #222;
+		}
+		.sceditor-fontsize-option:hover, .sceditor-font-option:hover { background: #eee; }
+		.sceditor-color-column { float: left; }
+			.sceditor-color-option {
+				display: block;
+				border: 1px solid #fff;
+				height: 20px;
+				width: 100px;
+				overflow: hidden;
+			}
+			.sceditor-color-option:hover { border: 1px solid #333; }
+
+	div.sceditor-toolbar {
+		overflow: hidden;
+		zoom: 1; /* IE6 */
+		padding: 3px 5px 0 5px;
+		*padding: 3px 5px 3px 5px;
+		background: #f7f7f7;
+		border-bottom: 1px solid #aaa;
+
+		-webkit-border-radius: 4px 4px 0 0;
+	}
+
+		div.sceditor-group {
+			overflow: hidden;
+			display: inline-block;
+			zoom: 1; /* IE6 */
+			*display: inline;
+			background: #ddd;
+			margin: 1px 5px 1px 0;
+			*margin: 2px 5px 2px 0;
+			padding: 2px;
+
+			-moz-border-radius: 4px;
+			-webkit-border-radius: 4px;
+			-opera-border-radius: 4px;
+			-khtml-border-radius: 4px;
+			border-radius: 4px;
+		}
+		div.sceditor-row {
+			overflow: hidden;
+			display: block;
+			zoom: 1; /* IE6 */
+			*display: inline;
+		}
+
+		.sceditor-button {
+			float: left;
+			cursor: pointer;
+			padding: 3px 4px;
+		}
+		/*.sceditor-button.disabled {
+			background-color: #666;
+		}*/
+		.sceditor-button.disabled div {
+			filter: alpha(opacity=30);
+			opacity: 0.3;
+		}
+		.sceditor-button.disabled:hover {
+			background: inherit;
+			cursor: default;
+		}
+		.sceditor-button div, .sceditor-button {
+			display: block;
+			width: 18px;
+			height: 18px;
+			background-repeat:no-repeat !important;
+			background-position:center center !important;
+		}
+		.sceditor-button div {
+			margin: 0;
+			padding: 0;
+			text-indent: -9999px;
+		}
+		.sceditor-button:hover { background: #eee; }
+			.sceditor-button-emoticon { display:none; }
+
+.sceditor-popup-grip {
+	display: block;
+	height: 15px;
+}
+
+/* SMF buttons styles */
+.sceditor-button-source div {
+	background: url('../images/bbc/toggle.png');
+}
+.sceditor-button-font div {
+	background: url('../images/bbc/font.png');
+}
+.sceditor-button-size div {
+	background: url('../images/bbc/font_size.png');
+}
+.sceditor-button-color div {
+	background: url('../images/bbc/color_swatch.png');
+}
+.sceditor-button-removeformat div {
+	background: url('../images/bbc/font_delete.png');
+}
+.sceditor-button-bold div {
+	background: url('../images/bbc/bold.png');
+}
+.sceditor-button-italic div {
+	background: url('../images/bbc/italicize.png');
+}
+.sceditor-button-underline div {
+	background: url('../images/bbc/underline.png');
+}
+.sceditor-button-strike div {
+	background: url('../images/bbc/strike.png');
+}
+.sceditor-button-pre div {
+	background: url('../images/bbc/pre.png');
+}
+.sceditor-button-left div {
+	background: url('../images/bbc/left.png');
+}
+.sceditor-button-center div {
+	background: url('../images/bbc/center.png');
+}
+.sceditor-button-right div {
+	background: url('../images/bbc/right.png');
+}
+.sceditor-button-image div {
+	background: url('../images/bbc/img.png');
+}
+.sceditor-button-link div {
+	background: url('../images/bbc/url.png');
+}
+.sceditor-button-email div {
+	background: url('../images/bbc/email.png');
+}
+.sceditor-button-ftp div {
+	background: url('../images/bbc/ftp.png');
+}
+.sceditor-button-glow div {
+	background: url('../images/bbc/glow.png');
+}
+.sceditor-button-shadow div {
+	background: url('../images/bbc/shadow.png');
+}
+.sceditor-button-move div {
+	background: url('../images/bbc/move.png');
+}
+.sceditor-button-superscript div {
+	background: url('../images/bbc/sup.png');
+}
+.sceditor-button-subscript div {
+	background: url('../images/bbc/sub.png');
+}
+.sceditor-button-tt div {
+	background: url('../images/bbc/tele.png');
+}
+.sceditor-button-table div {
+	background: url('../images/bbc/table.png');
+}
+.sceditor-button-code div {
+	background: url('../images/bbc/code.png');
+}
+.sceditor-button-quote div {
+	background: url('../images/bbc/quote.png');
+}
+.sceditor-button-bulletlist div {
+	background: url('../images/bbc/list.png');
+}
+.sceditor-button-orderedlist div {
+	background: url('../images/bbc/orderlist.png');
+}
+.sceditor-button-horizontalrule div {
+	background: url('../images/bbc/hr.png');
+}
+.sceditor-button-unformat div {
+	background: url('../images/bbc/unformat.png');
+}
+.sceditor-button-toggle div {
+	background: url('../images/bbc/toggle.png');
+}

+ 59 - 0
Themes/default/css/jquery.sceditor.default.css

@@ -0,0 +1,59 @@
+html, body, p, code:before, table {
+	margin: 0;
+	padding: 0;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 13px;
+	color: #111;
+}
+
+ul, ol {
+	margin-top: 0;
+	margin-bottom: 0;
+	padding-top: 0;
+	padding-bottom: 0;
+}
+
+table, td {
+	border: 1px dotted #000;
+	empty-cells: show;
+}
+
+code:before {
+	position: absolute;
+	content: 'Code:';
+	top: -1.35em;
+	left: 0;
+}
+code {
+	margin-top: 1.5em;
+	position: relative;
+	background: #eee;
+	border: 1px solid #aaa;
+	white-space: pre;
+	padding: .25em;
+}
+code:before, code {
+	display: block;
+	text-align: left;
+}
+
+blockquote {
+	position: relative;
+	background: #fff6c7;
+	margin: .25em 0;
+	border: 1px solid #aaa;
+	padding: .25em;
+}
+blockquote cite, code cite {
+	font-weight: bold;
+	display: block;
+	font-size: 1em;
+	border-bottom: 1px solid #aaa;
+}
+code cite {
+	position: relative;
+	top: -1.6em;
+	padding-left: 3em;
+}
+div { min-height: 1em; /*height:auto !important; height: 1em;*/ }
+h1, h2, h3, h4, h5, h6 { padding: 0; margin: 0; }

BIN
Themes/default/images/bbc/color_swatch.png


BIN
Themes/default/images/bbc/font.png


BIN
Themes/default/images/bbc/font_delete.png


BIN
Themes/default/images/bbc/font_size.png


+ 54 - 0
Themes/default/languages/Editor.english.php

@@ -0,0 +1,54 @@
+<?php
+// Version: 2.1; Editor
+global $editortxt;
+
+$editortxt['Bold'] = 'Bold';
+$editortxt['Italic'] = 'Italic';
+$editortxt['Underline'] = 'Underline';
+$editortxt['Strikethrough'] = 'Strikethrough';
+$editortxt['Subscript'] = 'Subscript';
+$editortxt['Superscript'] = 'Superscript';
+$editortxt['Align left'] = 'Align left';
+$editortxt['Center'] = 'Center';
+$editortxt['Align right'] = 'Align right';
+$editortxt['Justify'] = 'Justify';
+$editortxt['Font Name'] = 'Font Name';
+$editortxt['Font Size'] = 'Font Size';
+$editortxt['Font Color'] = 'Font Color';
+$editortxt['Remove Formatting'] = 'Remove Formatting';
+$editortxt['Cut'] = 'Cut';
+$editortxt['Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X'] = 'Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X';
+$editortxt['Copy'] = 'Copy';
+$editortxt['Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C'] = 'Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C';
+$editortxt['Paste'] = 'Paste';
+$editortxt['Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V'] = 'Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V';
+$editortxt['Paste your text inside the following box:'] = 'Paste your text inside the following box:';
+$editortxt['Paste Text'] = 'Paste Text';
+$editortxt['Bullet list'] = 'Bullet list';
+$editortxt['Numbered list'] = 'Numbered list';
+$editortxt['Undo'] = 'Undo';
+$editortxt['Redo'] = 'Redo';
+$editortxt['Rows:'] = 'Rows:';
+$editortxt['Cols:'] = 'Cols:';
+$editortxt['Insert a table'] = 'Insert a table';
+$editortxt['Insert a horizontal rule'] = 'Insert a horizontal rule';
+$editortxt['Code'] = 'Code';
+$editortxt['Insert a Quote'] = 'Insert a Quote';
+$editortxt['Width (optional):'] = 'Width (optional):';
+$editortxt['Height (optional):'] = 'Height (optional):';
+$editortxt['Insert an image'] = 'Insert an image';
+$editortxt['E-mail:'] = 'E-mail:';
+$editortxt['Insert an email'] = 'Insert an email';
+$editortxt['URL:'] = 'URL:';
+$editortxt['Insert a link'] = 'Insert a link';
+$editortxt['Unlink'] = 'Unlink';
+$editortxt['More'] = 'More';
+$editortxt['Insert an emoticon'] = 'Insert an emoticon';
+$editortxt['Video URL:'] = 'Video URL:';
+$editortxt['Insert'] = 'Insert';
+$editortxt['Insert a YouTube video'] = 'Insert a YouTube video';
+$editortxt['Insert current date'] = 'Insert current date';
+$editortxt['Insert current time'] = 'Insert current time';
+$editortxt['Print'] = 'Print';
+$editortxt['Preformatted Text'] = 'Preformatted Text';
+$editortxt['View source'] = 'View source';

File diff suppressed because it is too large
+ 9 - 1055
Themes/default/scripts/editor.js


+ 1509 - 0
Themes/default/scripts/jquery.sceditor.bbcode.js

@@ -0,0 +1,1509 @@
+/**
+ * SCEditor BBCode Plugin
+ * http://www.samclarke.com/2011/07/sceditor/
+ *
+ * Copyright (C) 2011-2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @author Sam Clarke
+ * @version 1.3.7
+ * @requires jQuery
+ */
+
+// ==ClosureCompiler==
+// @output_file_name jquery.sceditor.min.js
+// @compilation_level SIMPLE_OPTIMIZATIONS
+// ==/ClosureCompiler==
+
+/*jshint smarttabs: true, jquery: true, eqnull:true, curly: false */
+
+(function($) {
+	'use strict';
+
+	/**
+	 * BBCode plugin for SCEditor
+	 *
+	 * @param {Element} el The textarea to be converted
+	 * @return {Object} options
+	 * @class sceditorBBCodePlugin
+	 * @name jQuery.sceditorBBCodePlugin
+	 */
+	$.sceditorBBCodePlugin = function(element, options) {
+		var base = this;
+
+		/**
+		 * Private methods
+		 * @private
+		 */
+		var	init,
+			buildBbcodeCache,
+			handleStyles,
+			handleTags,
+			formatString,
+			getStyle,
+			wrapInDivs,
+			isEmpty,
+			mergeTextModeCommands;
+
+		base.bbcodes = $.sceditorBBCodePlugin.bbcodes;
+
+
+		/**
+		 * cache of all the tags pointing to their bbcodes to enable
+		 * faster lookup of which bbcode a tag should have
+		 * @private
+		 */
+		var tagsToBbcodes = {};
+
+		/**
+		 * Same as tagsToBbcodes but instead of HTML tags it's styles
+		 * @private
+		 */
+		var stylesToBbcodes = {};
+
+		/**
+		 * Allowed children of specific HTML tags. Empty array if no
+		 * children other than text nodes are allowed
+		 * @private
+		 */
+		var validChildren = {
+			list: ['li'],
+			table: ['tr'],
+			tr: ['td', 'th'],
+			code: ['br', 'p', 'div'],
+			youtube: []
+		};
+
+
+		/**
+		 * Initializer
+		 * @private
+		 * @name sceditorBBCodePlugin.init
+		 */
+		init = function() {
+			$.data(element, "sceditorbbcode", base);
+
+			base.options = $.extend({}, $.sceditor.defaultOptions, options);
+
+			// build the BBCode cache
+			buildBbcodeCache();
+
+			(new $.sceditor(element,
+				$.extend({}, base.options, {
+					getHtmlHandler: base.getHtmlHandler,
+					getTextHandler: base.getTextHandler,
+					commands: mergeTextModeCommands()
+				})
+			));
+		};
+
+		mergeTextModeCommands = function() {
+			var merge = {
+				bold: { txtExec: ["[b]", "[/b]"] },
+				italic: { txtExec: ["[i]", "[/i]"] },
+				underline: { txtExec: ["[u]", "[/u]"] },
+				strike: { txtExec: ["[s]", "[/s]"] },
+				subscript: { txtExec: ["[sub]", "[/sub]"] },
+				superscript: { txtExec: ["[sup]", "[/sup]"] },
+				left: { txtExec: ["[left]", "[/left]"] },
+				center: { txtExec: ["[center]", "[/center]"] },
+				right: { txtExec: ["[right]", "[/right]"] },
+				justify: { txtExec: ["[justify]", "[/justify]"] },
+				ftp: { txtExec: ["[ftp]", "[/ftp]"] },
+				tt: { txtExec: ["[tt]", "[/tt]"] },
+				glow: { txtExec: ["[glow=red,2,300]", "[/glow]"] },
+				shadow: { txtExec: ["[shadow=red,left]", "[/shadow]"] },
+				pre: { txtExec: ["[pre]", "[/pre]"] },
+				// @todo: check tooltip
+				font: { txtExec: function(caller) {
+					var editor = this;
+
+					$.sceditor.command.get('font')._dropDown(
+						editor,
+						caller,
+						function(fontName) {
+							editor.insertText("[font="+fontName+"]", "[/font]");
+						}
+					);
+				} },
+				// @todo: check overlapping
+				size: { txtExec: function(caller) {
+					var editor = this;
+
+					$.sceditor.command.get('size')._dropDown(
+						editor,
+						caller,
+						function(fontSize) {
+							editor.insertText("[size="+fontSize+"]", "[/size]");
+						}
+					);
+				} },
+				color: { txtExec: function(caller) {
+					var editor = this;
+
+					$.sceditor.command.get('color')._dropDown(
+						editor,
+						caller,
+						function(color) {
+							editor.insertText("[color="+color+"]", "[/color]");
+						}
+					);
+				} },
+				bulletlist: { txtExec: ["[list]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
+				orderedlist: { txtExec: ["[list type=decimal]\n[li]", "[/li]\n[li][/li]\n[/list]"] },
+				table: { txtExec: ["[table]\n[tr]\n[td]", "[/td]\n[/tr]\n[/table]"] },
+				horizontalrule: { txtExec: ["[hr]"] },
+				code: { txtExec: ["[code]", "[/code]"] },
+				image: { txtExec: function(caller, selected) {
+					var url = prompt(this._("Enter the image URL:"), selected);
+
+					if(url)
+						this.insertText("[img]" + url + "[/img]");
+				} },
+				email: { txtExec: function(caller, selected) {
+					var	display = selected && selected.indexOf('@') > -1 ? null : selected,
+						email	= prompt(this._("Enter the e-mail address:"), (display ? '' : selected)),
+						text	= prompt(this._("Enter the displayed text:"), display || email) || email;
+
+					if(email)
+						this.insertText("[email=" + email + "]" + text + "[/email]");
+				} },
+				link: { txtExec: function(caller, selected) {
+					var	display = selected && selected.indexOf('http://') > -1 ? null : selected,
+						url	= prompt(this._("Enter URL:"), (display ? 'http://' : selected)),
+						text	= prompt(this._("Enter the displayed text:"), display || url) || url;
+
+					if(url)
+						this.insertText("[url=" + url + "]" + text + "[/url]");
+				} },
+				quote: { txtExec: ["[quote]", "[/quote]"] },
+				youtube: { txtExec: function(caller) {
+					var editor = this;
+
+					$.sceditor.command.get('youtube')._dropDown(
+						editor,
+						caller,
+						function(id) {
+							editor.insertText("[youtube]" + id + "[/youtube]");
+						}
+					);
+				} },
+				rtl: { txtExec: ["[rtl]", "[/rtl]"] },
+				ltr: { txtExec: ["[ltr]", "[/ltr]"] }
+			};
+
+			return $.extend(true, {}, merge, $.sceditor.commands);
+		};
+
+		/**
+		 * Populates tagsToBbcodes and stylesToBbcodes to enable faster lookups
+		 *
+		 * @private
+		 */
+		buildBbcodeCache = function() {
+			$.each(base.bbcodes, function(bbcode, info) {
+				if(typeof base.bbcodes[bbcode].tags !== "undefined")
+					$.each(base.bbcodes[bbcode].tags, function(tag, values) {
+						var isBlock = !!base.bbcodes[bbcode].isBlock;
+						tagsToBbcodes[tag] = (tagsToBbcodes[tag] || {});
+						tagsToBbcodes[tag][isBlock] = (tagsToBbcodes[tag][isBlock] || {});
+						tagsToBbcodes[tag][isBlock][bbcode] = values;
+					});
+
+				if(typeof base.bbcodes[bbcode].styles !== "undefined")
+					$.each(base.bbcodes[bbcode].styles, function(style, values) {
+						var isBlock = !!base.bbcodes[bbcode].isBlock;
+						stylesToBbcodes[isBlock] = (stylesToBbcodes[isBlock] || {});
+						stylesToBbcodes[isBlock][style] = (stylesToBbcodes[isBlock][style] || {});
+						stylesToBbcodes[isBlock][style][bbcode] = values;
+					});
+			});
+		};
+
+		getStyle = function(element, property) {
+			var	name = $.camelCase(property),
+				$elm, ret, dir;
+
+			// add exception for align
+			if("text-align" === property)
+			{
+				$elm = $(element);
+
+				if($elm.parent().css(property) !== $elm.css(property) &&
+					$elm.css('display') === "block" && !$elm.is('hr') && !$elm.is('th'))
+					ret = $elm.css(property);
+
+				// IE changes text-align to the same as direction so skip unless overried by user
+				dir = element.style.direction;
+				if(dir && ((/right/i.test(ret) && dir === 'rtl') || (/left/i.test(ret) && dir === 'ltr')))
+					return null;
+
+				return ret;
+			}
+
+			if(element.style)
+				return element.style[name];
+
+			return null;
+		};
+
+		isEmpty = function(element) {
+			var	childNodes = element.childNodes,
+				i = childNodes.length;
+
+			if(element.nodeValue)
+				return false;
+
+			if(childNodes.length === 0 || (childNodes.length === 1 && (/br/i.test(childNodes[0].nodeName) || isEmpty(childNodes[0]))))
+				return true;
+
+			while(i--)
+				if(!isEmpty(childNodes[i]))
+					return false;
+
+			return true;
+		};
+
+		/**
+		 * Checks if any bbcode styles match the elements styles
+		 *
+		 * @private
+		 * @return string Content with any matching bbcode tags wrapped around it.
+		 * @Private
+		 */
+		handleStyles = function(element, content, blockLevel) {
+			var	elementPropVal;
+
+			// convert blockLevel to boolean
+			blockLevel = !!blockLevel;
+
+			if(!stylesToBbcodes[blockLevel])
+				return content;
+
+			$.each(stylesToBbcodes[blockLevel], function(property, bbcodes) {
+				elementPropVal = getStyle(element[0], property);
+
+				// if the parent has the same style use that instead of this one
+				// so you dont end up with [i]parent[i]child[/i][/i]
+				if(!elementPropVal || getStyle(element.parent()[0], property) === elementPropVal)
+					return;
+
+				$.each(bbcodes, function(bbcode, values) {
+					if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0]))
+						return;
+
+					if(!values || $.inArray(elementPropVal.toString(), values) > -1) {
+						if($.isFunction(base.bbcodes[bbcode].format))
+							content = base.bbcodes[bbcode].format.call(base, element, content);
+						else
+							content = formatString(base.bbcodes[bbcode].format, content);
+					}
+				});
+			});
+
+			return content;
+		};
+
+		/**
+		 * Handles a HTML tag and finds any matching bbcodes
+		 *
+		 * @private
+		 * @param	jQuery element	element		The element to convert
+		 * @param	string			content		The Tags text content
+		 * @param	bool			blockLevel	If to convert block level tags
+		 * @return	string	Content with any matching bbcode tags wrapped around it.
+		 * @Private
+		 */
+		handleTags = function(element, content, blockLevel) {
+			var tag = element[0].nodeName.toLowerCase();
+
+			// convert blockLevel to boolean
+			blockLevel = !!blockLevel;
+
+			if(tagsToBbcodes[tag] && tagsToBbcodes[tag][blockLevel]) {
+				// loop all bbcodes for this tag
+				$.each(tagsToBbcodes[tag][blockLevel], function(bbcode, bbcodeAttribs) {
+					if(!/\S|\u00A0/.test(content) && !base.bbcodes[bbcode].allowsEmpty && isEmpty(element[0]))
+						return;
+
+					// if the bbcode requires any attributes then check this has
+					// all needed
+					if(bbcodeAttribs) {
+						var runBbcode = false;
+
+						// loop all the bbcode attribs
+						$.each(bbcodeAttribs, function(attrib, values)
+						{
+							// if the element has the bbcodes attribute and the bbcode attribute
+							// has values check one of the values matches
+							if(!element.attr(attrib) || (values && $.inArray(element.attr(attrib), values) < 0))
+								return;
+
+							// break this loop as we have matched this bbcode
+							runBbcode = true;
+							return false;
+						});
+
+						if(!runBbcode)
+							return;
+					}
+
+					if($.isFunction(base.bbcodes[bbcode].format))
+						content = base.bbcodes[bbcode].format.call(base, element, content);
+					else
+						content = formatString(base.bbcodes[bbcode].format, content);
+				});
+			}
+
+			// add newline after paragraph elements p and div (WebKit uses divs) and br tags
+			if(blockLevel && /^(br|div|p)$/.test(tag))
+			{
+				// Only treat divs/p as a newline if their last child was not a new line.
+				if(!(/^(div|p)$/i.test(tag) && element[0].lastChild && element[0].lastChild.nodeName.toLowerCase() === "br"))
+					content += "\n";
+
+				// needed for browsers that enter textnode then when return is pressed put the rest in a div, i.e.:
+				// text<div>line 2</div>
+				if("br" !== tag && !$.sceditor.dom.isInline(element[0].parentNode) && element[0].previousSibling &&
+					element[0].previousSibling.nodeType === 3) {
+					content = "\n" + content;
+				}
+			}
+
+			return content;
+		};
+
+		/**
+		 * Formats a string in the format
+		 * {0}, {1}, {2}, ect. with the params provided
+		 * @private
+		 * @return string
+		 * @Private
+		 */
+		formatString = function() {
+			var args = arguments;
+			return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
+				return typeof args[p1-0+1] !== "undefined" ?
+					args[p1-0+1] :
+					'{' + p1 + '}';
+			});
+		};
+
+		/**
+		 * Removes any leading or trailing quotes ('")
+		 *
+		 * @return string
+		 * @memberOf jQuery.sceditorBBCodePlugin.prototype
+		 */
+		base.stripQuotes = function(str) {
+			return str.replace(/^(["'])(.*?)\1$/, "$2");
+		};
+
+		/**
+		 * Converts HTML to BBCode
+		 * @param string	html	Html string, this function ignores this, it works off domBody
+		 * @param HtmlElement	domBody	Editors dom body object to convert
+		 * @return string BBCode which has been converted from HTML
+		 * @memberOf jQuery.sceditorBBCodePlugin.prototype
+		 */
+		base.getHtmlHandler = function(html, domBody) {
+			$.sceditor.dom.removeWhiteSpace(domBody[0]);
+
+			return $.trim(base.elementToBbcode(domBody));
+		};
+
+		/**
+		 * Converts a HTML dom element to BBCode starting from
+		 * the innermost element and working backwards
+		 *
+		 * @private
+		 * @param HtmlElement	element		The element to convert to BBCode
+		 * @param array			vChildren	Valid child tags allowed
+		 * @return string BBCode
+		 * @memberOf jQuery.sceditorBBCodePlugin.prototype
+		 */
+		base.elementToBbcode = function($element) {
+			return (function toBBCode(node, vChildren) {
+				var ret = '';
+
+				$.sceditor.dom.traverse(node, function(node) {
+					var	$node		= $(node),
+						curTag		= '',
+						tag		= node.nodeName.toLowerCase(),
+						vChild		= validChildren[tag],
+						isValidChild	= true;
+
+					if(typeof vChildren === 'object')
+					{
+						isValidChild = $.inArray(tag, vChildren) > -1;
+
+						// if this tag is one of the parents allowed children
+						// then set this tags allowed children to whatever it allows,
+						// otherwise set to what the parent allows
+						if(!isValidChild)
+							vChild = vChildren;
+					}
+
+					// 3 is text element
+					if(node.nodeType !== 3)
+					{
+						// skip ignored elments
+						if($node.hasClass("sceditor-ignore"))
+							return;
+
+						// don't loop inside iframes
+						if(tag !== 'iframe')
+							curTag = toBBCode(node, vChild);
+
+						if(isValidChild)
+						{
+							// code tags should skip most styles
+							if(!$node.is('code'))
+							{
+								// handle inline bbcodes
+								curTag = handleStyles($node, curTag);
+								curTag = handleTags($node, curTag);
+
+								// handle blocklevel bbcodes
+								curTag = handleStyles($node, curTag, true);
+							}
+
+							ret += handleTags($node, curTag, true);
+						}
+						else
+							ret += curTag;
+					}
+					else if(node.wholeText && (!node.previousSibling || node.previousSibling.nodeType !== 3))
+					{
+						if($(node).parents('code').length === 0)
+							ret += node.wholeText.replace(/ +/g, " ");
+						else
+							ret += node.wholeText;
+					}
+					else if(!node.wholeText)
+						ret += node.nodeValue;
+				}, false, true);
+
+				return ret;
+			}($element.get(0)));
+		};
+
+		/**
+		 * Converts BBCode to HTML
+		 *
+		 * @param {String} text
+		 * @param {Bool} isFragment
+		 * @return {String} HTML
+		 * @memberOf jQuery.sceditorBBCodePlugin.prototype
+		 */
+		base.getTextHandler = function(text, isFragment) {
+
+			var	oldText, replaceBBCodeFunc,
+// Previous				bbcodeRegex = /\[([^\[\s=]*?)(?:([\s=][^\[]*?))?\]((?:[\s\S(?!=\[\\\1)](?!\[\1))*?)\[\/(\1)\]/g,
+				bbcodeRegex = /\[([^\[\s=]+)(?:([\s=][^\[\]]+))?\]((?:[\s\S](?!\[\1))*?)\[\/(\1)\]/g,
+				atribsRegex = /(\S+)=((?:(?:(["'])(?:\\\3|[^\3])*?\3))|(?:[^'"\s]+))/g;
+
+			replaceBBCodeFunc = function(str, bbcode, attrs, content)
+			{
+				var	attrsMap = {},
+					matches;
+
+				bbcode = bbcode.toLowerCase();
+
+				if(attrs)
+				{
+					attrs = $.trim(attrs);
+
+					// if only one attribute then remove the = from the start and strip any quotes
+					if((attrs.charAt(0) === "=" && (attrs.split("=").length - 1) <= 1) || bbcode === 'url')
+						attrsMap.defaultattr = base.stripQuotes(attrs.substr(1));
+					else
+					{
+						if(attrs.charAt(0) === "=")
+							attrs = "defaultattr" + attrs;
+
+						if (typeof base.bbcodes[bbcode].attrs == 'function')
+						{
+							var declaredAttrs = base.bbcodes[bbcode].attrs();
+							var attrArray = new Array;
+							var compatArray = new Array;
+							for (var i = 0; i < declaredAttrs.length; i++)
+							{
+								var attrPos = attrs.indexOf(declaredAttrs[i]);
+								if (attrPos != -1)
+								{
+									attrArray[attrPos] = [declaredAttrs[i], attrPos + declaredAttrs[i].length + 1];
+								}
+							}
+
+							for (var attrElem in attrArray)
+								compatArray.push(attrArray[attrElem]);
+							for (var i = 0; i < compatArray.length; i++)
+							{
+								if (typeof compatArray[i+1] != 'undefined')
+									attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.indexOf(compatArray[i+1][0]) - compatArray[i][1]).trim();
+								else
+									attrsMap[compatArray[i][0].toLowerCase()] = attrs.substr(compatArray[i][1], attrs.length);
+							}
+						}
+						else
+							while((matches = atribsRegex.exec(attrs)))
+								attrsMap[matches[1].toLowerCase()] = base.stripQuotes(matches[2]);
+					}
+				}
+
+				if(!base.bbcodes[bbcode])
+					return str;
+
+				if($.isFunction(base.bbcodes[bbcode].html))
+					return base.bbcodes[bbcode].html.call(base, bbcode, attrsMap, content);
+				else
+					return formatString(base.bbcodes[bbcode].html, content);
+			};
+
+			text = text.replace(/&/g, "&amp;")
+					.replace(/</g, "&lt;")
+					.replace(/>/g, "&gt;")
+					.replace(/\r/g, "")
+					.replace(/(\[\/?(?:left|center|right|justify|align|rtl|ltr)\])\n/g, "$1")
+					.replace(/\n/g, "<br />");
+
+			while(text !== oldText)
+			{
+				oldText = text;
+				text    = text.replace(bbcodeRegex, replaceBBCodeFunc);
+			}
+
+			// As hr is the only bbcode not to have a start and end tag it's
+			// just being replace here instead of adding support for it above.
+			text = text.replace(/\[hr\]/gi, "<hr>")
+					.replace(/\[\*\]/gi, "<li>");
+
+			// replace multi-spaces which are not inside tags with a non-breaking space
+			// to preserve them. Otherwise they will just be converted to 1!
+			text = text.replace(/ {2}(?=([^<\>]*?<|[^<\>]*?$))/g, " &nbsp;");
+
+			return wrapInDivs(text, isFragment);
+		};
+
+		/**
+		 * Wraps divs around inline HTML. Needed for IE
+		 *
+		 * @param string html
+		 * @return string HTML
+		 * @private
+		 */
+		wrapInDivs = function(html, excludeFirstLast)
+		{
+			var	d		= document,
+				inlineFrag	= d.createDocumentFragment(),
+				outputDiv	= d.createElement('div'),
+				tmpDiv		= d.createElement('div'),
+				div, node, next, nodeName;
+
+			$(tmpDiv).hide().appendTo(d.body);
+			tmpDiv.innerHTML = html;
+
+			node = tmpDiv.firstChild;
+			while(node)
+			{
+				next = node.nextSibling;
+				nodeName = node.nodeName.toLowerCase();
+
+				if((node.nodeType === 1 && !$.sceditor.dom.isInline(node)) || nodeName === "br")
+				{
+					if(inlineFrag.childNodes.length > 0 || nodeName === "br")
+					{
+						div = d.createElement('div');
+						div.appendChild(inlineFrag);
+
+						// Putting BR in a div in IE9 causes it to do a double line break,
+						// as much as I hate browser UA sniffing, to do feature detection would
+						// be more code than it's worth for this specific bug.
+						if(nodeName === "br" && !$.sceditor.ie)
+							div.appendChild(d.createElement('br'));
+
+						// If it's an empty DIV and in compatibility mode is below IE8 then
+						// we must add a non-breaking space to the div otherwise the div
+						// will be collapsed. Adding a BR works but when you press enter
+						// to make a newline it suddenly gose back to the normal IE div
+						// behaviour and creates two lines, one for the newline and one
+						// for the BR. I'm sure there must be a better fix but I've yet to
+						// find one.
+						// Cannot do zoom: 1; or set a height on the div to fix it as that
+						// causes resize handles to be added to the div when it's clicked on/
+						if(!div.childNodes.length && (d.documentMode && d.documentMode < 8 || $.sceditor.ie < 8))
+							div.appendChild(d.createTextNode('\u00a0'));
+
+						outputDiv.appendChild(div);
+						inlineFrag = d.createDocumentFragment();
+					}
+
+					if(nodeName !== "br")
+						outputDiv.appendChild(node);
+				}
+				else
+					inlineFrag.appendChild(node);
+
+				node = next;
+			}
+
+			if(inlineFrag.childNodes.length > 0)
+			{
+				div = d.createElement('div');
+				div.appendChild(inlineFrag);
+				outputDiv.appendChild(div);
+			}
+
+			// needed for paste, the first shouldn't be wrapped in a div
+			if(excludeFirstLast)
+			{
+				node = outputDiv.firstChild;
+				if(node && node.nodeName.toLowerCase() === "div")
+				{
+					while((next = node.firstChild))
+						outputDiv.insertBefore(next, node);
+
+					if($.sceditor.ie >= 9)
+						outputDiv.insertBefore(d.createElement('br'), node);
+
+					outputDiv.removeChild(node);
+				}
+
+				node = outputDiv.lastChild;
+				if(node && node.nodeName.toLowerCase() === "div")
+				{
+					while((next = node.firstChild))
+						outputDiv.insertBefore(next, node);
+
+					if($.sceditor.ie >= 9)
+						outputDiv.insertBefore(d.createElement('br'), node);
+
+					outputDiv.removeChild(node);
+				}
+			}
+
+			$(tmpDiv).remove();
+			return outputDiv.innerHTML;
+		};
+
+		init();
+	};
+
+	$.sceditorBBCodePlugin.bbcodes = {
+		// START_COMMAND: Bold
+		b: {
+			tags: {
+				b: null,
+				strong: null
+			},
+			styles: {
+				// 401 is for FF 3.5
+				"font-weight": ["bold", "bolder", "401", "700", "800", "900"]
+			},
+			format: "[b]{0}[/b]",
+			html: '<strong>{0}</strong>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Italic
+		i: {
+			tags: {
+				i: null,
+				em: null
+			},
+			styles: {
+				"font-style": ["italic", "oblique"]
+			},
+			format: "[i]{0}[/i]",
+			html: '<em>{0}</em>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Underline
+		u: {
+			tags: {
+				u: null
+			},
+			styles: {
+				"text-decoration": ["underline"]
+			},
+			format: "[u]{0}[/u]",
+			html: '<u>{0}</u>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Strikethrough
+		s: {
+			tags: {
+				s: null,
+				strike: null
+			},
+			styles: {
+				"text-decoration": ["line-through"]
+			},
+			format: "[s]{0}[/s]",
+			html: '<s>{0}</s>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Subscript
+		sub: {
+			tags: {
+				sub: null
+			},
+			format: "[sub]{0}[/sub]",
+			html: '<sub>{0}</sub>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Superscript
+		sup: {
+			tags: {
+				sup: null
+			},
+			format: "[sup]{0}[/sup]",
+			html: '<sup>{0}</sup>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Font
+		font: {
+			tags: {
+				font: {
+					face: null
+				}
+			},
+			styles: {
+				"font-family": null
+			},
+			format: function(element, content) {
+				if(element[0].nodeName.toLowerCase() === "font" && element.attr('face'))
+					return '[font=' + this.stripQuotes(element.attr('face')) + ']' + content + '[/font]';
+
+				return '[font=' + this.stripQuotes(element.css('font-family')) + ']' + content + '[/font]';
+			},
+			html: function(element, attrs, content) {
+				return '<font face="' + attrs.defaultattr + '">' + content + '</font>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Size
+		size: {
+			tags: {
+				font: {
+					size: null
+				}
+			},
+			styles: {
+				"font-size": null
+			},
+			format: function(element, content) {
+				var	fontSize = element.css('fontSize'),
+					size     = 1;
+
+				if(element.attr('size'))
+					size = element.attr('size');
+				// Most browsers return px value but IE returns 1-7
+				else if(fontSize.indexOf("px") > -1) {
+					// convert size to an int
+					fontSize = fontSize.replace("px", "") - 0;
+
+					if(fontSize > 12)
+						size = 2;
+					if(fontSize > 15)
+						size = 3;
+					if(fontSize > 17)
+						size = 4;
+					if(fontSize > 23)
+						size = 5;
+					if(fontSize > 31)
+						size = 6;
+					if(fontSize > 47)
+						size = 7;
+				}
+				else
+					size = fontSize;
+
+				return '[size=' + size + ']' + content + '[/size]';
+			},
+			html: function(element, attrs, content) {
+				return '<font size="' + attrs.defaultattr + '">' + content + '</font>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Color
+		color: {
+			tags: {
+				font: {
+					color: null
+				}
+			},
+			styles: {
+				color: null
+			},
+			format: function(element, content) {
+				/**
+				 * Converts CSS rgb value into hex
+				 * @private
+				 * @return string Hex color
+				 */
+				var rgbToHex = function(rgbStr) {
+					var m;
+
+					function toHex(n) {
+						n = parseInt(n,10);
+						if(isNaN(n))
+							return "00";
+						n = Math.max(0,Math.min(n,255)).toString(16);
+
+						return n.length<2 ? '0'+n : n;
+					}
+
+					// rgb(n,n,n);
+					if((m = rgbStr.match(/rgb\((\d+),\s*?(\d+),\s*?(\d+)\)/i)))
+						return '#' + toHex(m[1]) + toHex(m[2]-0) + toHex(m[3]-0);
+
+					// expand shorthand
+					if((m = rgbStr.match(/#([0-f])([0-f])([0-f])\s*?$/i)))
+						return '#' + m[1] + m[1] + m[2] + m[2] + m[3] + m[3];
+
+					return rgbStr;
+				};
+
+				var color = element.css('color');
+
+				if(element[0].nodeName.toLowerCase() === "font" && element.attr('color'))
+					color = element.attr('color');
+
+				color = rgbToHex(color);
+
+				return '[color=' + color + ']' + content + '[/color]';
+			},
+			html: function(element, attrs, content) {
+				return '<font color="' + attrs.defaultattr + '">' + content + '</font>';
+			}
+		},
+		black: {
+			html: '<font color="black">{0}</font>'
+		},
+		blue: {
+			html: '<font color="blue">{0}</font>'
+		},
+		green: {
+			html: '<font color="green">{0}</font>'
+		},
+		red: {
+			html: '<font color="red">{0}</font>'
+		},
+		white: {
+			html: '<font color="white">{0}</font>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Lists
+		list: {
+			isBlock: true,
+			html: function(element, attrs, content) {
+				var style = '';
+				var code = 'ul';
+				if (attrs.type)
+						style = ' style="list-style-type: ' + attrs.type + '"';
+
+				return '<' + code + style + '>' + content + '</' + code + '>';
+			}
+		},
+		ul: {
+			tags: {
+				ul: null
+			},
+			isBlock: true,
+			format: function(element, content) {
+				if ($(element[0]).css('list-style-type') == 'disc')
+					return '[list]' + content + '[/list]';
+				else
+					return '[list type=' + $(element[0]).css('list-style-type') + ']' + content + '[/list]';
+			},
+			html: '<ul>{0}</ul>'
+		},
+		ol: {
+			tags: {
+				ol: null
+			},
+			isBlock: true,
+			format: '[list type=decimal]{0}[/list]',
+			html: '<ol>{0}</ol>'
+		},
+		li: {
+			tags: {
+				li: null
+			},
+			format: "[li]{0}[/li]",
+			html: '<li>{0}</li>'
+		},
+		"*": {
+			html: '<li>{0}</li>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Table
+		table: {
+			tags: {
+				table: null
+			},
+			format: "[table]{0}[/table]",
+			html: '<table>{0}</table>'
+		},
+		tr: {
+			tags: {
+				tr: null
+			},
+			format: "[tr]{0}[/tr]",
+			html: '<tr>{0}</tr>'
+		},
+		th: {
+			tags: {
+				th: null
+			},
+			isBlock: true,
+			format: "[th]{0}[/th]",
+			html: '<th>{0}</th>'
+		},
+		td: {
+			tags: {
+				td: null
+			},
+			isBlock: true,
+			format: "[td]{0}[/td]",
+			html: '<td>{0}<br class="sceditor-ignore" /></td>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Emoticons
+		emoticon: {
+			allowsEmpty: true,
+			tags: {
+				img: {
+					src: null,
+					"data-sceditor-emoticon": null
+				}
+			},
+			format: function(element, content) {
+				if (element.attr('data-sceditor-emoticon') == '')
+					return content;
+				return element.attr('data-sceditor-emoticon') + content;
+			},
+			html: '{0}'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Horizontal Rule
+		horizontalrule: {
+			allowsEmpty: true,
+			tags: {
+				hr: null
+			},
+			format: "[hr]{0}",
+			html: "<hr />"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Image
+		img: {
+			allowsEmpty: true,
+			tags: {
+				img: {
+					src: null
+				}
+			},
+			format: function(element, content) {
+				var	attribs = '',
+					style = function(name) {
+						return element.style ? element.style[name] : null;
+					};
+
+				// check if this is an emoticon image
+				if(typeof element.attr('data-sceditor-emoticon') !== "undefined")
+					return content;
+
+				var width = ' width=' + $(element).width();
+				var height = ' height=' + $(element).height();
+				var alt = $(element).attr('alt') != undefined ? ' alt=' + $(element).attr('author').php_unhtmlspecialchars() : '';
+
+				return '[img' + width + height + alt + ']' + element.attr('src') + '[/img]';
+			},
+			attrs: function () {
+				return ['alt', 'width', 'height'];
+			},
+			html: function(element, attrs, content) {
+				var attribs = "", parts;
+
+				// handle [img width=340 height=240]url[/img]
+				if(typeof attrs.width !== "undefined")
+					attribs += ' width="' + attrs.width + '"';
+				if(typeof attrs.height !== "undefined")
+					attribs += ' height="' + attrs.height + '"';
+				if(typeof attrs.alt !== "undefined")
+					attribs += ' alt="' + attrs.alt + '"';
+
+				return '<img ' + attribs + ' src="' + content + '" />';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: URL
+		url: {
+			allowsEmpty: true,
+			tags: {
+				a: {
+					href: null
+				}
+			},
+			format: function(element, content) {
+				// make sure this link is not an e-mail, if it is return e-mail BBCode
+				if(element.attr('href').substr(0, 7) === 'mailto:')
+					return '[email=' + element.attr('href').substr(7) + ']' + content + '[/email]';
+
+				if(element.attr('target') !== undefined)
+					return '[url=' + decodeURI(element.attr('href')) + ']' + content + '[/url]';
+				else
+					return '[iurl=' + decodeURI(element.attr('href')) + ']' + content + '[/iurl]';
+			},
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					attrs.defaultAttr = content;
+
+				return '<a trget="_blank" href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
+			}
+		},
+		iurl: {
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					attrs.defaultAttr = content;
+
+				return '<a href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
+			}
+		},
+		ftp: {
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					attrs.defaultAttr = content;
+
+				return '<a trget="_blank" href="' + encodeURI(attrs.defaultAttr) + '">' + content + '</a>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: E-mail
+		email: {
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultattr === "undefined")
+					attrs.defaultattr = content;
+
+				return '<a href="mailto:' + attrs.defaultattr + '">' + content + '</a>';
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Quote
+		quote: {
+			tags: {
+				blockquote: null
+			},
+			isBlock: true,
+			format: function(element, content) {
+				var author = '';
+				var date = '';
+				var link = '';
+
+				if ($(element).children("cite:first").length === 1)
+				{
+					author = $(element).children("cite:first").find("author").text();
+					date = $(element).children("cite:first").find("date").attr('timestamp');
+					link = $(element).children("cite:first").find("quote_link").text();
+
+					$(element).attr({'author': author.php_htmlspecialchars(), 'date': date, 'link': link});
+					if (author != '')
+						author = ' author=' + author;
+					if (date != '')
+						date = ' date=' + date;
+					if (link != '')
+						link = ' link=' + link;
+
+					content = '';
+					$(element).children("cite:first").remove();
+
+					var preserve_newline = $(element).children().last().is("br") && $(element).children().last().last().is("br");
+					content = this.elementToBbcode($(element)) + (preserve_newline ? "\n" : '');
+				}
+				else
+				{
+					if ($(element).attr('author') != undefined)
+						author = ' author=' + $(element).attr('author').php_unhtmlspecialchars();
+					if ($(element).attr('date') != undefined)
+						date = ' date=' + $(element).attr('date');
+					if ($(element).attr('link') != undefined)
+						link = ' link=' + $(element).attr('link');
+				}
+
+				return '[quote' + author + date + link + ']' + content + '[/quote]';
+			},
+			attrs: function () {
+				return ['author', 'date', 'link'];
+			},
+			html: function(element, attrs, content) {
+				var author = '';
+				var sDate = '';
+				var link = '';
+				if(typeof attrs.author !== "undefined")
+					author = bbc_quote_from + ': <author>' + attrs.author + '</author>';
+
+				// Links could be in the form: link=topic=71.msg201#msg201 that would fool javascript, so we need a workaround
+				// Probably no more necessary
+				for (var key in attrs)
+				{
+					if (key.substr(0, 4) == 'link' && attrs.hasOwnProperty(key))
+					{
+						var possible_url = key.length > 4 ? key.substr(5) + '=' + attrs[key] : attrs[key];
+
+						link = possible_url.substr(0, 7) == 'http://' ? possible_url : smf_scripturl + '?' + possible_url;
+						author = author == '' ? '<a href="' + link + '">' + bbc_quote_from + ': <author src=">' + link + '</author></a>' : '<a href="' + link + '">' + author + '</a>';
+						link = '<quote_link style="display:none">' + possible_url + '</quote_link>';
+					}
+				}
+
+				if(typeof attrs.date !== "undefined")
+				{
+					var date = new Date(attrs.date * 1000);
+					sDate = date;
+				}
+
+				if (author == '' && sDate == '')
+					author = bbc_quote;
+				else
+					author += ' ';
+
+				content = '<blockquote><cite>' + author + bbc_search_on + ' ' + '<date timestamp="' + attrs.date + '">' + sDate + '</date>' + link + '</cite>' + content + '</blockquote>';
+
+				return content;
+			}
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Code
+		code: {
+			tags: {
+				code: null
+			},
+			isBlock: true,
+			format: function(element, content) {
+				if ($(element[0]).hasClass('php'))
+					return '[php]' + content.replace('&#91;', '[') + '[/php]';
+
+				var from = '';
+				if ($(element).children("cite:first").length === 1)
+				{
+					from = $(element).children("cite:first").text();
+
+					$(element).attr({'from': from.php_htmlspecialchars()});
+
+					from = '=' + from;
+					content = '';
+					$(element).children("cite:first").remove();
+					content = this.elementToBbcode($(element));
+				}
+				else
+				{
+					if ($(element).attr('from') != undefined)
+					{
+						from = '=' + $(element).attr('from').php_unhtmlspecialchars();
+					}
+				}
+
+				return '[code' + from + ']' + content.replace('&#91;', '[') + '[/code]';
+			},
+			html:  function(element, attrs, content) {
+				var from = '';
+				if(typeof attrs.defaultAttr !== "undefined")
+					from = '<cite>' + attrs.defaultAttr + '</cite>';
+
+				return '<code>' + from + content.replace('[', '&#91;') + '</code>'
+			}
+		},
+		php: {
+			isBlock: true,
+			format: "[php]{0}[/php]",
+			html: '<code class="php">{0}</code>'
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Left
+		left: {
+			styles: {
+				"text-align": ["left", "-webkit-left", "-moz-left", "-khtml-left"]
+			},
+			isBlock: true,
+			format: "[left]{0}[/left]",
+			html: '<div align="left">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Centre
+		center: {
+			styles: {
+				"text-align": ["center", "-webkit-center", "-moz-center", "-khtml-center"]
+			},
+			isBlock: true,
+			format: "[center]{0}[/center]",
+			html: '<div align="center">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Right
+		right: {
+			styles: {
+				"text-align": ["right", "-webkit-right", "-moz-right", "-khtml-right"]
+			},
+			isBlock: true,
+			format: "[right]{0}[/right]",
+			html: '<div align="right">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Justify
+		justify: {
+			styles: {
+				"text-align": ["justify", "-webkit-justify", "-moz-justify", "-khtml-justify"]
+			},
+			isBlock: true,
+			format: "[justify]{0}[/justify]",
+			html: '<div align="justify">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: YouTube
+		youtube: {
+			allowsEmpty: true,
+			tags: {
+				iframe: {
+					'data-youtube-id': null
+				}
+			},
+			format: function(element, content) {
+				if(!element.attr('data-youtube-id'))
+					return content;
+
+				return '[youtube]' + element.attr('data-youtube-id') + '[/youtube]';
+			},
+			html: '<iframe width="560" height="315" src="http://www.youtube.com/embed/{0}?wmode=opaque' +
+				'" data-youtube-id="{0}" frameborder="0" allowfullscreen></iframe>'
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Rtl
+		rtl: {
+			styles: {
+				"direction": ["rtl"]
+			},
+			format: "[rtl]{0}[/rtl]",
+			html: '<div style="direction: rtl">{0}</div>'
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Ltr
+		ltr: {
+			styles: {
+				"direction": ["ltr"]
+			},
+			format: "[ltr]{0}[/ltr]",
+			html: '<div style="direction: ltr">{0}</div>'
+		},
+		// END_COMMAND
+
+		abbr: {
+			tags: {
+				abbr: {
+					title: null
+				}
+			},
+			format: function(element, content) {
+				return '[abbr=' + element.attr('title') + ']' + content + '[/abbr]';
+			},
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					return content;
+
+				return '<abbr title="' + attrs.defaultAttr + '">' + content + '</abbr>';
+			}
+		},
+		acronym: {
+			tags: {
+				acronym: {
+					title: null
+				}
+			},
+			format: function(element, content) {
+				return '[acronym=' + element.attr('title') + ']' + content + '[/acronym]';
+			},
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					return content;
+
+				return '<acronym title="' + attrs.defaultAttr + '">' + content + '</acronym>';
+			}
+		},
+		bdo: {
+			tags: {
+				bdo: {
+					dir: null
+				}
+			},
+			format: function(element, content) {
+				return '[bdo=' + element.attr('dir') + ']' + content + '[/bdo]';
+			},
+			html: function(element, attrs, content) {
+				if(typeof attrs.defaultAttr === "undefined" || attrs.defaultAttr.length === 0)
+					return content;
+				if (attrs.defaultAttr != 'rtl' && attrs.defaultAttr != 'ltr')
+					return '[bdo=' + attrs.defaultAttr + ']' + content + '[/bdo]';
+
+				return '<bdo dir="' + attrs.defaultAttr + '">' + content + '</bdo>';
+			}
+		},
+		tt: {
+			tags: {
+				tt: null
+			},
+			format: "[tt]{0}[/tt]",
+			html: '<tt>{0}</tt>'
+		},
+		pre: {
+			tags: {
+				pre: null
+			},
+			isBlock: true,
+			format: "[pre]{0}[/pre]",
+			html: "<pre>{0}</pre>\n"
+		},
+		move: {
+			tags: {
+				marquee: null
+			},
+			format: "[move]{0}[/move]",
+			html: '<marquee>{0}</marquee>'
+		},
+
+		// this is here so that commands above can be removed
+		// without having to remove the , after the last one.
+		// Needed for IE.
+		ignore: {}
+	};
+
+	/**
+	 * Static BBCode helper class
+	 * @class command
+	 * @name jQuery.sceditorBBCodePlugin.bbcode
+	 */
+	$.sceditorBBCodePlugin.bbcode =
+	/** @lends jQuery.sceditorBBCodePlugin.bbcode */
+	{
+		/**
+		 * Gets a BBCode
+		 *
+		 * @param {String} name
+		 * @return {Object|null}
+		 * @since v1.3.5
+		 */
+		get: function(name) {
+			return $.sceditorBBCodePlugin.bbcodes[name] || null;
+		},
+
+		/**
+		 * <p>Adds a BBCode to the parser or updates an exisiting
+		 * BBCode if a BBCode with the specified name already exists.</p>
+		 *
+		 * @param {String} name
+		 * @param {Object} bbcode
+		 * @return {this|false} Returns false if name or bbcode is false
+		 * @since v1.3.5
+		 */
+		set: function(name, bbcode) {
+			if(!name || !bbcode)
+				return false;
+
+			// merge any existing command properties
+			bbcode = $.extend($.sceditorBBCodePlugin.bbcodes[name] || {}, bbcode);
+
+			bbcode.remove = function() { $.sceditorBBCodePlugin.bbcode.remove(name); };
+
+			$.sceditorBBCodePlugin.bbcodes[name] = bbcode;
+			return this;
+		},
+
+		/**
+		 * Removes a BBCode
+		 *
+		 * @param {String} name
+		 * @return {this}
+		 * @since v1.3.5
+		 */
+		remove: function(name) {
+			if($.sceditorBBCodePlugin.bbcodes[name])
+				delete $.sceditorBBCodePlugin.bbcodes[name];
+
+			return this;
+		}
+	};
+
+	/**
+	 * Checks if a command with the specified name exists
+	 *
+	 * @param string name
+	 * @return bool
+	 * @deprecated Since v1.3.5
+	 * @memberOf jQuery.sceditorBBCodePlugin
+	 */
+	$.sceditorBBCodePlugin.commandExists = function(name) {
+		return !!$.sceditorBBCodePlugin.bbcode.get(name);
+	};
+
+	/**
+	 * Adds/updates a BBCode.
+	 *
+	 * @param String		name		The BBCode name
+	 * @param Object		tags		Any html tags this bbcode applies to, i.e. strong for [b]
+	 * @param Object		styles		Any style properties this applies to, i.e. font-weight for [b]
+	 * @param String|Function	format		Function or string to convert the element into BBCode
+	 * @param String|Function	html		String or function to format the BBCode back into HTML.
+	 * @param bool			allowsEmpty	If this BBCodes is allowed to be empty, e.g. [b][/b]
+	 * @return Bool
+	 * @deprecated Since v1.3.5
+	 * @memberOf jQuery.sceditorBBCodePlugin
+	 */
+	$.sceditorBBCodePlugin.setCommand = function(name, tags, styles, format, html, allowsEmpty, isBlock) {
+		return $.sceditorBBCodePlugin.bbcode.set(name,
+		{
+			tags: tags || {},
+			styles: styles || {},
+			allowsEmpty: allowsEmpty,
+			isBlock: isBlock,
+			format: format,
+			html: html
+		});
+	};
+
+	$.fn.sceditorBBCodePlugin = function(options) {
+		if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported())
+			return;
+
+		return this.each(function() {
+			(new $.sceditorBBCodePlugin(this, options));
+		});
+	};
+})(jQuery);

+ 3728 - 0
Themes/default/scripts/jquery.sceditor.js

@@ -0,0 +1,3728 @@
+/**
+ * SCEditor
+ * http://www.samclarke.com/2011/07/sceditor/
+ *
+ * Copyright (C) 2011-2012, Sam Clarke (samclarke.com)
+ *
+ * SCEditor is licensed under the MIT license:
+ *	http://www.opensource.org/licenses/mit-license.php
+ *
+ * @fileoverview SCEditor - A lightweight WYSIWYG BBCode and HTML editor
+ * @author Sam Clarke
+ * @version 1.3.7
+ * @requires jQuery
+ */
+
+// ==ClosureCompiler==
+// @output_file_name jquery.sceditor.min.js
+// @compilation_level SIMPLE_OPTIMIZATIONS
+// ==/ClosureCompiler==
+
+/*jshint smarttabs: true, scripturl: true, jquery: true, devel:true, eqnull:true, curly: false */
+/*global XMLSerializer: true*/
+
+;(function ($, window, document) {
+	'use strict';
+
+	var _templates = {
+		html:		'<!DOCTYPE html>' +
+				'<html>' +
+					'<head>' +
+						'<!--[if IE]><style>* {min-height: auto !important}</style><![endif]-->' +
+						'<meta http-equiv="Content-Type" content="text/html;charset={charset}" />' +
+						'<link rel="stylesheet" type="text/css" href="{style}" />' +
+					'</head>' +
+					'<body contenteditable="true"></body>' +
+				'</html>',
+
+		toolbarButton:	'<a class="sceditor-button sceditor-button-{name}" data-sceditor-command="{name}" unselectable="on"><div unselectable="on">{dispName}</div></a>',
+
+		emoticon:	'<img src="{url}" data-sceditor-emoticon="{key}" alt="{key}" />',
+
+		fontOpt:	'<a class="sceditor-font-option" href="#" data-font="{font}"><font face="{font}">{font}</font></a>',
+
+		sizeOpt:	'<a class="sceditor-fontsize-option" data-size="{size}" style="line-height:{points}pt" href="#"><font size="{size}">{size}</font></a>',
+
+		pastetext:	'<div><label for="txt">{label}</label> ' +
+				'<textarea cols="20" rows="7" id="txt"></textarea></div>' +
+				'<div><input type="button" class="button" value="{insert}" /></div>',
+
+		table:		'<div><label for="rows">{rows}</label><input type="text" id="rows" value="2" /></div>' +
+				'<div><label for="cols">{cols}</label><input type="text" id="cols" value="2" /></div>' +
+				'<div><input type="button" class="button" value="{insert}" /></div>',
+
+		image:		'<div><label for="link">{url}</label> <input type="text" id="image" value="http://" /></div>' +
+				'<div><label for="width">{width}</label> <input type="text" id="width" size="2" /></div>' +
+				'<div><label for="height">{height}</label> <input type="text" id="height" size="2" /></div>' +
+				'<div><input type="button" class="button" value="{insert}" /></div>',
+
+		email:		'<div><label for="email">{label}</label> <input type="text" id="email" /></div>' +
+				'<div><input type="button" class="button" value="{insert}" /></div>',
+
+		link:		'<div><label for="link">{url}</label> <input type="text" id="link" value="http://" /></div>' +
+				'<div><label for="des">{desc}</label> <input type="text" id="des" /></div>' +
+				'<div><input type="button" class="button" value="{ins}" /></div>',
+
+		youtubeMenu:	'<div><label for="link">{label}</label> <input type="text" id="link" value="http://" /></div><div><input type="button" class="button" value="{insert}" /></div>',
+
+		youtube:	'<iframe width="560" height="315" src="http://www.youtube.com/embed/{id}?wmode=opaque" data-youtube-id="{id}" frameborder="0" allowfullscreen></iframe>'
+	};
+
+	/**
+	 * <p>Replaces any params in a template with the passed params.</p>
+	 *
+	 * <p>If createHTML is passed it will use jQuery to create the HTML. The
+	 * same as doing: $(editor.tmpl("html", {params...}));</p>
+	 *
+	 * @param {string} templateName
+	 * @param {Object} params
+	 * @param {Boolean} createHTML
+	 * @private
+	 */
+	var _tmpl = function(name, params, createHTML) {
+		var template = _templates[name];
+
+		$.each(params, function(name, val) {
+			template = template.replace(new RegExp('\\{' + name + '\\}', 'g'), val);
+		});
+
+		if(createHTML)
+			template = $(template);
+
+		return template;
+	};
+
+	/**
+	 * SCEditor - A lightweight WYSIWYG editor
+	 *
+	 * @param {Element} el The textarea to be converted
+	 * @return {Object} options
+	 * @class sceditor
+	 * @name jQuery.sceditor
+	 */
+	$.sceditor = function (el, options) {
+		/**
+		 * Alias of this
+		 * @private
+		 */
+		var base = this;
+
+		/**
+		 * The textarea element being replaced
+		 * @private
+		 */
+		var $textarea = $(el);
+		var textarea  = el;
+
+		/**
+		 * The div which contains the editor and toolbar
+		 * @private
+		 */
+		var $editorContainer;
+
+		/**
+		 * The editors toolbar
+		 * @private
+		 */
+		var $toolbar;
+
+		/**
+		 * The editors iframe which should be in design mode
+		 * @private
+		 */
+		var $wysiwygEditor;
+		var wysiwygEditor;
+
+		/**
+		 * The editors textarea for viewing source
+		 * @private
+		 */
+		var $textEditor;
+		var textEditor;
+
+		/**
+		 * The current dropdown
+		 * @private
+		 */
+		var $dropdown;
+
+		/**
+		 * Array of all the commands key press functions
+		 * @private
+		 */
+		var keyPressFuncs = [];
+
+		/**
+		 * Store the last cursor position. Needed for IE because it forgets
+		 * @private
+		 */
+		var lastRange;
+
+		/**
+		 * The editors locale
+		 * @private
+		 */
+		var locale;
+
+		/**
+		 * Stores a cache of preloaded images
+		 * @private
+		 */
+		var preLoadCache = [];
+
+		var rangeHelper;
+
+		var $blurElm;
+
+		var	init,
+			replaceEmoticons,
+			handleCommand,
+			saveRange,
+			handlePasteEvt,
+			handlePasteData,
+			handleKeyPress,
+			handleFormReset,
+			handleMouseDown,
+			initEditor,
+			initToolBar,
+			initKeyPressFuncs,
+			initResize,
+			documentClickHandler,
+			formSubmitHandler,
+			initEmoticons,
+			getWysiwygDoc,
+			handleWindowResize,
+			initLocale,
+			updateToolBar,
+			textEditorSelectedText,
+			autofocus;
+
+		/**
+		 * All the commands supported by the editor
+		 */
+		base.commands = $.extend({}, (options.commands || $.sceditor.commands));
+
+		/**
+		 * Initializer. Creates the editor iframe and textarea
+		 * @private
+		 * @name sceditor.init
+		 */
+		init = function () {
+			$textarea.data("sceditor", base);
+			base.options = $.extend({}, $.sceditor.defaultOptions, options);
+
+			// Load locale
+			if(base.options.locale && base.options.locale !== "en")
+				initLocale();
+
+			// if either width or height are % based, add the resize handler to update the editor
+			// when the window is resized
+			var h = base.options.height, w = base.options.width;
+			if((h && (h + "").indexOf("%") > -1) || (w && (w + "").indexOf("%") > -1))
+				$(window).resize(handleWindowResize);
+
+			$editorContainer = $('<div class="sceditor-container" />').insertAfter($textarea);
+
+			// create the editor
+			initToolBar();
+			initEditor();
+			initKeyPressFuncs();
+
+			if(base.options.resizeEnabled)
+				initResize();
+
+			if(base.options.id)
+				$editorContainer.attr('id', base.options.id);
+
+			$(document).click(documentClickHandler);
+			$(textarea.form)
+				.attr('novalidate','novalidate')
+				.bind("reset", handleFormReset)
+				.submit(formSubmitHandler);
+
+			// load any textarea value into the editor
+			base.val($textarea.hide().val());
+/*
+			// Pass the value though the getTextHandler if it is set so that
+			// BBCode, ect. can be converted
+			if(base.options.getTextHandler && base.options.supportedWysiwyg)
+			{
+				val = base.options.getTextHandler(val);
+
+				base.setWysiwygEditorValue(val);
+			}
+			else
+			{
+				base.toggleTextMode();
+				base.setTextareaValue(val);
+			}
+*/
+			if(base.options.autofocus)
+				autofocus();
+
+			// force into source mode if is a browser that can't handle
+			// full editing
+			if(!$.sceditor.isWysiwygSupported())
+				base.toggleTextMode();
+
+			if(base.options.toolbar.indexOf('emoticon') !== -1)
+				initEmoticons();
+
+			// Can't use load event as it gets fired before CSS is loaded
+			// in some browsers
+			if(base.options.autoExpand)
+				var interval = setInterval(function() {
+					if (!document.readyState || document.readyState === "complete") {
+						base.expandToContent();
+						clearInterval(interval);
+					}
+				}, 10);
+		};
+
+		/**
+		 * Creates the editor iframe and textarea
+		 * @private
+		 */
+		initEditor = function () {
+			var $doc, $body;
+
+			$textEditor	= $('<textarea></textarea>').hide();
+			$wysiwygEditor	= $('<iframe frameborder="0"></iframe>');
+
+			if(window.location.protocol === "https:")
+				$wysiwygEditor.attr("src", "javascript:false");
+
+			// add the editor to the HTML and store the editors element
+			$editorContainer.append($wysiwygEditor).append($textEditor);
+			wysiwygEditor	= $wysiwygEditor[0];
+			textEditor	= $textEditor[0];
+
+			base.width(base.options.width || $textarea.width());
+			base.height(base.options.height || $textarea.height());
+
+			getWysiwygDoc().open();
+			getWysiwygDoc().write(_tmpl("html", {
+				charset: base.options.charset,
+				style: base.options.style
+			}));
+			getWysiwygDoc().close();
+
+			base.readOnly(!!base.options.readOnly);
+
+			$doc	= $(getWysiwygDoc());
+			$body	= $doc.find("body");
+
+			// Add IE version class to the HTML element so can apply
+			// conditional styling without CSS hacks
+			if($.sceditor.ie)
+				$doc.find("html").addClass('ie' + $.sceditor.ie);
+
+			// iframe overflow fix
+			if(/iPhone|iPod|iPad| wosbrowser\//i.test(navigator.userAgent))
+				$body.height('100%');
+
+			// set the key press event
+			$body.keypress(handleKeyPress);
+			$doc.keypress(handleKeyPress)
+				.mousedown(handleMouseDown)
+				.bind("beforedeactivate keyup", saveRange)
+				.focus(function() {
+					lastRange = null;
+				});
+
+			if(base.options.rtl)
+			{
+				$body.attr('dir', 'rtl');
+				$textEditor.attr('dir', 'rtl');
+			}
+
+			if(base.options.enablePasteFiltering)
+				$body.bind("paste", handlePasteEvt);
+
+			if(base.options.autoExpand)
+				$doc.bind("keyup", base.expandToContent);
+
+			rangeHelper = new $.sceditor.rangeHelper(wysiwygEditor.contentWindow);
+		};
+
+		/**
+		 * Creates the toolbar and appends it to the container
+		 * @private
+		 */
+		initToolBar = function () {
+			var	$group, $button, buttons,
+				i, x, buttonClick,
+				groups = base.options.toolbar.split("|");
+
+			buttonClick = function () {
+				var self = $(this);
+
+				if(!self.hasClass('disabled'))
+					handleCommand(self, base.commands[self.data("sceditor-command")]);
+
+				return false;
+			};
+
+			$toolbar = $('<div class="sceditor-toolbar" />');
+			var rows = base.options.toolbar.split("||");
+			for (var r=0; r < rows.length; r++) {
+				var row   = $('<div class="sceditor-row" />');
+				var	groups = rows[r].split("|"),
+					buttons, accessibilityName, button, i;
+
+				for (i=0; i < groups.length; i++) {
+					$group   = $('<div class="sceditor-group" />');
+					buttons = groups[i].split(",");
+
+					for (x=0; x < buttons.length; x++) {
+						// the button must be a valid command otherwise ignore it
+						if(!base.commands[buttons[x]])
+							continue;
+
+						$button = _tmpl("toolbarButton", {
+							name: buttons[x],
+							dispName: base.commands[buttons[x]].tooltip || buttons[x]
+						}, true).click(buttonClick);
+
+						if(base.commands[buttons[x]].hasOwnProperty("tooltip"))
+							$button.attr('title', base._(base.commands[buttons[x]].tooltip));
+
+						if(base.commands[buttons[x]].exec)
+							$button.data('sceditor-wysiwygmode', true);
+						else
+							$button.addClass('disabled');
+
+						if(base.commands[buttons[x]].txtExec)
+							$button.data('sceditor-txtmode', true);
+
+						$group.append($button);
+					}
+					row.append($group);
+				}
+				$toolbar.append(row);
+			}
+
+			// append the toolbar to the toolbarContainer option if given
+			if(base.options.toolbarContainer)
+				$(base.options.toolbarContainer).append($toolbar);
+			else
+				$editorContainer.append($toolbar);
+		};
+
+		/**
+		 * Autofocus the editor
+		 * @private
+		 */
+		autofocus = function() {
+			var	doc	= wysiwygEditor.contentWindow.document,
+				body	= doc.body, rng;
+
+			if(!doc.createRange)
+				return base.focus();
+
+			if(!body.firstChild)
+				return;
+
+			rng = doc.createRange();
+			rng.setStart(body.firstChild, 0);
+			rng.setEnd(body.firstChild, 0);
+
+			rangeHelper.selectRange(rng);
+			body.focus();
+		};
+
+		/**
+		 * Gets the readOnly property of the editor
+		 *
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name readOnly
+		 * @return {boolean}
+		 */
+		/**
+		 * Sets the readOnly property of the editor
+		 *
+		 * @param {boolean} readOnly
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name readOnly^2
+		 * @return {this}
+		 */
+		base.readOnly = function(readOnly) {
+			if(typeof readOnly !== 'boolean')
+				return $textEditor.attr('readonly') === 'readonly';
+
+			getWysiwygDoc().body.contentEditable = !readOnly;
+
+			if(!readOnly)
+				$textEditor.removeAttr('readonly');
+			else
+				$textEditor.attr('readonly', 'readonly');
+
+			updateToolBar(readOnly);
+
+			return this;
+		};
+
+		/**
+		 * Updates the toolbar to disable/enable the appropriate buttons
+		 * @private
+		 */
+		updateToolBar = function(disable) {
+			$toolbar.find('.sceditor-button').removeClass('disabled');
+
+			$toolbar.find('.sceditor-button').each(function () {
+				var button = $(this);
+
+				if(disable === true)
+					button.addClass('disabled');
+				else if(base.inSourceMode() && !button.data('sceditor-txtmode'))
+					button.addClass('disabled');
+				else if (!base.inSourceMode() && !button.data('sceditor-wysiwygmode'))
+					button.addClass('disabled');
+			});
+		};
+
+		/**
+		 * Creates an array of all the key press functions
+		 * like emoticons, ect.
+		 * @private
+		 */
+		initKeyPressFuncs = function () {
+			$.each(base.commands, function (command, values) {
+				if(values.keyPress)
+					keyPressFuncs.push(values.keyPress);
+			});
+		};
+
+		/**
+		 * Gets the width of the editor in px
+		 *
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name width
+		 * @return {int}
+		 */
+		/**
+		 * Sets the width of the editor
+		 *
+		 * @param {int} width Width in px
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name width^2
+		 * @return {this}
+		 */
+		base.width = function (width) {
+			if(!width)
+				return $editorContainer.width();
+
+			$editorContainer.width(width);
+
+			// fix the height and width of the textarea/iframe
+			$wysiwygEditor.width(width);
+			$wysiwygEditor.width(width + (width - $wysiwygEditor.outerWidth(true)));
+
+			$textEditor.width(width);
+			$textEditor.width(width + (width - $textEditor.outerWidth(true)));
+
+			return this;
+		};
+
+		/**
+		 * Gets the height of the editor in px
+		 *
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name height
+		 * @return {int}
+		 */
+		/**
+		 * Sets the height of the editor
+		 *
+		 * @param {int} height Height in px
+		 * @since 1.3.5
+		 * @function
+		 * @memberOf jQuery.sceditor.prototype
+		 * @name height^2
+		 * @return {this}
+		 */
+		base.height = function (height) {
+			if(!height)
+				return $editorContainer.height();
+
+			$editorContainer.height(height);
+
+			height -= !base.options.toolbarContainer ? $toolbar.outerHeight(true) : 0;
+
+			// fix the height and width of the textarea/iframe
+			$wysiwygEditor.height(height);
+			$wysiwygEditor.height(height + (height - $wysiwygEditor.outerHeight(true)));
+
+			$textEditor.height(height);
+			$textEditor.height(height + (height - $textEditor.outerHeight(true)));
+
+			return this;
+		};
+
+		/**
+		 * Expands the editor to the size of it's content
+		 *
+		 * @since 1.3.5
+		 * @param {Boolean} [ignoreMaxHeight=false]
+		 * @function
+		 * @name expandToContent
+		 * @memberOf jQuery.sceditor.prototype
+		 * @see #resizeToContent
+		 */
+		base.expandToContent = function(ignoreMaxHeight) {
+			var	doc		= getWysiwygDoc(),
+				currentHeight	= $editorContainer.height(),
+				height		= doc.body.scrollHeight || doc.documentElement.scrollHeight,
+				padding		= (currentHeight - $wysiwygEditor.height()),
+				maxHeight	= base.options.resizeMaxHeight || ((base.options.height || $textarea.height()) * 2);
+
+			height += padding;
+
+			if(ignoreMaxHeight !== true && height > maxHeight)
+				height = maxHeight;
+
+			if(height > currentHeight)
+				base.height(height);
+		};
+
+		/**
+		 * Creates the resizer.
+		 * @private
+		 */
+		initResize = function () {
+			var	$grip		= $('<div class="sceditor-grip" />'),
+				// cover is used to cover the editor iframe so document still gets mouse move events
+				$cover		= $('<div class="sceditor-resize-cover" />'),
+				startX		= 0,
+				startY		= 0,
+				startWidth	= 0,
+				startHeight	= 0,
+				origWidth	= $editorContainer.width(),
+				origHeight	= $editorContainer.height(),
+				dragging	= false,
+				minHeight, maxHeight, minWidth, maxWidth, mouseMoveFunc;
+
+			minHeight = base.options.resizeMinHeight || origHeight / 1.5;
+			maxHeight = base.options.resizeMaxHeight || origHeight * 2.5;
+			minWidth = base.options.resizeMinWidth  || origWidth / 1.25;
+			maxWidth = base.options.resizeMaxWidth || origWidth * 1.25;
+
+			mouseMoveFunc = function (e) {
+				var	newHeight = startHeight + (e.pageY - startY),
+					newWidth  = startWidth  + (e.pageX - startX);
+
+				if (newWidth >= minWidth && (maxWidth < 0 || newWidth <= maxWidth))
+					base.width(newWidth);
+
+				if (newHeight >= minHeight && (maxHeight < 0 || newHeight <= maxHeight))
+					base.height(newHeight);
+
+				e.preventDefault();
+			};
+
+			$editorContainer.append($grip);
+			$editorContainer.append($cover.hide());
+
+			$grip.mousedown(function (e) {
+				startX		= e.pageX;
+				startY		= e.pageY;
+				startWidth	= $editorContainer.width();
+				startHeight	= $editorContainer.height();
+				dragging	= true;
+
+				$editorContainer.addClass('resizing');
+				$cover.show();
+				$(document).bind('mousemove', mouseMoveFunc);
+				e.preventDefault();
+			});
+
+			$(document).mouseup(function (e) {
+				if(!dragging)
+					return;
+
+				dragging = false;
+				$cover.hide();
+
+				$editorContainer.removeClass('resizing');
+				$(document).unbind('mousemove', mouseMoveFunc);
+				e.preventDefault();
+			});
+		};
+
+		/**
+		 * Handles the forms submit event
+		 * @private
+		 */
+		formSubmitHandler = function(e) {
+			base.updateTextareaValue();
+			$(this).removeAttr('novalidate');
+
+			if(this.checkValidity && !this.checkValidity())
+				e.preventDefault();
+
+			$(this).attr('novalidate','novalidate');
+			base.blur();
+		};
+
+		/**
+		 * Destroys the editor, removing all elements and
+		 * event handlers.
+		 *
+		 * @function
+		 * @name destory
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.destory = function () {
+			$(document).unbind('click', documentClickHandler);
+			$(window).unbind('resize', handleWindowResize);
+
+			$(textarea.form).removeAttr('novalidate')
+				.unbind('submit', formSubmitHandler)
+				.unbind("reset", handleFormReset);
+
+			$(getWysiwygDoc()).find('*').remove();
+			$(getWysiwygDoc()).unbind("keypress mousedown beforedeactivate keyup focus paste keypress");
+
+			$editorContainer.find('*').remove();
+			$editorContainer.remove();
+
+			$textarea.removeData("sceditor").removeData("sceditorbbcode").show();
+		};
+
+		/**
+		 * Preloads the emoticon images
+		 * Idea from: http://engineeredweb.com/blog/09/12/preloading-images-jquery-and-javascript
+		 * @private
+		 */
+		initEmoticons = function () {
+			// prefix emoticon root to emoticon urls
+			if(base.options.emoticonsRoot && base.options.emoticons)
+			{
+				$.each(base.options.emoticons, function (idx, emoticons) {
+					$.each(emoticons, function (key, url) {
+						base.options.emoticons[idx][key] = base.options.emoticonsRoot + url;
+					});
+				});
+			}
+
+			var	emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden),
+				emoticon;
+
+			$.each(emoticons, function (key, url) {
+				// @todo Why did I (emanuele) add this?
+				if (key == '')
+					emoticon = document.createElement('br');
+				else
+				{
+					emoticon	= document.createElement('img');
+					emoticon.src	= url;
+				}
+				preLoadCache.push(emoticon);
+			});
+		};
+
+		/**
+		 * Creates a menu item drop down
+		 *
+		 * @param HTMLElement	menuItem	The button to align the drop down with
+		 * @param string	dropDownName	Used for styling the dropown, will be a class sceditor-dropDownName
+		 * @param string	content			The HTML content of the dropdown
+		 * @param bool		ieUnselectable	If to add the unselectable attribute to all the contents elements. Stops IE from deselecting the text in the editor
+		 * @function
+		 * @name createDropDown
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.createDropDown = function (menuItem, dropDownName, content, ieUnselectable) {
+			base.closeDropDown();
+
+			// IE needs unselectable attr to stop it from unselecting the text in the editor.
+			// The editor can cope if IE does unselect the text it's just not nice.
+			if(ieUnselectable !== false) {
+				$(content).find(':not(input,textarea)')
+					.filter(function() {
+						return this.nodeType===1;
+					})
+					.attr('unselectable', 'on');
+			}
+
+			var css = {
+				top: menuItem.offset().top,
+				left: menuItem.offset().left
+			};
+
+			$.extend(css, base.options.dropDownCss);
+
+			$dropdown = $('<div class="sceditor-dropdown sceditor-' + dropDownName + '" />')
+				.css(css)
+				.append(content)
+				.appendTo($('body'))
+				.click(function (e) {
+					// stop clicks within the dropdown from being handled
+					e.stopPropagation();
+				});
+		};
+
+		/**
+		 * Handles any document click and closes the dropdown if open
+		 * @private
+		 */
+		documentClickHandler = function (e) {
+			// ignore right clicks
+			if(e.which !== 3)
+				base.closeDropDown();
+		};
+
+		handlePasteEvt = function(e) {
+			var	elm		= getWysiwygDoc().body,
+				checkCount	= 0,
+				pastearea	= elm.ownerDocument.createElement('div'),
+				prePasteContent	= elm.ownerDocument.createDocumentFragment();
+
+			rangeHelper.saveRange();
+			document.body.appendChild(pastearea);
+
+			if (e && e.clipboardData && e.clipboardData.getData)
+			{
+				var html, handled=true;
+
+				if ((html = e.clipboardData.getData('text/html')) || (html = e.clipboardData.getData('text/plain')))
+					pastearea.innerHTML = html;
+				else
+					handled = false;
+
+				if(handled)
+				{
+					handlePasteData(elm, pastearea);
+
+					if (e.preventDefault)
+					{
+						e.stopPropagation();
+						e.preventDefault();
+					}
+
+					return false;
+				}
+			}
+
+			while(elm.firstChild)
+				prePasteContent.appendChild(elm.firstChild);
+
+			function handlePaste(elm, pastearea) {
+				if (elm.childNodes.length > 0)
+				{
+					while(elm.firstChild)
+						pastearea.appendChild(elm.firstChild);
+
+					while(prePasteContent.firstChild)
+						elm.appendChild(prePasteContent.firstChild);
+
+					handlePasteData(elm, pastearea);
+				}
+				else
+				{
+					// Allow max 25 checks before giving up.
+					// Needed inscase empty input is posted or
+					// something gose wrong.
+					if(checkCount > 25)
+					{
+						while(prePasteContent.firstChild)
+							elm.appendChild(prePasteContent.firstChild);
+
+						return;
+					}
+
+					++checkCount;
+					setTimeout(function () {
+						handlePaste(elm, pastearea);
+					}, 20);
+				}
+			}
+			handlePaste(elm, pastearea);
+
+			base.focus();
+
+			return true;
+		};
+
+		/**
+		 * @param {Element} elm
+		 * @param {Element} pastearea
+		 * @private
+		 */
+		handlePasteData = function(elm, pastearea) {
+			// fix any invalid nesting
+			$.sceditor.dom.fixNesting(pastearea);
+
+			var pasteddata = pastearea.innerHTML;
+
+			if(base.options.getHtmlHandler)
+				pasteddata = base.options.getHtmlHandler(pasteddata, $(pastearea));
+
+			pastearea.parentNode.removeChild(pastearea);
+
+			if(base.options.getTextHandler)
+				pasteddata = base.options.getTextHandler(pasteddata, true);
+
+			rangeHelper.restoreRange();
+			rangeHelper.insertHTML(pasteddata);
+		};
+
+		/**
+		 * Closes the current drop down
+		 *
+		 * @param bool focus If to focus the editor on close
+		 * @function
+		 * @name closeDropDown
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.closeDropDown = function (focus) {
+			if($dropdown) {
+				$dropdown.remove();
+				$dropdown = null;
+			}
+
+			if(focus === true)
+				base.focus();
+		};
+
+		/**
+		 * Gets the WYSIWYG editors document
+		 * @private
+		 */
+		getWysiwygDoc = function () {
+			if (wysiwygEditor.contentDocument)
+				return wysiwygEditor.contentDocument;
+
+			if (wysiwygEditor.contentWindow && wysiwygEditor.contentWindow.document)
+				return wysiwygEditor.contentWindow.document;
+
+			if (wysiwygEditor.document)
+				return wysiwygEditor.document;
+
+			return null;
+		};
+
+
+		/**
+		 * <p>Inserts HTML into WYSIWYG editor.</p>
+		 *
+		 * <p>If endHtml is specified instead of the inserted HTML replacing the selected
+		 * text the selected text will be placed between html and endHtml. If there is
+		 * no selected text html and endHtml will be concated together.</p>
+		 *
+		 * @param {string} html
+		 * @param {string} [endHtml=null]
+		 * @param {boolean} [overrideCodeBlocking=false]
+		 * @function
+		 * @name wysiwygEditorInsertHtml
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.wysiwygEditorInsertHtml = function (html, endHtml, overrideCodeBlocking) {
+			base.focus();
+
+			// don't apply to code elements
+			if(!overrideCodeBlocking && ($(rangeHelper.parentNode()).is('code') ||
+				$(rangeHelper.parentNode()).parents('code').length !== 0))
+				return;
+
+			rangeHelper.insertHTML(html, endHtml);
+		};
+
+		/**
+		 * Like wysiwygEditorInsertHtml except it will convert any HTML into text
+		 * before inserting it.
+		 *
+		 * @param {String} text
+		 * @param {String} [endText=null]
+		 * @function
+		 * @name wysiwygEditorInsertText
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.wysiwygEditorInsertText = function (text, endText) {
+			var escape = function(str) {
+				if(!str)
+					return str;
+
+				return str.replace(/&/g, "&amp;")
+					.replace(/</g, "&lt;")
+					.replace(/>/g, "&gt;")
+					.replace(/ /g, "&nbsp;")
+					.replace(/\r\n|\r/g, "\n")
+					.replace(/\n/g, "<br />");
+			};
+
+			base.wysiwygEditorInsertHtml(escape(text), escape(endText));
+		};
+
+		/**
+		 * <p>Inserts text into either WYSIWYG or textEditor depending on which
+		 * mode the editor is in.</p>
+		 *
+		 * <p>If endText is specified any selected text will be placed between
+		 * text and endText. If no text is selected text and endText will
+		 * just be concated together.</p>
+		 *
+		 * @param {String} text
+		 * @param {String} [endText=null]
+		 * @since 1.3.5
+		 * @function
+		 * @name insertText
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.insertText = function (text, endText) {
+			if(base.inSourceMode())
+				base.textEditorInsertText(text, endText);
+			else
+				base.wysiwygEditorInsertText(text, endText);
+
+			return this;
+		};
+
+		/**
+		 * Like wysiwygEditorInsertHtml but inserts text into the text
+		 * (source mode) editor instead
+		 *
+		 * @param {String} text
+		 * @param {String} [endText=null]
+		 * @function
+		 * @name textEditorInsertText
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.textEditorInsertText = function (text, endText) {
+			var range, start, end, txtLen;
+
+			textEditor.focus();
+
+			if(typeof textEditor.selectionStart !== "undefined")
+			{
+				start	= textEditor.selectionStart;
+				end	= textEditor.selectionEnd;
+				txtLen	= text.length;
+
+				if(endText)
+					text += textEditor.value.substring(start, end) + endText;
+
+				textEditor.value = textEditor.value.substring(0, start) + text + textEditor.value.substring(end, textEditor.value.length);
+
+				if(endText)
+					textEditor.selectionStart = (start + text.length) - endText.length;
+				else
+					textEditor.selectionStart = start + text.length;
+
+				textEditor.selectionEnd = textEditor.selectionStart;
+			}
+			else if(typeof document.selection.createRange !== "undefined")
+			{
+				range = document.selection.createRange();
+
+				if(endText)
+					text += range.text + endText;
+
+				range.text = text;
+
+				if(endText)
+					range.moveEnd('character', 0-endText.length);
+
+				range.moveStart('character', range.End - range.Start);
+				range.select();
+			}
+			else
+				textEditor.value += text + endText;
+
+			textEditor.focus();
+		};
+
+		/**
+		 * Gets the current rangeHelper instance
+		 *
+		 * @return jQuery.sceditor.rangeHelper
+		 * @function
+		 * @name getRangeHelper
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.getRangeHelper = function () {
+			return rangeHelper;
+		};
+
+		/**
+		 * Gets the value of the editor
+		 *
+		 * @since 1.3.5
+		 * @return {string}
+		 * @function
+		 * @name val
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		/**
+		 * Sets the value of the editor
+		 *
+		 * @param {String} val
+		 * @param {Boolean} [filter]
+		 * @return {this}
+		 * @since 1.3.5
+		 * @function
+		 * @name val^2
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.val = function (val, filter) {
+			if(typeof val === "string")
+			{
+				if(base.inSourceMode())
+					base.setTextareaValue(val);
+				else
+				{
+					if(filter !== false && base.options.getTextHandler)
+						val = base.options.getTextHandler(val);
+
+					base.setWysiwygEditorValue(val);
+				}
+
+				return this;
+			}
+
+			return base.inSourceMode() ?
+				base.getTextareaValue(false) :
+				base.getWysiwygEditorValue();
+		};
+
+		/**
+		 * <p>Inserts HTML/BBCode into the editor</p>
+		 *
+		 * <p>If end is supplied any slected text will be placed between
+		 * start and end. If there is no selected text start and end
+		 * will be concated together.</p>
+		 *
+		 * @param {String} start
+		 * @param {String} [end=null]
+		 * @param {Boolean} [filter=true]
+		 * @param {Boolean} [convertEmoticons=true]
+		 * @return {this}
+		 * @since 1.3.5
+		 * @function
+		 * @name insert
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.insert = function (start, end, filter, convertEmoticons) {
+			if(base.inSourceMode())
+				base.textEditorInsertText(start, end);
+			else
+			{
+				if(end)
+				{
+					var	html = base.getRangeHelper().selectedHtml(),
+						frag = $('<div>').appendTo($('body')).hide().html(html);
+
+					if(filter !== false && base.options.getHtmlHandler)
+					{
+						html = base.options.getHtmlHandler(html, frag);
+						frag.remove();
+					}
+
+					start += html + end;
+				}
+
+				if(filter !== false && base.options.getTextHandler)
+					start = base.options.getTextHandler(start, true);
+
+				if(convertEmoticons !== false)
+					start = replaceEmoticons(start);
+
+				base.wysiwygEditorInsertHtml(start);
+			}
+
+			return this;
+		};
+
+		/**
+		 * Gets the WYSIWYG editors HTML which is between the body tags
+		 *
+		 * @param {bool} [filter=true]
+		 * @return {string}
+		 * @function
+		 * @name getWysiwygEditorValue
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.getWysiwygEditorValue = function (filter) {
+			// Possible replacement:
+			// if(!$.sceditor.isWysiwygSupported())
+			if (!base.options.supportedWysiwyg)
+				return;
+
+			var	$body = $wysiwygEditor.contents().find("body"),
+				html;
+
+			// fix any invalid nesting
+			$.sceditor.dom.fixNesting($body.get(0));
+			html = $body.html();
+
+			if(filter !== false && base.options.getHtmlHandler)
+				html = base.options.getHtmlHandler(html, $body);
+
+			return html;
+		};
+
+		/**
+		 * Gets the text editor value
+		 *
+		 * @param {bool} [filter=true]
+		 * @return {string}
+		 * @function
+		 * @name getTextareaValue
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.getTextareaValue = function (filter) {
+			var val = $textEditor.val();
+
+			if(filter !== false && base.options.getTextHandler)
+				val = base.options.getTextHandler(val);
+
+			return val;
+		};
+
+		/**
+		 * Sets the WYSIWYG HTML editor value. Should only be the HTML
+		 * contained within the body tags
+		 *
+		 * @param {string} value
+		 * @function
+		 * @name setWysiwygEditorValue
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.setWysiwygEditorValue = function (value) {
+			if(!value)
+				value = '<p>' + ($.sceditor.ie ? '' : '<br />') + '</p>';
+
+			getWysiwygDoc().body.innerHTML = replaceEmoticons(value);
+		};
+
+		/**
+		 * Sets the text editor value
+		 *
+		 * @param {string} value
+		 * @function
+		 * @name setTextareaValue
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.setTextareaValue = function (value) {
+			$textEditor.val(value);
+		};
+
+		/**
+		 * Updates the textarea that the editor is replacing
+		 * with the value currently inside the editor.
+		 *
+		 * @function
+		 * @name updateTextareaValue
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.updateTextareaValue = function () {
+			if(base.inSourceMode())
+				$textarea.val(base.getTextareaValue(false));
+			else
+				$textarea.val(base.getWysiwygEditorValue());
+		};
+
+		/**
+		 * Replaces any emoticon codes in the passed HTML with their emoticon images
+		 * @private
+		 */
+		replaceEmoticons = function (html) {
+			if(base.options.toolbar.indexOf('emoticon') === -1)
+				return html;
+
+			var emoticons = $.extend({}, base.options.emoticons.more, base.options.emoticons.dropdown, base.options.emoticons.hidden);
+
+			$.each(emoticons, function (key, url) {
+				// @todo Why did I (emanuele) add this?
+				if (key == '')
+					return;
+				// escape the key before using it as a regex
+				// and append the regex to only find emoticons outside
+				// of HTML tags
+				var	reg = $.sceditor.regexEscape(key) + "(?=([^\\<\\>]*?<(?!/code)|[^\\<\\>]*?$))",
+					group = '';
+
+				// Make sure the emoticon is surrounded by whitespace or is at the start/end of a string or html tag
+				if(base.options.emoticonsCompat)
+				{
+					reg = "((>|^|\\s|\xA0|\u2002|\u2003|\u2009|&nbsp;))" + reg + "(?=(\\s|$|<|\xA0|\u2002|\u2003|\u2009|&nbsp;))";
+					group = '$1';
+				}
+
+				html = html.replace(
+					new RegExp(reg, 'gm'),
+					group + _tmpl('emoticon', {key: key, url: url})
+				);
+			});
+
+			return html;
+		};
+
+		/**
+		 * If the editor is in source code mode
+		 *
+		 * @return {bool}
+		 * @function
+		 * @name inSourceMode
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.inSourceMode = function () {
+			return $textEditor.is(':visible');
+		};
+
+		/**
+		 * Gets if the editor is in sourceMode
+		 *
+		 * @return boolean
+		 * @function
+		 * @name sourceMode
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		/**
+		 * Sets if the editor is in sourceMode
+		 *
+		 * @param {bool} enable
+		 * @return {this}
+		 * @function
+		 * @name sourceMode^2
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.sourceMode = function (enable) {
+			if(typeof enable !== 'boolean')
+				return base.inSourceMode();
+
+			if((base.inSourceMode() && !enable) || (!base.inSourceMode() && enable))
+				base.toggleTextMode();
+
+			return this;
+		};
+
+		/**
+		 * Switches between the WYSIWYG and plain text modes
+		 *
+		 * @function
+		 * @name toggleTextMode
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.toggleTextMode = function () {
+			/*
+			// Possible replacement
+			// don't allow switching to WYSIWYG if doesn't support it
+			if(!$.sceditor.isWysiwygSupported() && base.inSourceMode())
+				return;
+
+			if(base.inSourceMode())
+			*/
+
+
+			if(base.inSourceMode() && base.options.supportedWysiwyg)
+				base.setWysiwygEditorValue(base.getTextareaValue());
+			else
+				base.setTextareaValue(base.getWysiwygEditorValue());
+
+			lastRange = null;
+			$textEditor.toggle();
+			$wysiwygEditor.toggle();
+
+			$editorContainer.removeClass('sourceMode');
+			$editorContainer.removeClass('wysiwygMode');
+
+			if(base.inSourceMode())
+				$editorContainer.addClass('sourceMode');
+			else
+				$editorContainer.addClass('wysiwygMode');
+
+			updateToolBar();
+		};
+
+		textEditorSelectedText = function () {
+			textEditor.focus();
+
+			if(textEditor.selectionStart != null)
+				return textEditor.value.substring(textEditor.selectionStart, textEditor.selectionEnd);
+			else if(document.selection.createRange)
+				return document.selection.createRange().text;
+		};
+
+		/**
+		 * Handles the passed command
+		 * @private
+		 */
+		handleCommand = function (caller, command) {
+			// check if in text mode and handle text commands
+			if(base.inSourceMode())
+			{
+				if(command.txtExec)
+				{
+					if($.isArray(command.txtExec))
+						base.textEditorInsertText.apply(base, command.txtExec);
+					else
+						command.txtExec.call(base, caller, textEditorSelectedText());
+				}
+
+				return;
+			}
+
+			if(!command.exec)
+				return;
+
+			if($.isFunction(command.exec))
+				command.exec.call(base, caller);
+			else
+				base.execCommand(command.exec, command.hasOwnProperty("execParam") ? command.execParam : null);
+		};
+
+		/**
+		 * Fucuses the editors input area
+		 *
+		 * @return {this}
+		 * @function
+		 * @name focus
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.focus = function () {
+			if(!base.inSourceMode())
+			{
+				wysiwygEditor.contentWindow.focus();
+
+				// Needed for IE < 9
+				if(lastRange) {
+					rangeHelper.selectRange(lastRange);
+
+					// remove the stored range after being set.
+					// If the editor loses focus it should be
+					// saved again.
+					lastRange = null;
+				}
+			}
+			else
+				textEditor.focus();
+
+			return this;
+		};
+
+		/**
+		 * Blurs the editors input area
+		 *
+		 * @return {this}
+		 * @function
+		 * @name blur
+		 * @memberOf jQuery.sceditor.prototype
+		 * @since 1.3.6
+		 */
+		base.blur = function () {
+			// Must use an element that isn't display:hidden or visibility:hidden for iOS
+			// so create a special blur element to use
+			if(!$blurElm)
+				$blurElm = $('<input style="width:0; height:0; opacity:0; filter: alpha(opacity=0)" type="text" />').appendTo($editorContainer);
+
+			$blurElm.removeAttr("disabled")
+				.focus()
+				.blur()
+				.attr("disabled", "disabled");
+
+			return this;
+		};
+
+		/**
+		 * Saves the current range. Needed for IE because it forgets
+		 * where the cursor was and what was selected
+		 * @private
+		 */
+		saveRange = function () {
+			/* this is only needed for IE */
+			if(!$.sceditor.ie)
+				return;
+
+			lastRange = rangeHelper.selectedRange();
+		};
+
+		/**
+		 * Executes a command on the WYSIWYG editor
+		 *
+		 * @param {String|Function} command
+		 * @param {String|Boolean} [param]
+		 * @function
+		 * @name execCommand
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base.execCommand = function (command, param) {
+			var	executed	= false,
+				$parentNode	= $(rangeHelper.parentNode());
+
+			base.focus();
+
+			// don't apply any comannds to code elements
+			if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
+				return;
+
+			if(getWysiwygDoc())
+			{
+				try
+				{
+					executed = getWysiwygDoc().execCommand(command, false, param);
+				}
+				catch (e) {}
+			}
+
+			// show error if execution failed and an error message exists
+			if(!executed && base.commands[command] && base.commands[command].errorMessage)
+				alert(base._(base.commands[command].errorMessage));
+		};
+
+		/**
+		 * Handles any key press in the WYSIWYG editor
+		 *
+		 * @private
+		 */
+		handleKeyPress = function(e) {
+			base.closeDropDown();
+
+			var $parentNode = $(rangeHelper.parentNode());
+
+			// "Fix" (ok it's a cludge) for blocklevel elements being duplicated in some browsers when
+			// enter is pressed instead of inserting a newline
+			if(e.which === 13)
+			{
+				if($parentNode.is('code,blockquote,pre') || $parentNode.parents('code,blockquote,pre').length !== 0)
+				{
+					lastRange = null;
+					base.wysiwygEditorInsertHtml('<br />', null, true);
+					return false;
+				}
+			}
+
+			// make sure there is always a newline after code or quote tags
+			var d = getWysiwygDoc();
+			$.sceditor.dom.rTraverse(d.body, function(node) {
+				if((node.nodeType === 3 && node.nodeValue !== "") ||
+					node.nodeName.toLowerCase() === 'br') {
+					// this is the last text or br node, if its in a code or quote tag
+					// then add a newline after it
+					if($(node).parents('code, blockquote').length > 0)
+						$(d.body).append(d.createElement('br'));
+
+					return false;
+				}
+			}, true);
+
+			// don't apply to code elements
+			if($parentNode.is('code') || $parentNode.parents('code').length !== 0)
+				return;
+
+			var i = keyPressFuncs.length;
+			while(i--)
+				keyPressFuncs[i].call(base, e, wysiwygEditor, $textEditor);
+		};
+
+		/**
+		 * Handles any mousedown press in the WYSIWYG editor
+		 * @private
+		 */
+		handleFormReset = function() {
+			base.val($textarea.val());
+		};
+
+		/**
+		 * Handles any mousedown press in the WYSIWYG editor
+		 * @private
+		 */
+		handleMouseDown = function() {
+			base.closeDropDown();
+			lastRange = null;
+		};
+
+		/**
+		 * Handles the window resize event. Needed to resize then editor
+		 * when the window size changes in fluid deisgns.
+		 * @ignore
+		 */
+		handleWindowResize = function() {
+			if(base.options.height && base.options.height.toString().indexOf("%") > -1)
+				base.height($editorContainer.parent().height() *
+					(parseFloat(base.options.height) / 100));
+
+			if(base.options.width && base.options.width.toString().indexOf("%") > -1)
+				base.width($editorContainer.parent().width() *
+					(parseFloat(base.options.width) / 100));
+		};
+
+		/**
+		 * Translates the string into the locale language.
+		 *
+		 * Replaces any {0}, {1}, {2}, ect. with the params provided.
+		 *
+		 * @param {string} str
+		 * @param {...String} args
+		 * @return {string}
+		 * @function
+		 * @name _
+		 * @memberOf jQuery.sceditor.prototype
+		 */
+		base._ = function() {
+			var args = arguments;
+
+			if(locale && locale[args[0]])
+				args[0] = locale[args[0]];
+
+			return args[0].replace(/\{(\d+)\}/g, function(str, p1) {
+				return typeof args[p1-0+1] !== "undefined" ?
+					args[p1-0+1] :
+					'{' + p1 + '}';
+			});
+		};
+
+		/**
+		 * Init the locale variable with the specified locale if possible
+		 * @private
+		 * @return void
+		 */
+		initLocale = function() {
+			if($.sceditor.locale[base.options.locale])
+				locale = $.sceditor.locale[base.options.locale];
+			else
+			{
+				var lang = base.options.locale.split("-");
+
+				if($.sceditor.locale[lang[0]])
+					locale = $.sceditor.locale[lang[0]];
+			}
+
+			if(locale && locale.dateFormat)
+				base.options.dateFormat = locale.dateFormat;
+		};
+
+		// run the initializer
+		init();
+	};
+
+	/**
+	 * Detects which version of IE is being used if any.
+	 *
+	 * Will be the IE version number or undefined if not IE.
+	 *
+	 * Source: https://gist.github.com/527683
+	 * @type {int}
+	 * @memberOf jQuery.sceditor
+	 */
+	$.sceditor.ie = (function(){
+		var	undef,
+			v	= 3,
+			div	= document.createElement('div'),
+			all	= div.getElementsByTagName('i');
+
+		do {
+			div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->';
+		} while (all[0]);
+
+		return v > 4 ? v : undef;
+	}());
+
+	/**
+	 * Detects if WYSIWYG is supported by the browser
+	 *
+	 * @return {bool}
+	 * @memberOf jQuery.sceditor
+	 */
+	$.sceditor.isWysiwygSupported = function() {
+		var	contentEditable			= $('<div contenteditable="true">')[0].contentEditable,
+			contentEditableSupported	= typeof contentEditable !== 'undefined' && contentEditable !== 'inherit',
+			userAgent			= navigator.userAgent,
+			match;
+
+		if(!contentEditableSupported)
+			return false;
+
+		// I think blackberry supports it or will at least
+		// give a valid value for the contentEditable detection above
+		// so it's' not included here.
+
+
+		// The latest WebOS dose support contentEditable.
+		// But I still till need to check if all supported
+		// versions of WebOS support contentEditable
+
+
+		// I hate having to use UA sniffing but as some mobile browsers say they support
+		// contentediable/design mode when it isn't usable (i.e. you can't eneter text, ect.).
+		// This is the only way I can think of to detect them. It's also how every other editor
+		// I've seen detects them
+		var isUnsupported = /Opera Mobi|Opera Mini/i.test(userAgent);
+
+		if(/Android/i.test(userAgent))
+		{
+			isUnsupported = true;
+
+			if(/Safari/.test(userAgent))
+			{
+				// android browser 534+ supports content editable
+				match = /Safari\/(\d+)/.exec(userAgent);
+				isUnsupported = (!match || !match[1] ? true : match[1] < 534);
+			}
+		}
+
+		// Amazon Silk doesn't but as it uses webkit like android
+		// it might in a later version if it uses version >= 534
+		if(/ Silk\//i.test(userAgent))
+		{
+			match = /AppleWebKit\/(\d+)/.exec(userAgent);
+			isUnsupported = (!match || !match[1] ? true : match[1] < 534);
+		}
+
+		// iOS 5+ supports content editable
+		if(/iPhone|iPod|iPad/i.test(userAgent))
+			isUnsupported = !/OS 5(_\d)+ like Mac OS X/i.test(userAgent);
+
+		// FireFox dose support WYSIWYG on mobiles so override
+		// any previous value if using FF
+		if(/fennec/i.test(userAgent))
+			isUnsupported = false;
+
+		return !isUnsupported;
+	};
+
+	/**
+	 * Escapes a string so it's safe to use in regex
+	 *
+	 * @param {string} str
+	 * @return {string}
+	 * @memberOf jQuery.sceditor
+	 */
+	$.sceditor.regexEscape = function (str) {
+		return str.replace(/[\$\?\[\]\.\*\(\)\|\\]/g, "\\$&")
+			.replace("<", "&lt;")
+			.replace(">", "&gt;");
+	};
+
+	$.sceditor.locale = {};
+
+	$.sceditor.commands = {
+		// START_COMMAND: Bold
+		bold: {
+			exec: "bold",
+			tooltip: "Bold"
+		},
+		// END_COMMAND
+		// START_COMMAND: Italic
+		italic: {
+			exec: "italic",
+			tooltip: "Italic"
+		},
+		// END_COMMAND
+		// START_COMMAND: Underline
+		underline: {
+			exec: "underline",
+			tooltip: "Underline"
+		},
+		// END_COMMAND
+		// START_COMMAND: Strikethrough
+		strike: {
+			exec: "strikethrough",
+			tooltip: "Strikethrough"
+		},
+		// END_COMMAND
+		// START_COMMAND: Subscript
+		subscript: {
+			exec: "subscript",
+			tooltip: "Subscript"
+		},
+		// END_COMMAND
+		// START_COMMAND: Superscript
+		superscript: {
+			exec: "superscript",
+			tooltip: "Superscript"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Left
+		left: {
+			exec: "justifyleft",
+			tooltip: "Align left"
+		},
+		// END_COMMAND
+		// START_COMMAND: Centre
+		center: {
+			exec: "justifycenter",
+			tooltip: "Center"
+		},
+		// END_COMMAND
+		// START_COMMAND: Right
+		right: {
+			exec: "justifyright",
+			tooltip: "Align right"
+		},
+		// END_COMMAND
+		// START_COMMAND: Justify
+		justify: {
+			exec: "justifyfull",
+			tooltip: "Justify"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Font
+		font: {
+			_dropDown: function(editor, caller, callback) {
+				var	fonts   = editor.options.fonts.split(","),
+					content = $("<div />"),
+					/** @private */
+					clickFunc = function () {
+						callback($(this).data('font'));
+						editor.closeDropDown(true);
+						return false;
+					};
+
+				for (var i=0; i < fonts.length; i++)
+					content.append(_tmpl('fontOpt', {font: fonts[i]}, true).click(clickFunc));
+
+				editor.createDropDown(caller, "font-picker", content);
+			},
+			exec: function (caller) {
+				var editor = this;
+
+				$.sceditor.command.get('font')._dropDown(
+					editor,
+					caller,
+					function(fontName) {
+						editor.execCommand("fontname", fontName);
+					}
+				);
+			},
+			tooltip: "Font Name"
+		},
+		// END_COMMAND
+		// START_COMMAND: Size
+		size: {
+			_dropDown: function(editor, caller, callback) {
+				var sizes = [0, 8, 10, 12, 14, 18, 24, 36];
+				var	content   = $("<div />"),
+					/** @private */
+					clickFunc = function (e) {
+						callback($(this).data('size'));
+						editor.closeDropDown(true);
+						e.preventDefault();
+					};
+
+				for (var i=1; i<= 7; i++)
+					content.append(_tmpl('sizeOpt', {size: i, points: sizes[i]}, true).click(clickFunc));
+
+				editor.createDropDown(caller, "fontsize-picker", content);
+			},
+			exec: function (caller) {
+				var editor = this;
+
+				$.sceditor.command.get('size')._dropDown(
+					editor,
+					caller,
+					function(fontSize) {
+						editor.execCommand("fontsize", fontSize);
+					}
+				);
+			},
+			tooltip: "Font Size"
+		},
+		// END_COMMAND
+		// START_COMMAND: Colour
+		color: {
+			_dropDown: function(editor, caller, callback) {
+				var	genColor		= {r: 255, g: 255, b: 255},
+					content			= $("<div />"),
+					colorColumns		= editor.options.colors?editor.options.colors.split("|"):new Array(21),
+					// IE is slow at string concation so use an array
+					html			= [],
+					htmlIndex		= 0;
+
+				for (var i=0; i < colorColumns.length; ++i) {
+					var colors = colorColumns[i]?colorColumns[i].split(","):new Array(21);
+
+					html[htmlIndex++] = '<div class="sceditor-color-column">';
+
+					for (var x=0; x < colors.length; ++x) {
+						// use pre defined colour if can otherwise use the generated color
+						var color = colors[x]?colors[x]:"#" + genColor.r.toString(16) + genColor.g.toString(16) + genColor.b.toString(16);
+
+						html[htmlIndex++] = '<a href="#" class="sceditor-color-option" style="background-color: '+color+'" data-color="'+color+'"></a>';
+
+						// calculate the next generated color
+						if(x%5===0)
+							genColor = {r: genColor.r, g: genColor.g-51, b: 255};
+						else
+							genColor = {r: genColor.r, g: genColor.g, b: genColor.b-51};
+					}
+
+					html[htmlIndex++] = '</div>';
+
+					// calculate the next generated color
+					if(i%5===0)
+						genColor = {r: genColor.r-51, g: 255, b: 255};
+					else
+						genColor = {r: genColor.r, g: 255, b: 255};
+				}
+
+				content.append(html.join(''))
+					.find('a')
+					.click(function (e) {
+						callback($(this).attr('data-color'));
+						editor.closeDropDown(true);
+						e.preventDefault();
+					});
+
+				editor.createDropDown(caller, "color-picker", content);
+			},
+			exec: function (caller) {
+				var editor = this;
+
+				$.sceditor.command.get('color')._dropDown(
+					editor,
+					caller,
+					function(color) {
+						editor.execCommand("forecolor", color);
+					}
+				);
+			},
+			tooltip: "Font Color"
+		},
+		// END_COMMAND
+		// START_COMMAND: Remove Format
+		removeformat: {
+			exec: "removeformat",
+			tooltip: "Remove Formatting"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Cut
+		cut: {
+			exec: "cut",
+			tooltip: "Cut",
+			errorMessage: "Your browser does not allow the cut command. Please use the keyboard shortcut Ctrl/Cmd-X"
+		},
+		// END_COMMAND
+		// START_COMMAND: Copy
+		copy: {
+			exec: "copy",
+			tooltip: "Copy",
+			errorMessage: "Your browser does not allow the copy command. Please use the keyboard shortcut Ctrl/Cmd-C"
+		},
+		// END_COMMAND
+		// START_COMMAND: Paste
+		paste: {
+			exec: "paste",
+			tooltip: "Paste",
+			errorMessage: "Your browser does not allow the paste command. Please use the keyboard shortcut Ctrl/Cmd-V"
+		},
+		// END_COMMAND
+		// START_COMMAND: Paste Text
+		pastetext: {
+			exec: function (caller) {
+				var	val,
+					editor	= this,
+					content	= _tmpl("pastetext", {
+						label: editor._("Paste your text inside the following box:"),
+						insert: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					val = content.find("#txt").val();
+
+					if(val)
+						editor.wysiwygEditorInsertText(val);
+
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "pastetext", content);
+			},
+			tooltip: "Paste Text"
+		},
+		// END_COMMAND
+		// START_COMMAND: Bullet List
+		bulletlist: {
+			exec: "insertunorderedlist",
+			tooltip: "Bullet list"
+		},
+		// END_COMMAND
+		// START_COMMAND: Ordered List
+		orderedlist: {
+			exec: "insertorderedlist",
+			tooltip: "Numbered list"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Table
+		table: {
+			exec: function (caller) {
+				var	editor  = this,
+					content = _tmpl("table", {
+						rows: editor._("Rows:"),
+						cols: editor._("Cols:"),
+						insert: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					var	rows = content.find("#rows").val() - 0,
+						cols = content.find("#cols").val() - 0,
+						html = '<table>';
+
+					if(rows < 1 || cols < 1)
+						return;
+
+					for (var row=0; row < rows; row++) {
+						html += '<tr>';
+
+						for (var col=0; col < cols; col++)
+							html += '<td>' + ($.sceditor.ie ? '' : '<br class="sceditor-ignore" />') + '</td>';
+
+						html += '</tr>';
+					}
+
+					html += '</table>';
+
+					editor.wysiwygEditorInsertHtml(html);
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "inserttable", content);
+			},
+			tooltip: "Insert a table"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Horizontal Rule
+		horizontalrule: {
+			exec: "inserthorizontalrule",
+			tooltip: "Insert a horizontal rule"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Code
+		code: {
+			exec: function () {
+				this.wysiwygEditorInsertHtml('<code>', '<br /></code>');
+			},
+			tooltip: "Code"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Image
+		image: {
+			exec: function (caller) {
+				var	editor  = this,
+					content = _tmpl("image", {
+						url: editor._("URL:"),
+						width: editor._("Width (optional):"),
+						height: editor._("Height (optional):"),
+						insert: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					var	val	= content.find("#image").val(),
+						attrs	= '',
+						width, height;
+
+					if((width = content.find("#width").val()))
+						attrs += ' width="' + width + '"';
+					if((height = content.find("#height").val()))
+						attrs += ' height="' + height + '"';
+
+					if(val && val !== "http://")
+						editor.wysiwygEditorInsertHtml('<img' + attrs + ' src="' + val + '" />');
+
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "insertimage", content);
+			},
+			tooltip: "Insert an image"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: E-mail
+		email: {
+			exec: function (caller) {
+				var	editor  = this,
+					content = _tmpl("email", {
+						label: editor._("E-mail:"),
+						insert: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					var val = content.find("#email").val();
+
+					if(val)
+					{
+						// needed for IE to reset the last range
+						editor.focus();
+
+						if(!editor.getRangeHelper().selectedHtml())
+							editor.wysiwygEditorInsertHtml('<a href="' + 'mailto:' + val + '">' + val + '</a>');
+						else
+							editor.execCommand("createlink", 'mailto:' + val);
+					}
+
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "insertemail", content);
+			},
+			tooltip: "Insert an email"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Link
+		link: {
+			exec: function (caller) {
+				var	editor  = this,
+					content = _tmpl("link", {
+						url: editor._("URL:"),
+						desc: editor._("Description (optional):"),
+						ins: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					var	val		= content.find("#link").val(),
+						description	= content.find("#des").val();
+
+					if(val !== "" && val !== "http://") {
+						// needed for IE to reset the last range
+						editor.focus();
+
+						if(!editor.getRangeHelper().selectedHtml() || description)
+						{
+							if(!description)
+								description = val;
+
+							editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
+						}
+						else
+							editor.execCommand("createlink", val);
+					}
+
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "insertlink", content);
+			},
+			tooltip: "Insert a link"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Unlink
+		unlink: {
+			exec: "unlink",
+			tooltip: "Unlink"
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Quote
+		quote: {
+			exec: function (caller, html, author) {
+				var	before	= '<blockquote>',
+					end	= '</blockquote>';
+
+				// if there is HTML passed set end to null so any selected
+				// text is replaced
+				if(html)
+				{
+					author = (author ? '<cite>' + author + '</cite>' : '');
+
+					before = before + author + html + end + '<br />';
+					end = null;
+				}
+				// if not add a newline to the end of the inserted quote
+				else if(this.getRangeHelper().selectedHtml() === "")
+					end = '<br />' + end;
+
+				this.wysiwygEditorInsertHtml(before, end);
+			},
+			tooltip: "Insert a Quote"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Emoticons
+		emoticon: {
+			exec: function (caller) {
+				var	appendEmoticon,
+					editor  = this,
+					end	= (editor.options.emoticonsCompat ? ' ' : ''),
+					content = $('<div />'),
+					line    = $('<div />');
+
+				appendEmoticon = function (code, emoticon) {
+					line.append($('<img />')
+							.attr({
+								src: emoticon,
+								alt: code
+							})
+							.click(function (e) {
+								editor.insert($(this).attr('alt') + end);
+								editor.closeDropDown(true);
+								e.preventDefault();
+							})
+						);
+
+					if(line.children().length > 3) {
+						content.append(line);
+						line = $('<div />');
+					}
+				};
+
+				$.each(editor.options.emoticons.dropdown, appendEmoticon);
+
+				if(line.children().length > 0)
+					content.append(line);
+
+				if(editor.options.emoticons.more) {
+					var more = $(
+						this._('<a class="sceditor-more">{0}</a>', this._("More"))
+					).click(function () {
+						var	emoticons	= $.extend({}, editor.options.emoticons.dropdown, editor.options.emoticons.more);
+							content		= $('<div />');
+
+						$.each(emoticons, appendEmoticon);
+
+						if(line.children().length > 0)
+							content.append(line);
+
+						editor.createDropDown(caller, "insertemoticon", content);
+						return false;
+					});
+
+					content.append(more);
+				}
+
+				editor.createDropDown(caller, "insertemoticon", content);
+			},
+			txtExec: function(caller) {
+				$.sceditor.command.get('emoticon').exec.call(this, caller);
+			},
+			keyPress: function (e)
+			{
+				// make sure emoticons command is in the toolbar before running
+				if(this.options.toolbar.indexOf('emoticon') === -1)
+					return;
+
+				var	editor = this,
+					pos = 0,
+					curChar = String.fromCharCode(e.which);
+
+				if(!editor.EmoticonsCache) {
+					editor.EmoticonsCache = [];
+
+					$.each($.extend({}, editor.options.emoticons.more, editor.options.emoticons.dropdown, editor.options.emoticons.hidden), function(key, url) {
+						editor.EmoticonsCache[pos++] = [
+							key,
+							_tmpl("emoticon", {key: key, url: url})
+						];
+					});
+
+					editor.EmoticonsCache.sort(function(a, b){
+						return a[0].length - b[0].length;
+					});
+				}
+
+				if(!editor.longestEmoticonCode)
+					editor.longestEmoticonCode = editor.EmoticonsCache[editor.EmoticonsCache.length - 1][0].length;
+
+				if(editor.getRangeHelper().raplaceKeyword(editor.EmoticonsCache, true, true, editor.longestEmoticonCode, editor.options.emoticonsCompat, curChar))
+				{
+					if(/^\s$/.test(curChar) && editor.options.emoticonsCompat)
+						return true;
+
+					e.preventDefault();
+					e.stopPropagation();
+					return false;
+				}
+			},
+			tooltip: "Insert an emoticon"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: YouTube
+		youtube: {
+			_dropDown: function (editor, caller, handleIdFunc) {
+				var	matches,
+					content = _tmpl("youtubeMenu", {
+						label: editor._("Video URL:"),
+						insert: editor._("Insert")
+					}, true);
+
+				content.find('.button').click(function (e) {
+					var val = content.find("#link").val().replace("http://", "");
+
+					if (val !== "") {
+						matches = val.match(/(?:v=|v\/|embed\/|youtu.be\/)(.{11})/);
+						if (matches) val = matches[1];
+
+						if (/^[a-zA-Z0-9_\-]{11}$/.test(val))
+							handleIdFunc(val);
+						else
+							alert('Invalid YouTube video');
+					}
+
+					editor.closeDropDown(true);
+					e.preventDefault();
+				});
+
+				editor.createDropDown(caller, "insertlink", content);
+			},
+			exec: function (caller) {
+				var editor = this;
+
+				$.sceditor.command.get('youtube')._dropDown(
+					editor,
+					caller,
+					function(id) {
+						editor.wysiwygEditorInsertHtml(_tmpl("youtube", { id: id }));
+					}
+				);
+			},
+			tooltip: "Insert a YouTube video"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Date
+		date: {
+			_date: function (editor) {
+				var	now   = new Date(),
+					year  = now.getYear(),
+					month = now.getMonth()+1,
+					day   = now.getDate();
+
+				if(year < 2000)
+					year = 1900 + year;
+				if(month < 10)
+					month = "0" + month;
+				if(day < 10)
+					day = "0" + day;
+
+				return editor.options.dateFormat.replace(/year/i, year).replace(/month/i, month).replace(/day/i, day);
+			},
+			exec: function () {
+				this.insertText($.sceditor.command.get('date')._date(this));
+			},
+			txtExec: function () {
+				this.insertText($.sceditor.command.get('date')._date(this));
+			},
+			tooltip: "Insert current date"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Time
+		time: {
+			_time: function () {
+				var	now   = new Date(),
+					hours = now.getHours(),
+					mins  = now.getMinutes(),
+					secs  = now.getSeconds();
+
+				if(hours < 10)
+					hours = "0" + hours;
+				if(mins < 10)
+					mins = "0" + mins;
+				if(secs < 10)
+					secs = "0" + secs;
+
+				return hours + ':' + mins + ':' + secs;
+			},
+			exec: function () {
+				this.insertText($.sceditor.command.get('time')._time());
+			},
+			txtExec: function () {
+				this.insertText($.sceditor.command.get('time')._time());
+			},
+			tooltip: "Insert current time"
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Ltr
+		ltr: {
+			exec: function() {
+				var	editor	= this,
+					elm	= editor.getRangeHelper().getFirstBlockParent(),
+					$elm	= $(elm);
+
+				editor.focus();
+
+				if(!elm || $elm.is('body'))
+				{
+					editor.execCommand("formatBlock", "p");
+
+					elm	= editor.getRangeHelper().getFirstBlockParent();
+					$elm	= $(elm);
+
+					if(!elm || $elm.is('body'))
+						return;
+				}
+
+				if($elm.css('direction') === 'ltr')
+					$(elm).css('direction', '');
+				else
+					$(elm).attr('direction', 'ltr');
+			},
+			tooltip: "Left-to-Right"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Rtl
+		rtl: {
+			exec: function() {
+				var	editor	= this,
+					elm	= editor.getRangeHelper().getFirstBlockParent(),
+					$elm	= $(elm);
+
+				editor.focus();
+
+				if(!elm || $elm.is('body'))
+				{
+					editor.execCommand("formatBlock", "p");
+
+					elm	= editor.getRangeHelper().getFirstBlockParent();
+					$elm	= $(elm);
+
+					if(!elm || $elm.is('body'))
+						return;
+				}
+
+				if($elm.css('direction') === 'rtl')
+					$(elm).css('direction', '');
+				else
+					$(elm).css('direction', 'rtl');
+			},
+			tooltip: "Right-to-Left"
+		},
+		// END_COMMAND
+
+
+		// START_COMMAND: Print
+		print: {
+			exec: "print",
+			tooltip: "Print"
+		},
+		// END_COMMAND
+
+		// START_COMMAND: Source
+		source: {
+			exec: function () {
+				this.toggleTextMode();
+			},
+			txtExec: function () {
+				this.toggleTextMode();
+			},
+			tooltip: "View source"
+		},
+		// END_COMMAND
+
+		// this is here so that commands above can be removed
+		// without having to remove the , after the last one.
+		// Needed for IE.
+		ignore: {}
+	};
+
+	/**
+	 * Range helper class
+	 * @class rangeHelper
+	 * @name jQuery.sceditor.rangeHelper
+	 */
+	$.sceditor.rangeHelper = function(w, d) {
+		var	win, doc,
+			isW3C		= true,
+			startMarker	= "sceditor-start-marker",
+			endMarker	= "sceditor-end-marker",
+			base		= this,
+			init, _createMarker, _getOuterText, _selectOuterText;
+
+		/**
+		 * @constructor
+		 * @param Window window
+		 * @param Document document
+		 * @private
+		 */
+		init = function (window, document) {
+			doc	= document || window.contentDocument || window.document;
+			win	= window;
+			isW3C	= !!window.getSelection;
+		}(w, d);
+
+		/**
+		 * <p>Inserts HTML into the current range replacing any selected
+		 * text.</p>
+		 *
+		 * <p>If endHTML is specified the selected contents will be put between
+		 * html and endHTML. If there is nothing selected html and endHTML are
+		 * just concated together.</p>
+		 *
+		 * @param {string} html
+		 * @param {string} endHTML
+		 * @function
+		 * @name insertHTML
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.insertHTML = function(html, endHTML) {
+			var node, div;
+
+			if(endHTML)
+				html += base.selectedHtml() + endHTML;
+
+			if(isW3C)
+			{
+				div		= doc.createElement('div');
+				node		= doc.createDocumentFragment();
+				div.innerHTML	= html;
+
+				while(div.firstChild)
+					node.appendChild(div.firstChild);
+
+				base.insertNode(node);
+			}
+			else
+				base.selectedRange().pasteHTML(html);
+		};
+
+		/**
+		 * <p>The same as insertHTML except with DOM nodes instead</p>
+		 *
+		 * <p><strong>Warning:</strong> the nodes must belong to the
+		 * document they are being inserted into. Some browsers
+		 * will throw exceptions if they don't.</p>
+		 *
+		 * @param {Node} node
+		 * @param {Node} endNode
+		 *
+		 * @function
+		 * @name insertNode
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.insertNode = function(node, endNode) {
+			if(isW3C)
+			{
+				var	toInsert	= doc.createDocumentFragment(),
+					range		= base.selectedRange(),
+					selection, selectAfter;
+
+				toInsert.appendChild(node);
+
+				if(endNode)
+				{
+					toInsert.appendChild(range.extractContents());
+					toInsert.appendChild(endNode);
+				}
+
+				selectAfter = toInsert.lastChild;
+				range.deleteContents();
+				range.insertNode(toInsert);
+
+				selection = doc.createRange();
+				selection.setStartAfter(selectAfter);
+				base.selectRange(selection);
+			}
+			else
+				base.insertHTML(node.outerHTML, endNode?endNode.outerHTML:null);
+		};
+
+		/**
+		 * <p>Clones the selected Range</p>
+		 *
+		 * <p>IE <= 8 will return a TextRange, all other browsers
+		 * will return a Range object.</p>
+		 *
+		 * @return {Range|TextRange}
+		 * @function
+		 * @name cloneSelected
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.cloneSelected = function() {
+			if(!isW3C)
+				return base.selectedRange().duplicate();
+
+			return base.selectedRange().cloneRange();
+		};
+
+		/**
+		 * <p>Gets the selected Range</p>
+		 *
+		 * <p>IE <= 8 will return a TextRange, all other browsers
+		 * will return a Range object.</p>
+		 *
+		 * @return {Range|TextRange}
+		 * @function
+		 * @name selectedRange
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.selectedRange = function() {
+			var sel;
+
+			if(win.getSelection)
+				sel = win.getSelection();
+			else
+				sel = doc.selection;
+
+			if(sel.getRangeAt && sel.rangeCount <= 0)
+				sel.addRange(doc.createRange());
+
+			if(!isW3C)
+				return sel.createRange();
+
+			return sel.getRangeAt(0);
+		};
+
+		/**
+		 * Gets the currently selected HTML
+		 *
+		 * @return {string}
+		 * @function
+		 * @name selectedHtml
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.selectedHtml = function() {
+			var range = base.selectedRange();
+
+			if(!range)
+				return '';
+
+			// IE9+ and all other browsers
+			if (window.XMLSerializer)
+				return new XMLSerializer().serializeToString(range.cloneContents());
+
+			// IE < 9
+			if(!isW3C)
+			{
+				if(range.text !== '' && range.htmlText)
+					return range.htmlText;
+			}
+
+			return '';
+		};
+
+		/**
+		 * Gets the parent node of the selected contents in the range
+		 *
+		 * @return {HTMLElement}
+		 * @function
+		 * @name parentNode
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.parentNode = function() {
+			var range = base.selectedRange();
+
+			if(isW3C)
+				return range.commonAncestorContainer;
+			else
+				return range.parentElement();
+		};
+
+		/**
+		 * Gets the first block level parent of the selected
+		 * contents of the range.
+		 *
+		 * @return {HTMLElement}
+		 * @function
+		 * @name getFirstBlockParent
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.getFirstBlockParent = function() {
+			var func = function(node) {
+				if(!$.sceditor.dom.isInline(node))
+					return node;
+
+				var p = node.parentNode;
+				if(p)
+					return func(p);
+
+				return null;
+			};
+
+			return func(base.parentNode());
+		};
+
+		/**
+		 * Inserts a node at either the start or end of the current selection
+		 *
+		 * @param {Bool} start
+		 * @param {Node} node
+		 * @function
+		 * @name insertNodeAt
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.insertNodeAt = function(start, node) {
+			var range = base.cloneSelected();
+
+			range.collapse(start);
+
+			if(range.insertNode)
+				range.insertNode(node);
+			else
+				range.pasteHTML(node.outerHTML);
+		};
+
+		/**
+		 * Creates a marker node
+		 *
+		 * @param {String} id
+		 * @return {Node}
+		 * @private
+		 */
+		_createMarker = function(id) {
+			base.removeMarker(id);
+
+			var marker = doc.createElement("span");
+			marker.id = id;
+			marker.style.lineHeight	= "0";
+			marker.style.display	= "none";
+			marker.className	= "sceditor-selection";
+
+			return marker;
+		};
+
+		/**
+		 * Inserts start/end markers for the current selection
+		 * which can be used by restoreRange to re-select the
+		 * range.
+		 *
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 * @function
+		 * @name insertMarkers
+		 */
+		base.insertMarkers = function() {
+			base.insertNodeAt(true, _createMarker(startMarker));
+			base.insertNodeAt(false, _createMarker(endMarker));
+		};
+
+		/**
+		 * Gets the marker with the specified ID
+		 *
+		 * @param {String} id
+		 * @return {Node}
+		 * @function
+		 * @name getMarker
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.getMarker = function(id) {
+			return doc.getElementById(id);
+		};
+
+		/**
+		 * Removes the marker with the specified ID
+		 *
+		 * @param {String} id
+		 * @function
+		 * @name removeMarker
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.removeMarker = function(id) {
+			var marker = base.getMarker(id);
+
+			if(marker)
+				marker.parentNode.removeChild(marker);
+		};
+
+		/**
+		 * Removes the start/end markers
+		 *
+		 * @function
+		 * @name removeMarkers
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.removeMarkers = function() {
+			base.removeMarker(startMarker);
+			base.removeMarker(endMarker);
+		};
+
+		/**
+		 * Saves the current range location. Alias of insertMarkers()
+		 *
+		 * @function
+		 * @name saveRage
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.saveRange = function() {
+			base.insertMarkers();
+		};
+
+		/**
+		 * Select the specified range
+		 *
+		 * @param {Range|TextRange} range
+		 * @function
+		 * @name selectRange
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.selectRange = function(range) {
+			if(!isW3C)
+				range.select();
+			else
+			{
+				win.getSelection().removeAllRanges();
+				win.getSelection().addRange(range);
+			}
+		};
+
+		/**
+		 * Restores the last range saved by saveRange() or insertMarkers()
+		 *
+		 * @function
+		 * @name restoreRange
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.restoreRange = function() {
+			var	range	= base.selectedRange(),
+				start	= base.getMarker(startMarker),
+				end	= base.getMarker(endMarker);
+
+			if(!start || !end)
+				return false;
+
+			if(!isW3C)
+			{
+				range = doc.body.createTextRange();
+				var marker = doc.body.createTextRange();
+
+				marker.moveToElementText(start);
+				range.setEndPoint('StartToStart', marker);
+				range.moveStart('character', 0);
+
+				marker.moveToElementText(end);
+				range.setEndPoint('EndToStart', marker);
+				range.moveEnd('character', 0);
+
+				base.selectRange(range);
+			}
+			else
+			{
+				range = doc.createRange();
+				range.setStartBefore(start);
+				range.setEndAfter(end);
+
+				base.selectRange(range);
+			}
+
+			base.removeMarkers();
+		};
+
+		/**
+		 * Selects the text left and right of the current selection
+		 * @param int left
+		 * @param int right
+		 * @private
+		 */
+		_selectOuterText = function(left, right) {
+			var range = base.cloneSelected();
+
+			range.collapse(false);
+			if(!isW3C)
+			{
+				range.moveStart('character', 0-left);
+				range.moveEnd('character', right);
+			}
+			else
+			{
+				range.setStart(range.startContainer, range.startOffset-left);
+				range.setEnd(range.endContainer, range.endOffset+right);
+				//range.deleteContents();
+			}
+
+			base.selectRange(range);
+		};
+
+		/**
+		 * Gets the text left or right of the current selection
+		 * @param bool before
+		 * @param int length
+		 * @private
+		 */
+		_getOuterText = function(before, length) {
+			var	ret	= "",
+				range	= base.cloneSelected();
+
+			range.collapse(false);
+			if(before)
+			{
+				if(!isW3C)
+				{
+					range.moveStart('character', 0-length);
+					ret = range.text;
+				}
+				else
+				{
+					ret = range.startContainer.textContent.substr(0, range.startOffset);
+					ret = ret.substr(Math.max(0, ret.length - length));
+				}
+			}
+			else
+			{
+				if(!isW3C)
+				{
+					range.moveEnd('character', length);
+					ret = range.text;
+				}
+				else
+					ret = range.startContainer.textContent.substr(range.startOffset, length);
+			}
+
+			return ret;
+		};
+
+		/**
+		 * Replaces keys with values based on the current range
+		 *
+		 * @param {Array} rep
+		 * @param {Bool} includePrev If to include text before or just text after
+		 * @param {Bool} repSorted If the keys array is pre sorted
+		 * @param {Int} longestKey Length of the longest key
+		 * @param {Bool} requireWhiteSpace If the key must be surrounded by whitespace
+		 * @function
+		 * @name raplaceKeyword
+		 * @memberOf jQuery.sceditor.rangeHelper.prototype
+		 */
+		base.raplaceKeyword = function(rep, includeAfter, repSorted, longestKey, requireWhiteSpace, curChar) {
+			if(!repSorted)
+				rep.sort(function(a, b){
+					return a.length - b.length;
+				});
+
+			var	maxKeyLen = longestKey || rep[rep.length-1][0].length,
+				before, after, str, i, start, left, pat, lookStart;
+
+			before = after = str = "";
+
+			if(requireWhiteSpace)
+			{
+				// forcing spaces around doesn't work with textRanges as they will select text
+				// on the other side of an image causing space-img-key to be returned as
+				// space-key which would be valid when it's not.
+				if(!isW3C)
+					return false;
+
+				++maxKeyLen;
+			}
+
+			before = _getOuterText(true, maxKeyLen);
+
+			if(includeAfter)
+				after	= _getOuterText(false, maxKeyLen);
+
+			str	= before + (curChar!=null?curChar:"") + after;
+			i	= rep.length;
+			while(i--)
+			{
+				pat = new RegExp("(?:[\\s\xA0\u2002\u2003\u2009])" + $.sceditor.regexEscape(rep[i][0]) + "(?=[\\s\xA0\u2002\u2003\u2009])");
+				lookStart = before.length - 1 - rep[i][0].length;
+
+				if(requireWhiteSpace)
+					--lookStart;
+
+				lookStart = Math.max(0, lookStart);
+
+				if((!requireWhiteSpace && (start = str.indexOf(rep[i][0], lookStart)) > -1) ||
+					(requireWhiteSpace && (start = str.substr(lookStart).search(pat)) > -1))
+				{
+					if(requireWhiteSpace)
+						start += lookStart + 1;
+
+					// make sure the substr is between before and after not entierly in one
+					// or the other
+					if(start > before.length || start+rep[i][0].length + (requireWhiteSpace?1:0) < before.length)
+						continue;
+
+					left = before.length - start;
+					_selectOuterText(left, rep[i][0].length-left-(curChar!=null&&/^\S/.test(curChar)?1:0));
+					base.insertHTML(rep[i][1]);
+					return true;
+				}
+			}
+
+			return false;
+		};
+	};
+
+	/**
+	 * Static DOM helper class
+	 * @class dom
+	 * @name jQuery.sceditor.dom
+	 */
+	$.sceditor.dom =
+	/** @lends jQuery.sceditor.dom */
+	{
+		/**
+		 * Loop all child nodes of the passed node
+		 *
+		 * The function should accept 1 parameter being the node.
+		 * If the function returns false the loop will be exited.
+		 *
+		 * @param {HTMLElement}	node
+		 * @param {function}	func		Function that is called for every node, should accept 1 param for the node
+		 * @param {bool}	innermostFirst	If the innermost node should be passed to the function before it's parents
+		 * @param {bool}	siblingsOnly	If to only traverse the nodes siblings
+		 * @param {bool}	reverse		If to traverse the nodes in reverse
+		 */
+		traverse: function(node, func, innermostFirst, siblingsOnly, reverse) {
+			if(node)
+			{
+				node = reverse ? node.lastChild : node.firstChild;
+
+				while(node != null)
+				{
+					var next = reverse ? node.previousSibling : node.nextSibling;
+
+					if(!innermostFirst && func(node) === false)
+						return false;
+
+					// traverse all children
+					if(!siblingsOnly && this.traverse(node, func, innermostFirst, siblingsOnly, reverse) === false)
+						return false;
+
+					if(innermostFirst && func(node) === false)
+						return false;
+
+					// move to next child
+					node = next;
+				}
+			}
+		},
+
+		/**
+		 * Like traverse but loops in reverse
+		 * @see traverse
+		 */
+		rTraverse: function(node, func, innermostFirst, siblingsOnly) {
+			this.traverse(node, func, innermostFirst, siblingsOnly, true);
+		},
+
+		/**
+		 * List of block level elements seperated by bars (|)
+		 * @type {string}
+		 */
+		blockLevelList: "|body|hr|p|div|h1|h2|h3|h4|h5|h6|address|pre|form|table|tbody|thead|tfoot|th|tr|td|li|ol|ul|blockquote|center|",
+
+		/**
+		 * Checks if an element is inline
+		 *
+		 * @return {bool}
+		 */
+		isInline: function(elm, includeCodeAsBlock) {
+			if(!elm || elm.nodeType !== 1)
+				return true;
+
+			if(includeCodeAsBlock && elm.tagName.toLowerCase() === 'code')
+				return false;
+
+			return $.sceditor.dom.blockLevelList.indexOf("|" + elm.tagName.toLowerCase() + "|") < 0;
+		},
+
+		/**
+		 * Copys the CSS from 1 node to another
+		 *
+		 * @param {HTMLElement} from
+		 * @param {HTMLElement} to
+		 */
+		copyCSS: function(from, to) {
+			to.style.cssText = from.style.cssText;
+		},
+
+		/**
+		 * Fixes block level elements inside in inline elements.
+		 *
+		 * @param {HTMLElement} node
+		 */
+		fixNesting: function(node) {
+			var	base = this,
+				getLastInlineParent = function(node) {
+					while(base.isInline(node.parentNode, true))
+						node = node.parentNode;
+
+					return node;
+				};
+
+			base.traverse(node, function(node) {
+				// if node is an element, and it is blocklevel and the parent isn't block level
+				// then it needs fixing
+				if(node.nodeType === 1 && !base.isInline(node, true) && base.isInline(node.parentNode, true))
+				{
+					var	parent	= getLastInlineParent(node),
+						rParent	= parent.parentNode,
+						before	= base.extractContents(parent, node),
+						middle	= node;
+
+					// copy current styling so when moved out of the parent
+					// it still has the same styling
+					base.copyCSS(middle, middle);
+
+					rParent.insertBefore(before, parent);
+					rParent.insertBefore(middle, parent);
+				}
+			});
+		},
+
+		/**
+		 * Finds the common parent of two nodes
+		 *
+		 * @param {HTMLElement} node1
+		 * @param {HTMLElement} node2
+		 * @return {HTMLElement}
+		 */
+		findCommonAncestor: function(node1, node2) {
+			// not as fast as making two arrays of parents and comparing
+			// but is a lot smaller and as it's currently only used with
+			// fixing invalid nesting it doesn't need to be very fast
+			return $(node1).parents().has($(node2)).first();
+		},
+
+		/**
+		 * Removes unused whitespace from the root and all it's children
+		 *
+		 * @param HTMLElement root
+		 * @return void
+		 */
+		removeWhiteSpace: function(root) {
+			// 00A0 is non-breaking space which should not be striped
+			var regex = /[^\S|\u00A0]+/g;
+
+			this.traverse(root, function(node) {
+				if(node.nodeType === 3 && $(node).parents('code, pre').length === 0 && node.nodeValue)
+				{
+					// new lines in text nodes are always ignored in normal handling
+					node.nodeValue = node.nodeValue.replace(/[\r\n]/, "");
+
+					//remove empty nodes
+					if(!node.nodeValue.length)
+					{
+						node.parentNode.removeChild(node);
+						return;
+					}
+
+					if(!/\S|\u00A0/.test(node.nodeValue))
+						node.nodeValue = " ";
+					else if(regex.test(node.nodeValue))
+						node.nodeValue = node.nodeValue.replace(regex, " ");
+				}
+			});
+		},
+
+		/**
+		 * Extracts all the nodes between the start and end nodes
+		 *
+		 * @param {HTMLElement} startNode	The node to start extracting at
+		 * @param {HTMLElement} endNode		The node to stop extracting at
+		 * @return {DocumentFragment}
+		 */
+		extractContents: function(startNode, endNode) {
+			var	base		= this,
+				$commonAncestor	= base.findCommonAncestor(startNode, endNode),
+				commonAncestor	= !$commonAncestor?null:$commonAncestor.get(0),
+				startReached	= false,
+				endReached	= false;
+
+			return (function extract(root) {
+				var df = startNode.ownerDocument.createDocumentFragment();
+
+				base.traverse(root, function(node) {
+					// if end has been reached exit loop
+					if(endReached || (node === endNode && startReached))
+					{
+						endReached = true;
+						return false;
+					}
+
+					if(node === startNode)
+						startReached = true;
+
+					var c, n;
+					if(startReached)
+					{
+						// if the start has been reached and this elm contains
+						// the end node then clone it
+						if(jQuery.contains(node, endNode) && node.nodeType === 1)
+						{
+							c = extract(node);
+							n = node.cloneNode(false);
+
+							n.appendChild(c);
+							df.appendChild(n);
+						}
+						// otherwise just move it
+						else
+							df.appendChild(node);
+					}
+					// if this node contains the start node then add it
+					else if(jQuery.contains(node, startNode) && node.nodeType === 1)
+					{
+						c = extract(node);
+						n = node.cloneNode(false);
+
+						n.appendChild(c);
+						df.appendChild(n);
+					}
+				}, false);
+
+				return df;
+			}(commonAncestor));
+		}
+	};
+
+	/**
+	 * Static command helper class
+	 * @class command
+	 * @name jQuery.sceditor.command
+	 */
+	$.sceditor.command =
+	/** @lends jQuery.sceditor.command */
+	{
+		/**
+		 * Gets a command
+		 *
+		 * @param {String} name
+		 * @return {Object|null}
+		 * @since v1.3.5
+		 */
+		get: function(name) {
+			return $.sceditor.commands[name] || null;
+		},
+
+		/**
+		 * <p>Adds a command to the editor or updates an exisiting
+		 * command if a command with the specified name already exists.</p>
+		 *
+		 * <p>Once a command is add it can be included in the toolbar by
+		 * adding it's name to the toolbar option in the constructor. It
+		 * can also be executed manually by calling {@link jQuery.sceditor.execCommand}</p>
+		 *
+		 * @example
+		 * $.sceditor.command.set("hello",
+		 * {
+		 *     exec: function() {
+		 *         alert("Hello World!");
+		 *     }
+		 * });
+		 *
+		 * @param {String} name
+		 * @param {Object} cmd
+		 * @return {this|false} Returns false if name or cmd is false
+		 * @since v1.3.5
+		 */
+		set: function(name, cmd) {
+			if(!name || !cmd)
+				return false;
+
+			// merge any existing command properties
+			cmd = $.extend($.sceditor.commands[name] || {}, cmd);
+
+			cmd.remove = function() { $.sceditor.command.remove(name); };
+
+			$.sceditor.commands[name] = cmd;
+			return this;
+		},
+
+		/**
+		 * Removes a command
+		 *
+		 * @param {String} name
+		 * @return {this}
+		 * @since v1.3.5
+		 */
+		remove: function(name) {
+			if($.sceditor.commands[name])
+				delete $.sceditor.commands[name];
+
+			return this;
+		}
+	};
+
+	/**
+	 * Checks if a command with the specified name exists
+	 *
+	 * @param {String} name
+	 * @return {Bool}
+	 * @deprecated Since v1.3.5
+	 * @memberOf jQuery.sceditor
+	 */
+	$.sceditor.commandExists = function(name) {
+		return !!$.sceditor.command.get(name);
+	};
+
+	/**
+	 * Adds/updates a command.
+	 *
+	 * Only name and exec are required. Exec is only required if
+	 * the command dose not already exist.
+	 *
+	 * @param {String}		name		The commands name
+	 * @param {String|Function}	exec		The commands exec function or string for the native execCommand
+	 * @param {String}		tooltip		The commands tooltip text
+	 * @param {Function}		keypress	Function that gets called every time a key is pressed
+	 * @param {Function|Array}	txtExec		Called when the command is executed in source mode or array containing prepend and optional append
+	 * @return {Bool}
+	 * @deprecated Since v1.3.5
+	 * @memberOf jQuery.sceditor
+	 */
+	$.sceditor.setCommand = function(name, exec, tooltip, keypress, txtExec) {
+		return !!$.sceditor.command.set(name, {
+			exec: exec,
+			tooltip: tooltip,
+			keypress: keypress,
+			txtExec: txtExec
+		});
+	};
+
+	$.sceditor.defaultOptions = {
+		// Toolbar buttons order and groups. Should be comma seperated and have a bar | to seperate groups
+		toolbar:	"bold,italic,underline,strike,subscript,superscript|left,center,right,justify|" +
+				"font,size,color,removeformat|cut,copy,paste,pastetext|bulletlist,orderedlist|" +
+				"table|code,quote|horizontalrule,image,email,link,unlink|emoticon,youtube,date,time|" +
+				"ltr,rtl|print,source",
+
+		// Stylesheet to include in the WYSIWYG editor. Will style the WYSIWYG elements
+		style: "jquery.sceditor.default.css",
+
+		// Comma seperated list of fonts for the font selector
+		fonts: "Arial,Arial Black,Comic Sans MS,Courier New,Georgia,Impact,Sans-serif,Serif,Times New Roman,Trebuchet MS,Verdana",
+
+		// Colors should be comma seperated and have a bar | to signal a new column. If null the colors will be auto generated.
+		colors: null,
+
+		locale: "en",
+
+		charset: "utf-8",
+
+		// compatibility mode for if you have emoticons such as :/ This mode requires
+		// emoticons to be surrounded by whitespace or end of line chars. This mode
+		// has limited As You Type emoticon converstion support (end of line chars)
+		// are not accepted as whitespace so only emoticons surrounded by whitespace
+		// will work
+		emoticonsCompat: false,
+		emoticonsRoot: '',
+		emoticons:	{
+					dropdown: {
+						":)": "emoticons/smile.png",
+						":angel:": "emoticons/angel.png",
+						":angry:": "emoticons/angry.png",
+						"8-)": "emoticons/cool.png",
+						":'(": "emoticons/cwy.png",
+						":ermm:": "emoticons/ermm.png",
+						":D": "emoticons/grin.png",
+						"<3": "emoticons/heart.png",
+						":(": "emoticons/sad.png",
+						":O": "emoticons/shocked.png",
+						":P": "emoticons/tongue.png",
+						";)": "emoticons/wink.png"
+					},
+					more: {
+						":alien:": "emoticons/alien.png",
+						":blink:": "emoticons/blink.png",
+						":blush:": "emoticons/blush.png",
+						":cheerful:": "emoticons/cheerful.png",
+						":devil:": "emoticons/devil.png",
+						":dizzy:": "emoticons/dizzy.png",
+						":getlost:": "emoticons/getlost.png",
+						":happy:": "emoticons/happy.png",
+						":kissing:": "emoticons/kissing.png",
+						":ninja:": "emoticons/ninja.png",
+						":pinch:": "emoticons/pinch.png",
+						":pouty:": "emoticons/pouty.png",
+						":sick:": "emoticons/sick.png",
+						":sideways:": "emoticons/sideways.png",
+						":silly:": "emoticons/silly.png",
+						":sleeping:": "emoticons/sleeping.png",
+						":unsure:": "emoticons/unsure.png",
+						":woot:": "emoticons/w00t.png",
+						":wassat:": "emoticons/wassat.png"
+					},
+					hidden: {
+						":whistling:": "emoticons/whistling.png",
+						":love:": "emoticons/wub.png"
+					}
+				},
+
+		// Width of the editor. Set to null for automatic with
+		width: null,
+
+		// Height of the editor including toolbat. Set to null for automatic height
+		height: null,
+
+		// If to allow the editor to be resized
+		resizeEnabled: true,
+
+		// Min resize to width, set to null for half textarea width or -1 for unlimited
+		resizeMinWidth: null,
+		// Min resize to height, set to null for half textarea height or -1 for unlimited
+		resizeMinHeight: null,
+		// Max resize to height, set to null for double textarea height or -1 for unlimited
+		resizeMaxHeight: null,
+		// Max resize to width, set to null for double textarea width or -1 for unlimited
+		resizeMaxWidth: null,
+
+		getHtmlHandler: null,
+		getTextHandler: null,
+
+		// date format. year, month and day will be replaced with the users current year, month and day.
+		dateFormat: "year-month-day",
+
+		toolbarContainer: null,
+
+		// Curently experimental
+		enablePasteFiltering: false,
+
+		readOnly: false,
+		rtl: false,
+		autofocus: false,
+		autoExpand: false,
+
+		// If to run the editor without WYSIWYG support
+		runWithoutWysiwygSupport: false,
+
+		id: null,
+
+		//add css to dropdown menu (eg. z-index)
+		dropDownCss: { }
+	};
+
+	$.fn.sceditor = function (options) {
+		if((!options || !options.runWithoutWysiwygSupport) && !$.sceditor.isWysiwygSupported())
+			return;
+
+		return this.each(function () {
+			(new $.sceditor(this, options));
+		});
+	};
+})(jQuery, window, document);
+
+(function($) {
+	var extensionMethods = {
+		InsertText: function(text, bClear) {
+			var bIsSource = this.inSourceMode();
+
+			// @TODO make it put the quote close to the current selection
+
+			if (!bIsSource)
+				this.toggleTextMode();
+
+			var current_value = bClear ? text + "\n" : this.getTextareaValue(false) + "\n" + text + "\n";
+			this.setTextareaValue(current_value);
+
+			if (!bIsSource)
+				this.toggleTextMode();
+
+		},
+		getText: function() {
+			if(this.inSourceMode())
+				var current_value = this.getTextareaValue(false);
+			else
+				var current_value = this.getWysiwygEditorValue();
+
+			return current_value;
+		},
+		appendEmoticon: function (code, emoticon) {
+			if (code == '')
+				line.append($('<br />'));
+			else
+				line.append($('<img />')
+					.attr({
+						src: emoticon,
+						alt: code,
+					})
+					.click(function (e) {
+						var	start = '', end = '';
+						
+						if (base.options.emoticonsCompat)
+						{
+							start = '<span> ';
+							end   = ' </span>';
+						}
+
+						if (base.inSourceMode())
+							base.textEditorInsertText(' ' + $(this).attr('alt') + ' ');
+						else
+							base.wysiwygEditorInsertHtml(start + '<img src="' + $(this).attr("src") +
+								'" data-sceditor-emoticon="' + $(this).attr('alt') + '" />' + end);
+
+						e.preventDefault();
+					})
+				);
+
+			if (line.children().length > 0)
+				content.append(line);
+
+			$(".sceditor-toolbar").append(content);
+		},
+		storeLastState: function (){
+			this.wasSource = this.inSourceMode();
+		},
+		setTextMode: function () {
+			if (!this.inSourceMode())
+				this.toggleTextMode();
+		},
+		createPermanentDropDown: function() {
+				var	emoticons	= $.extend({}, this.options.emoticons.dropdown);
+				var popup_exists = false;
+				content = $('<div class="sceditor-insertemoticon" />');
+				line = $('<div />');
+
+				base = this;
+				for (smiley_popup in this.options.emoticons.popup)
+				{
+					popup_exists = true;
+					break;
+				}
+				if (popup_exists)
+				{
+					this.options.emoticons.more = this.options.emoticons.popup;
+					moreButton = $('<div class="sceditor-more-button" />').attr({class: "sceditor-more"}).text('[' + this._('More') + ']').click(function () {
+						if ($(".sceditor-smileyPopup").length > 0)
+						{
+							$(".sceditor-smileyPopup").fadeIn('fast');
+						}
+						else
+						{
+							var emoticons = $.extend({}, base.options.emoticons.popup);
+							var popup_position;
+							var titlebar = $('<div class="catbg sceditor-popup-grip"/>');
+								popupContent = $('<div id="sceditor-popup"/>');
+								allowHide = true;
+								popupContent.append(titlebar);
+								line = $('<div />');
+								closeButton = $('<span />').text('[' + base._('Close') + ']').click(function () {
+									$(".sceditor-smileyPopup").fadeOut('fast');
+								});
+
+							$.each(emoticons, base.appendEmoticon);
+
+							if (line.children().length > 0)
+								popupContent.append(line);
+							if (typeof closeButton !== "undefined")
+								popupContent.append(closeButton);
+
+							// IE needs unselectable attr to stop it from unselecting the text in the editor.
+							// The editor can cope if IE does unselect the text it's just not nice.
+							if(base.ieUnselectable !== false) {
+								content = $(content);
+								content.find(':not(input,textarea)').filter(function() { return this.nodeType===1; }).attr('unselectable', 'on');
+							}
+
+							$dropdown = $('<div class="sceditor-dropdown sceditor-smileyPopup" />').append(popupContent);
+
+							$dropdown.appendTo($('body'));
+							dropdownIgnoreLastClick = true;
+							$dropdown.css({
+								position: "fixed",
+								top: $(window).height() * 0.2,
+								left: $(window).width() * 0.5 - ($dropdown.width() / 2),
+								"max-width": "50%"
+							});
+
+							$('.sceditor-smileyPopup').animaDrag({ 
+								speed: 150, 
+								interval: 120, 
+								grip: '.sceditor-popup-grip' 
+							});
+							// stop clicks within the dropdown from being handled
+							$dropdown.click(function (e) {
+								e.stopPropagation();
+							});
+						}
+					});
+				}
+				$.each(emoticons, base.appendEmoticon);
+				if (typeof moreButton !== "undefined")
+					content.append(moreButton);
+		}
+	};
+
+	$.extend(true, $['sceditor'].prototype, extensionMethods);
+})(jQuery);
+
+$.sceditor.setCommand(
+	'ftp',
+	function (caller) {
+		var	editor  = this,
+			content = $(this._('<form><div><label for="link">{0}</label> <input type="text" id="link" value="ftp://" /></div>' +
+					'<div><label for="des">{1}</label> <input type="text" id="des" value="" /></div></form>',
+				this._("URL:"),
+				this._("Description (optional):")
+			))
+			.submit(function () {return false;});
+
+		content.append($(
+			this._('<div><input type="button" class="button" value="{0}" /></div>',
+				this._("Insert")
+			)).click(function (e) {
+			var val = $(this).parent("form").find("#link").val(),
+				description = $(this).parent("form").find("#des").val();
+
+			if(val !== "" && val !== "ftp://") {
+				// needed for IE to reset the last range
+				editor.focus();
+
+				if(!editor.getRangeHelper().selectedHtml() || description)
+				{
+					if(!description)
+						description = val;
+					
+					editor.wysiwygEditorInsertHtml('<a href="' + val + '">' + description + '</a>');
+				}
+				else
+					editor.execCommand("createlink", val);
+			}
+
+			editor.closeDropDown(true);
+			e.preventDefault();
+		}));
+
+		editor.createDropDown(caller, "insertlink", content);
+	},
+	'Insert FTP Link'
+);
+$.sceditor.setCommand(
+	'glow',
+	function () {
+		this.wysiwygEditorInsertHtml('[glow=red,2,300]', '[/glow]');
+	},
+	'Glow'
+);
+$.sceditor.setCommand(
+	'shadow',
+	function () {
+		this.wysiwygEditorInsertHtml('[shadow=red,left]', '[/shadow]');
+	},
+	'Shadow'
+);
+$.sceditor.setCommand(
+	'tt',
+	function () {
+		this.wysiwygEditorInsertHtml('<tt>', '</tt>');
+	},
+	'Teletype'
+);
+$.sceditor.setCommand(
+	'pre',
+	function () {
+		this.wysiwygEditorInsertHtml('<pre>', '</pre>');
+	},
+	'Pre'
+);
+
+/**
+ * AnimaDrag
+ * Animated jQuery Drag and Drop Plugin
+ * Version 0.5.1 beta
+ * Author Abel Mohler
+ * Released with the MIT License: http://www.opensource.org/licenses/mit-license.php
+ */
+(function($){
+	$.fn.animaDrag = function(o, callback) {
+		var defaults = {
+			speed: 400,
+			interval: 300,
+			easing: null,
+			cursor: 'move',
+			boundary: document.body,
+			grip: null,
+			overlay: true,
+			after: function(e) {},
+			during: function(e) {},
+			before: function(e) {},
+			afterEachAnimation: function(e) {}
+		}
+		if(typeof callback == 'function') {
+				defaults.after = callback;
+		}
+		o = $.extend(defaults, o || {});
+		return this.each(function() {
+			var id, startX, startY, draggableStartX, draggableStartY, dragging = false, Ev, draggable = this,
+			grip = ($(this).find(o.grip).length > 0) ? $(this).find(o.grip) : $(this);
+			if(o.boundary) {
+				var limitTop = $(o.boundary).offset().top, limitLeft = $(o.boundary).offset().left,
+				limitBottom = limitTop + $(o.boundary).innerHeight(), limitRight = limitLeft + $(o.boundary).innerWidth();
+			}
+			grip.mousedown(function(e) {
+				o.before.call(draggable, e);
+
+				var lastX, lastY;
+				dragging = true;
+
+				Ev = e;
+
+				startX = lastX = e.pageX;
+				startY = lastY = e.pageY;
+				draggableStartX = $(draggable).offset().left;
+				draggableStartY = $(draggable).offset().top;
+
+				$(draggable).css({
+					position: 'absolute',
+					left: draggableStartX + 'px',
+					top: draggableStartY + 'px',
+					cursor: o.cursor,
+					zIndex: '1010'
+				}).addClass('anima-drag').appendTo(document.body);
+				if(o.overlay && $('#anima-drag-overlay').length == 0) {
+					$('<div id="anima-drag-overlay"></div>').css({
+						position: 'absolute',
+						top: '0',
+						left: '0',
+						zIndex: '1000',
+						width: $(document.body).outerWidth() + 'px',
+						height: $(document.body).outerHeight() + 'px'
+					}).appendTo(document.body);
+				}
+				else if(o.overlay) {
+					$('#anima-drag-overlay').show();
+				}
+				id = setInterval(function() {
+					if(lastX != Ev.pageX || lastY != Ev.pageY) {
+						var positionX = draggableStartX - (startX - Ev.pageX), positionY = draggableStartY - (startY - Ev.pageY);
+						if(positionX < limitLeft && o.boundary) {
+							positionX = limitLeft;
+						}
+						else if(positionX + $(draggable).innerWidth() > limitRight && o.boundary) {
+							positionX = limitRight - $(draggable).outerWidth();
+						}
+						if(positionY < limitTop && o.boundary) {
+							positionY = limitTop;
+						}
+						else if(positionY + $(draggable).innerHeight() > limitBottom && o.boundary) {
+							positionY = limitBottom - $(draggable).outerHeight();
+						}
+						$(draggable).stop().animate({
+							left: positionX + 'px',
+							top: positionY + 'px'
+						}, o.speed, o.easing, function(){o.afterEachAnimation.call(draggable, Ev)});
+					}
+					lastX = Ev.pageX;
+					lastY = Ev.pageY;
+				}, o.interval);
+				($.browser.safari || e.preventDefault());
+			});
+			$(document).mousemove(function(e) {
+				if(dragging) {
+					Ev = e;
+					o.during.call(draggable, e);
+				}
+			});
+			$(document).mouseup(function(e) {
+				if(dragging) {
+					$(draggable).css({
+						cursor: '',
+						zIndex: '990'
+					}).removeClass('anima-drag');
+					$('#anima-drag-overlay').hide().appendTo(document.body);
+					clearInterval(id);
+					o.after.call(draggable, e);
+					dragging = false;
+				}
+			});
+		});
+	}
+})(jQuery);

+ 2 - 4
Themes/default/scripts/smf_jquery_plugins.js

@@ -180,8 +180,7 @@
  * <http://cherne.net/brian/resources/jquery.hoverIntent.html>
  * 
  * hoverIntent is currently available for use in all personal or commercial 
- * projects under both MIT and GPL licenses. This means that you can choose 
- * the license that best suits your project, and use it accordingly.
+ * projects under MIT license.
  * 
  * // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
  * $("ul li").hoverIntent( showNav , hideNav );
@@ -303,9 +302,8 @@
  * Superfish v1.4.8 - jQuery menu widget
  * Copyright (c) 2008 Joel Birch
  *
- * Dual licensed under the MIT and GPL licenses:
+ * Licensed under the MIT license:
  * 	http://www.opensource.org/licenses/mit-license.php
- * 	http://www.gnu.org/licenses/gpl.html
  *
  * CHANGELOG: http://users.tpg.com.au/j_birch/plugins/superfish/changelog.txt
  */

+ 13 - 6
Themes/default/scripts/spellcheck.js

@@ -18,7 +18,7 @@ function spellCheck(formName, fieldName)
 	var aWordCharacters = ['-', '\''];
 
 	var aWords = new Array(), aResult = new Array();
-	var sText = document.forms[formName][fieldName].value;
+	var sText = $('#' + fieldName).data("sceditor").getTextareaValue(false);
 	var bInCode = false;
 	var iOffset1, iOffset2;
 
@@ -235,11 +235,7 @@ function nextWord(ignoreall)
 		mispstr = mispstr.replace(/_\|_/g, "\n");
 
 		// Get a handle to the field we need to re-populate.
-		window.opener.document.forms[spell_formname][spell_fieldname].value = mispstr;
-		if (!window.opener.spellCheckDone)
-			window.opener.document.forms[spell_formname][spell_fieldname].focus();
-		else
-			window.opener.spellCheckDone();
+		window.opener.spellCheckSetText(mispstr, spell_fieldname);
 
 		window.close();
 		return true;
@@ -294,4 +290,15 @@ function htmlspecialchars(thetext)
 function openSpellWin(width, height)
 {
 	window.open("", "spellWindow", "toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=no,width=" + width + ",height=" + height);
+}
+
+function spellCheckGetText(editorID)
+{
+	return $("#" + editorID).data("sceditor").getTextareaValue(false);
+}
+function spellCheckSetText(text, editorID)
+{
+	$("#" + editorID).data("sceditor").InsertText(text, true);
+	if (!$("#" + editorID).data("sceditor").wasSource)
+		$("#" + editorID).data("sceditor").toggleTextMode();
 }

+ 2 - 2
index.php

@@ -193,7 +193,7 @@ function smf_main()
 	if (!empty($topic) && empty($board_info['cur_topic_approved']) && !allowedTo('approve_posts') && ($user_info['id'] != $board_info['cur_topic_starter'] || $user_info['is_guest']))
 		fatal_lang_error('not_a_topic', false);
 
-	$no_stat_actions = array('dlattach', 'findmember', 'jseditor', 'jsoption', 'requestmembers', 'smstats', '.xml', 'xmlhttp', 'verificationcode', 'viewquery', 'viewsmfile');
+	$no_stat_actions = array('dlattach', 'findmember', 'jsoption', 'requestmembers', 'smstats', '.xml', 'xmlhttp', 'verificationcode', 'viewquery', 'viewsmfile');
 	call_integration_hook('integrate_pre_log_stats', $no_stat_actions);
 	// Do some logging, unless this is an attachment, avatar, toggle of editor buttons, theme option, XML feed etc.
 	if (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], $no_stat_actions))
@@ -281,9 +281,9 @@ function smf_main()
 		'help' => array('Help.php', 'ShowHelp'),
 		'helpadmin' => array('Help.php', 'ShowAdminHelp'),
 		'im' => array('PersonalMessage.php', 'MessageMain'),
-		'jseditor' => array('Subs-Editor.php', 'EditorMain'),
 		'jsmodify' => array('Post.php', 'JavaScriptModify'),
 		'jsoption' => array('Themes.php', 'SetJavaScript'),
+		'loadeditorlocale' => array('Subs-Editor.php', 'loadLocale'),
 		'lock' => array('Topic.php', 'LockTopic'),
 		'lockvoting' => array('Poll.php', 'LockVoting'),
 		'login' => array('LogInOut.php', 'Login'),

Some files were not shown because too many files changed in this diff