includes/clientside/static/rank-manager.js
author Dan Fuhry <dan@enanocms.org>
Thu, 28 Oct 2010 03:05:31 -0400
changeset 1308 f9bee9b125ee
parent 1227 bdac73ed481e
permissions -rw-r--r--
Parser updates. Added the "styled" keyword to wikitables to allow them to be styled using the current theme's standard table skinning, and changes to how the image tag parser decides how to display an image (framed, inline or raw).

/**
 * Creates a control that can be used to edit a rank.
 */

var RankEditorControl = function(rankdata)
{
	this.rankdata = ( typeof(rankdata) == 'object' ) ? rankdata : {};
	if ( !this.rankdata.rank_style )
	{
		this.rankdata.rank_style = '';
	}
	
	// have the browser parse CSS for us and use an anchor to be as close
	// as possible in calculating CSS
	
	// this is kind of a hack as it relies on setAttribute/getAttribute in
	// order to obtain stringified versions of CSS data
	var cssobj = document.createElement('a');
	cssobj.setAttribute('style', this.rankdata.rank_style);
	
	this.style_sim_obj = cssobj;
	
	// figure out if we're editing or creating
	this.editing = ( typeof(this.rankdata.rank_id) == 'number' );
	
	this.render = function()
	{
		var editor = document.createElement('div');
		editor.className = 'tblholder';
		// stash this editor instance in the parent div for later function calls
		editor.editor = this;
		this.wrapperdiv = editor;
		editor.style.width = '100%';
		
		// tables suck.
		var table = document.createElement('table');
		table.setAttribute('cellspacing', '1');
		table.setAttribute('cellpadding', '4');
		table.setAttribute('width', '100%');
		
		// heading: "Edit rank: foo" or "Create a new rank"
		var tr_head = document.createElement('tr');
		var th_head = document.createElement('th');
		th_head.setAttribute('colspan', '2');
		if ( this.editing )
		{
			var th_head_string = 'acpur_th_edit_rank';
			var th_head_data = { rank_title: $lang.get(this.rankdata.rank_title) };
		}
		else
		{
			var th_head_string = 'acpur_th_create_rank';
			var th_head_data = { };
		}
		th_head.appendChild(document.createTextNode($lang.get(th_head_string, th_head_data)));
		tr_head.appendChild(th_head);
		this.th_head = th_head;
		table.appendChild(tr_head);
		
		// row: rank title
		var tr_title = document.createElement('tr');
		var td_title_l = document.createElement('td');
		var td_title_f = document.createElement('td');
		
		td_title_l.className = td_title_f.className = 'row1';
		
		td_title_l.appendChild(document.createTextNode($lang.get('acpur_field_rank_title')));
		
		// field: rank title
		var f_rank_title = document.createElement('input');
		f_rank_title.type = 'text';
		f_rank_title.size = '30';
		f_rank_title.value = ( this.editing ) ? this.rankdata.rank_title : '';
		f_rank_title.editor = this;
		f_rank_title.onkeyup = function()
		{
			this.editor.renderPreview();
		}
		this.f_rank_title = f_rank_title;
		td_title_f.appendChild(f_rank_title);
		
		tr_title.appendChild(td_title_l);
		tr_title.appendChild(td_title_f);
		table.appendChild(tr_title);
		
		// row: basic style options
		var tr_basic = document.createElement('tr');
		var td_basic_l = document.createElement('td');
		var td_basic_f = document.createElement('td');
		
		td_basic_l.className = td_basic_f.className = 'row2';
		
		td_basic_l.appendChild(document.createTextNode($lang.get('acpur_field_style_basic')));
		
		// fieldset: basic style options
		// field: bold
		var l_basic_bold = document.createElement('label');
		var f_basic_bold = document.createElement('input');
		f_basic_bold.type = 'checkbox';
		f_basic_bold.checked = ( this.style_sim_obj.style.fontWeight == 'bold' ) ? true : false;
		f_basic_bold.editor = this;
		f_basic_bold.onclick = function()
		{
			this.editor.style_sim_obj.style.fontWeight = ( this.checked ) ? 'bold' : null;
			this.editor.renderPreview();
		}
		l_basic_bold.style.fontWeight = 'bold';
		l_basic_bold.appendChild(f_basic_bold);
		l_basic_bold.appendChild(document.createTextNode(' '));
		l_basic_bold.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_bold')));
		
		// field: italic
		var l_basic_italic = document.createElement('label');
		var f_basic_italic = document.createElement('input');
		f_basic_italic.type = 'checkbox';
		f_basic_italic.checked = ( this.style_sim_obj.style.fontStyle == 'italic' ) ? true : false;
		f_basic_italic.editor = this;
		f_basic_italic.onclick = function()
		{
			this.editor.style_sim_obj.style.fontStyle = ( this.checked ) ? 'italic' : null;
			this.editor.renderPreview();
		}
		l_basic_italic.style.fontStyle = 'italic';
		l_basic_italic.appendChild(f_basic_italic);
		l_basic_italic.appendChild(document.createTextNode(' '));
		l_basic_italic.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_italic')));
		
		// field: underline
		var l_basic_underline = document.createElement('label');
		var f_basic_underline = document.createElement('input');
		f_basic_underline.type = 'checkbox';
		f_basic_underline.checked = ( this.style_sim_obj.style.textDecoration == 'underline' ) ? true : false;
		f_basic_underline.editor = this;
		f_basic_underline.onclick = function()
		{
			this.editor.style_sim_obj.style.textDecoration = ( this.checked ) ? 'underline' : null;
			this.editor.renderPreview();
		}
		l_basic_underline.style.textDecoration = 'underline';
		l_basic_underline.appendChild(f_basic_underline);
		l_basic_underline.appendChild(document.createTextNode(' '));
		l_basic_underline.appendChild(document.createTextNode($lang.get('acpur_field_style_basic_underline')));
		
		// finish up formatting row#1
		td_basic_f.appendChild(l_basic_bold);
		td_basic_f.appendChild(document.createTextNode(' '));
		td_basic_f.appendChild(l_basic_italic);
		td_basic_f.appendChild(document.createTextNode(' '));
		td_basic_f.appendChild(l_basic_underline);
		
		tr_basic.appendChild(td_basic_l);
		tr_basic.appendChild(td_basic_f);
		table.appendChild(tr_basic);
		
		// row: rank color
		var tr_color = document.createElement('tr');
		var td_color_l = document.createElement('td');
		var td_color_f = document.createElement('td');
		
		td_color_l.className = td_color_f.className = 'row1';
		
		td_color_l.appendChild(document.createTextNode($lang.get('acpur_field_style_color')));
		
		// field: rank color
		var f_rank_color = document.createElement('input');
		f_rank_color.type = 'text';
		f_rank_color.size = '7';
		f_rank_color.value = ( this.editing ) ? this.rgb2hex(this.style_sim_obj.style.color) : '';
		f_rank_color.style.backgroundColor = this.style_sim_obj.style.color;
		f_rank_color.editor = this;
		this.f_rank_color = f_rank_color;
		f_rank_color.onkeyup = function(e)
		{
			if ( !e.keyCode )
				e = window.event;
			if ( !e )
				return false;
			var chr = (String.fromCharCode(e.keyCode)).toLowerCase();
			this.value = this.value.replace(/[^a-fA-F0-9]/g, '');
			if ( this.value.length > 6 )
			{
				this.value = this.value.substr(0, 6);
			}
			if ( this.value.length == 6 || this.value.length == 3 )
			{
				this.style.backgroundColor = '#' + this.value;
				this.editor.style_sim_obj.style.color = '#' + this.value;
				this.style.color = '#' + this.editor.determineLightness(this.value);
				this.editor.renderPreview();
			}
			else if ( this.value.length == 0 )
			{
				this.style.backgroundColor = null;
				this.editor.style_sim_obj.style.color = null;
				this.editor.renderPreview();
			}
		}
		td_color_f.appendChild(f_rank_color);
		
		tr_color.appendChild(td_color_l);
		tr_color.appendChild(td_color_f);
		table.appendChild(tr_color);
		
		// field: additional CSS
		var tr_css = document.createElement('tr');
		
		var td_css_l = document.createElement('td');
		td_css_l.className = 'row2';
		td_css_l.appendChild(document.createTextNode($lang.get('acpur_field_style_css')));
		tr_css.appendChild(td_css_l);
		
		var td_css_f = document.createElement('td');
		td_css_f.className = 'row2';
		var f_css = document.createElement('input');
		f_css.type = 'text';
		f_css.value = this.stripBasicCSSAttributes(this.rankdata.rank_style);
		f_css.style.width = '98%';
		f_css.editor = this;
		f_css.onkeyup = function()
		{
			if ( !(trim(this.value)).match(/^((([a-z-]+):(.+?);)+)?$/) )
				return;
			var newcss = this.editor.stripExtendedCSSAttributes(String(this.editor.style_sim_obj.getAttribute('style'))) + ' ' + this.value;
			this.editor.preview_div.setAttribute('style', 'font-size: x-large; ' + newcss);
			this.editor.style_sim_obj.setAttribute('style', newcss);
		}
		this.f_css = f_css;
		td_css_f.appendChild(f_css);
		tr_css.appendChild(td_css_f);
		table.appendChild(tr_css);
		
		// "field": preview
		var tr_preview = document.createElement('tr');
		var td_preview_l = document.createElement('td');
		td_preview_l.className = 'row1';
		td_preview_l.appendChild(document.createTextNode($lang.get('acpur_field_preview')));
		tr_preview.appendChild(td_preview_l);
		
		var td_preview_f = document.createElement('td');
		td_preview_f.className = 'row1';
		var div_preview = document.createElement('a');
		this.preview_div = div_preview;
		div_preview.style.fontSize = 'x-large';
		div_preview.appendChild(document.createTextNode(''));
		div_preview.firstChild.nodeValue = ( this.editing ) ? this.rankdata.rank_title : '';
		td_preview_f.appendChild(div_preview);
		tr_preview.appendChild(td_preview_f);
		
		table.appendChild(tr_preview);
		
		// submit button
		var tr_submit = document.createElement('tr');
		var th_submit = document.createElement('th');
		th_submit.className = 'subhead';
		th_submit.setAttribute('colspan', '2');
		var btn_submit = document.createElement('input');
		btn_submit.type = 'submit';
		btn_submit.value = ( this.editing ) ? $lang.get('acpur_btn_save') : $lang.get('acpur_btn_create_submit');
		btn_submit.editor = this;
		btn_submit.style.fontWeight = 'bold';
		btn_submit.onclick = function(e)
		{
			this.editor.submitEvent(e);
		}
		this.btn_submit = btn_submit;
		th_submit.appendChild(btn_submit);
		
		// delete button
		if ( this.editing )
		{
			var btn_delete = document.createElement('input');
			btn_delete.type = 'button';
			btn_delete.value = $lang.get('acpur_btn_delete');
			btn_delete.editor = this;
			btn_delete.onclick = function(e)
			{
				this.editor.deleteEvent(e);
			}
			th_submit.appendChild(document.createTextNode(' '));
			th_submit.appendChild(btn_delete);
		}
		
		tr_submit.appendChild(th_submit);
		
		table.appendChild(tr_submit);
		
		// render preview
		this.renderPreview();
		
		// finalize the editor table
		editor.appendChild(table);
		
		// stash rendered editor
		this.editordiv = editor;
		
		// send output
		return editor;
	}
	
	/**
 	* Takes the existing editor div and transforms the necessary elements so that it goes from "create" mode to "edit" mode
 	* @param object Edit data - same format as the rankdata parameter to the constructor, but we should only need rank_id
 	*/
	
	this.transformToEditor = function(rankdata)
	{
		// we need a rank ID
		if ( typeof(rankdata.rank_id) != 'number' )
			return false;
		
		if ( this.editing )
			return false;
		
		this.editing = true;
		
		this.rankdata = rankdata;
		this.rankdata.rank_title = this.f_rank_title.value;
		this.rankdata.rank_style = this.getCSS();
		
		// transform various controls
		this.th_head.firstChild.nodeValue = $lang.get('acpur_th_edit_rank', {
				rank_title: $lang.get(this.rankdata.rank_title)
			});
		this.btn_submit.value = $lang.get('acpur_btn_save');
		
		// add the delete button
		var th_submit = this.btn_submit.parentNode;
		
		var btn_delete = document.createElement('input');
		btn_delete.type = 'button';
		btn_delete.value = $lang.get('acpur_btn_delete');
		btn_delete.editor = this;
		btn_delete.onclick = function(e)
		{
			this.editor.deleteEvent(e);
		}
		th_submit.appendChild(document.createTextNode(' '));
		th_submit.appendChild(btn_delete);
		
		return true;
	}
	
	/**
 	* Takes a hex color, averages the three channels, and returns either 'ffffff' or '000000' depending on the luminosity of the color.
 	* @param string
 	* @return string
 	*/
	
	this.determineLightness = function(hexval)
	{
		var rgb = this.hex2rgb(hexval);
		var lumin = ( rgb[0] + rgb[1] + rgb[2] ) / 3;
		return ( lumin > 60 ) ? '000000' : 'ffffff';
	}
	
	/**
 	* Strips out basic CSS attributes (color, font-weight, font-style, text-decoration) from a snippet of CSS.
 	* @param string
 	* @return string
 	*/
	
	this.stripBasicCSSAttributes = function(css)
	{
		return trim(css.replace(/(color|font-weight|font-style|text-decoration): ?([A-z0-9# ,\(\)]+);/g, ''));
	}
	
	/**
 	* Strips out all but basic CSS attributes.
 	* @param string
 	* @return string
 	*/
	
	this.stripExtendedCSSAttributes = function(css)
	{
		var match;
		var final_css = '';
		var basics = ['color', 'font-weight', 'font-style', 'text-decoration'];
		while ( match = css.match(/([a-z-]+):(.+?);/) )
		{
			if ( in_array(match[1], basics) )
			{
				final_css += ' ' + match[0] + ' ';
			}
			css = css.replace(match[0], '');
		}
		final_css = trim(final_css);
		return final_css;
	}
	
	this.getCSS = function()
	{
		return this.style_sim_obj.getAttribute('style');
	}
	
	this.renderPreview = function()
	{
		if ( !this.preview_div )
			return false;
		var color = ( this.style_sim_obj.style.color ) ? '#' + this.rgb2hex(this.style_sim_obj.style.color) : null;
		this.preview_div.style.color = color;
		this.preview_div.style.fontWeight = this.style_sim_obj.style.fontWeight;
		this.preview_div.style.fontStyle = this.style_sim_obj.style.fontStyle;
		this.preview_div.style.textDecoration = this.style_sim_obj.style.textDecoration;
		this.preview_div.firstChild.nodeValue = $lang.get(this.f_rank_title.value);
	}
	
	this.submitEvent = function(e)
	{
		if ( this.onsubmit )
		{
			this.onsubmit(e);
		}
		else
		{
			window.console.error('RankEditorControl: no onsubmit event specified');
		}
	}
	
	this.deleteEvent = function(e)
	{
		if ( this.ondelete )
		{
			this.ondelete(e);
		}
		else
		{
			window.console.error('RankEditorControl: no ondelete event specified');
		}
	}
	
	/**
 	* Converts a parenthetical color specification (rgb(x, y, z)) to hex form (xxyyzz)
 	* @param string
 	* @return string
 	*/
	
	this.rgb2hex = function(rgb)
	{
		var p = rgb.match(/^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/);
		if ( !p )
			return rgb.replace(/^#/, '');
		
		var r = parseInt(p[1]).toString(16), g = parseInt(p[2]).toString(16), b = parseInt(p[3]).toString(16);
		if ( r.length < 2 )
			r = '0' + r;
		if ( g.length < 2 )
			g = '0' + g;
		if ( b.length < 2 )
			b = '0' + b;
		
		return r + g + b;
	}
	
	/**
 	* Get red, green, and blue values for the given hex color
 	* @param string
 	* @return array (numbered, e.g. not an object
 	*/
	
	this.hex2rgb = function(hex)
	{
		hex = hex.replace(/^#/, '');
		if ( hex.length != 3 && hex.length != 6 )
		{
			return hex;
		}
		if ( hex.length == 3 )
		{
			// is there a better way to do this?
			hex = hex.charAt(0) + hex.charAt(0) + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2);
		}
		hex = [ hex.substr(0, 2), hex.substr(2, 2), hex.substr(4, 2) ];
		var red = parseInt(hex[0], 16);
		var green = parseInt(hex[1], 16);
		var blue = parseInt(hex[2], 16);
		return [red, green, blue];
	}
}

/**
 * Perform request for editable rank data and draw editor
 */

function ajaxInitRankEdit(rank_id)
{
	load_component('messagebox');
	var json_packet = {
		mode: 'get_rank',
		rank_id: rank_id
	};
	json_packet = ajaxEscape(toJSONString(json_packet));
	ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
		{
			if ( ajax.readyState == 4 && ajax.status == 200 )
			{
				var response = String(ajax.responseText + '');
				if ( !check_json_response(response) )
				{
					handle_invalid_json(ajax.responseText);
					return false;
				}
				try
				{
					var response = parseJSON(ajax.responseText);
				}
				catch(e)
				{
					handle_invalid_json(ajax.responseText);
				}
				if ( response.error )
				{
					if ( response.error == 'need_auth_to_admin' )
					{
						load_component('login');
						var rid = rank_id;
						ajaxDynamicReauth(function()
							{
								ajaxInitRankEdit(rid);
							});
					}
					else
					{
						alert(response.error);
					}
					return false;
				}
				var editor = new RankEditorControl(response);
				editor.onsubmit = ajaxRankEditHandleSaveExisting;
				editor.ondelete = ajaxRankEditHandleDelete;
				var container = document.getElementById('admin_ranks_container_right');
				container.innerHTML = '';
				container.appendChild(editor.render());
			}
		}, true);
}

function ajaxInitRankCreate()
{
	load_component('messagebox');
	var editor = new RankEditorControl();
	editor.onsubmit = ajaxRankEditHandleSaveNew;
	var container = document.getElementById('admin_ranks_container_right');
	container.innerHTML = '';
	container.appendChild(editor.render());
}

function ajaxRankEditHandleSave(editor, switch_new)
{
	var whitey = whiteOutElement(editor.wrapperdiv);
	
	// pack it up, ...
	var json_packet = {
		mode: ( switch_new ) ? 'create_rank' : 'save_rank',
		rank_title: editor.f_rank_title.value,
		rank_style: editor.getCSS()
	}
	if ( !switch_new )
	{
		json_packet.rank_id = editor.rankdata.rank_id;
	}
	/// ... pack it in
	var json_packet = ajaxEscape(toJSONString(json_packet));
	
	ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
		{
			if ( ajax.readyState == 4 && ajax.status == 200 )
			{
				var response = String(ajax.responseText + '');
				if ( !check_json_response(response) )
				{
					handle_invalid_json(ajax.responseText);
					return false;
				}
				try
				{
					var response = parseJSON(ajax.responseText);
				}
				catch(e)
				{
					handle_invalid_json(ajax.responseText);
				}
				if ( response.mode == 'success' )
				{
					whiteOutReportSuccess(whitey);
					if ( switch_new )
					{
						//
						// we have a few more things to do with a newly created rank.
						//
						
						// 1. transform editor
						editor.transformToEditor(response);
						editor.onsubmit = ajaxRankEditHandleSaveExisting;
						editor.ondelete = ajaxRankEditHandleDelete;
						
						// 2. append the new rank to the list
						var create_link = document.getElementById('rankadmin_createlink');
						if ( create_link )
						{
							var parent = create_link.parentNode;
							var edit_link = document.createElement('a');
							edit_link.href = '#rank_edit:' + response.rank_id;
							edit_link.className = 'rankadmin-editlink';
							edit_link.setAttribute('style', editor.getCSS());
							edit_link.id = 'rankadmin_editlink_' + response.rank_id;
							edit_link.rank_id = response.rank_id;
							edit_link.appendChild(document.createTextNode($lang.get(editor.f_rank_title.value)));
							parent.insertBefore(edit_link, create_link);
							edit_link.onclick = function()
							{
								ajaxInitRankEdit(this.rank_id);
							}
						}
					}
					else
					{
						// update the rank title on the left
						var edit_link = document.getElementById('rankadmin_editlink_' + editor.rankdata.rank_id);
						if ( edit_link )
						{
							edit_link.firstChild.nodeValue = $lang.get(editor.f_rank_title.value);
							edit_link.setAttribute('style', editor.getCSS());
						}
					}
				}
				else
				{
					whitey.parentNode.removeChild(whitey);
					if ( response.error == 'need_auth_to_admin' )
					{
						load_component('login');
						ajaxDynamicReauth(function()
							{
								ajaxRankEditHandleSave(editor, switch_new);
							});
					}
					else
					{
						miniPromptMessage({
								title: $lang.get('acpur_err_save_failed_title'),
								message: response.error,
								buttons: [
									{
										text: $lang.get('etc_ok'),
										color: 'red',
										style: {
											fontWeight: 'bold'
										},
										onclick: function()
										{
											miniPromptDestroy(this);
										}
									}
								]
						});
					}
				}
			}
		}, true);
}

var ajaxRankEditHandleSaveExisting = function()
{
	ajaxRankEditHandleSave(this, false);
}

var ajaxRankEditHandleSaveNew = function()
{
	ajaxRankEditHandleSave(this, true);
}

var ajaxRankEditHandleDelete = function()
{
	var mp = miniPromptMessage({
			title: $lang.get('acpur_msg_rank_delete_confirm_title'),
			message: $lang.get('acpur_msg_rank_delete_confirm_body'),
			buttons: [
				{
					text: $lang.get('acpur_btn_delete'),
					color: 'red',
					style: {
						fontWeight: 'bold'
					},
					onclick: function()
					{
						var parent = miniPromptGetParent(this);
						var editor = parent.editor;
						setTimeout(function()
							{
								ajaxRankEditDeleteConfirmed(editor);
							}, 1000);
						miniPromptDestroy(parent);
					}
				},
				{
					text: $lang.get('etc_cancel'),
					onclick: function()
					{
						miniPromptDestroy(this);
					}
				}
			]
		});
	console.debug(mp);
	mp.editor = this;
}

function ajaxRankEditDeleteConfirmed(editor)
{
	var whitey = whiteOutElement(editor.wrapperdiv);
	
	load_component(['jquery', 'jquery-ui']);
	
	var json_packet = {
		mode: 'delete_rank',
		rank_id: editor.rankdata.rank_id
	};
	var rank_id = editor.rankdata.rank_id;
	
	json_packet = ajaxEscape(toJSONString(json_packet));
	ajaxPost(makeUrlNS('Admin', 'UserRanks/action.json'), 'r=' + json_packet, function(ajax)
		{
			if ( ajax.readyState == 4 && ajax.status == 200 )
			{
				var response = String(ajax.responseText + '');
				if ( !check_json_response(response) )
				{
					handle_invalid_json(ajax.responseText);
					return false;
				}
				try
				{
					var response = parseJSON(ajax.responseText);
				}
				catch(e)
				{
					handle_invalid_json(ajax.responseText);
				}
				if ( response.mode == 'success' )
				{
					// the deletion was successful, report success and kill off the editor
					whiteOutReportSuccess(whitey);
					setTimeout(function()
						{
							// nuke the rank title on the left
							var edit_link = document.getElementById('rankadmin_editlink_' + editor.rankdata.rank_id);
							if ( edit_link )
							{
								edit_link.parentNode.removeChild(edit_link);
							}
							// collapse and destroy the editor
							$(editor.wrapperdiv).hide("blind", {}, 500, function()
									{
										// when the animation finishes, nuke the whole thing
										var container = document.getElementById('admin_ranks_container_right');
										container.innerHTML = $lang.get('acpur_msg_select_rank');
									}
								);
						}, 1500);
				}
				else
				{
					whitey.parentNode.removeChild(whitey);
					if ( response.error == 'need_auth_to_admin' )
					{
						load_component('login');
						ajaxDynamicReauth(function()
							{
								ajaxRankEditDeleteConfirmed(editor);
							});
					}
					else
					{
						miniPromptMessage({
								title: $lang.get('acpur_err_delete_failed_title'),
								message: response.error,
								buttons: [
									{
										text: $lang.get('etc_ok'),
										color: 'red',
										style: {
											fontWeight: 'bold'
										},
										onclick: function()
										{
											miniPromptDestroy(this);
										}
									}
								]
						});
					}
				}
			}
		}, true);
}