
/* a very simple bind method. Returns an anonymous function that calls the method
 * with the scope provided. Can't pre-bind arguments right now.
 */
Bind = function(__method, scope) {
	return function() {
		__method.apply(scope, arguments);
	}
}

/* Track a method call */
FlutherPageTrack = function(url) {
	if (typeof pageTracker != "undefined") {					
		pageTracker._trackPageview(url);
	}
}

/*takes a link and adds target=_blank so it opens in a new tab*/
LinksNewTab = function(link) {
    this.remove_hash = function(link) {
        return link.href.substring(0, link.href.length - link.hash.length - 1);
    };
    // Skip links that refer to the same page
    if (this.remove_hash(link) != this.remove_hash(window.location)) {
        link.target = "_blank";    
    }
}

BannerMove = function(v) {
    var num_banners = 4;
    var classname = $('banner').className;
    var banner_num = (parseInt(classname.slice(6,7)) + v + num_banners) % num_banners;
    var nextclassname = "banner" + banner_num;
    YD.replaceClass("banner", classname, nextclassname);
}

ValidateCommas = function(id_name) {
	var tag_value = $(id_name).value;
	var bits = tag_value.split(' ');
	if (bits.length > 2 && (tag_value.search(',') == -1)) {
		return false;
	}
	
	return true;
};

function Updater(delay, func) {
  this.delay = delay;
  this.count = 0;
  this.func = func;
  this.args = [];
  this.interval = "stopped";

  for (var i = 2, length = arguments.length; i < length; i++) {
        this.args.push(arguments[i]);
  }

  Updater.prototype.stopUpdater = function() {
    this.count = 0;
    clearInterval(this.interval);
    this.interval = "stopped";
  }

  Updater.prototype.startUpdater = function() {
    if (this.interval == "stopped") {
      this.interval = setTimeout(Bind(this.fireFunc, this), this.delay[0]);
    }

  }
  
  Updater.prototype.fireFunc = function() {
    this.count += 1;
    var len = this.delay.length;
    if (this.count >= len) {
      var this_instance = this;
      this.interval = setInterval(function() { this_instance.func.apply(this_instance, this_instance.args); }, this.delay[len-1]);
    } else {
      this.func.apply(this, this.args);
      this.interval = setTimeout(Bind(this.fireFunc, this), this.delay[this.count]);
    }
    
  }
  
  Updater.prototype.resetUpdater = function(andFire) {
    this.count = 0;
    this.stopUpdater();
    if (andFire) { // Fire immediately
	this.fireFunc();
    } else { // Fire after the first interval
	this.startUpdater();
    }
  }

  Updater.prototype.resetUpdaterAndFire = function() {
	this.resetUpdater(true);
  }
  
  this.startUpdater();

}

var FormValueListener = function(input_str, callback) {
	 this.oSelf = this;
	 if (arguments.length) {
        this.init(input_str, callback);
     }
};

FormValueListener.prototype = {
	old_value: "",
	fCallback: undefined,
	oInput: null,
	
	init: function(input_str,  callback) {
		this.oInput = $(input_str);
		this.fCallback = callback;
		
		//YE.on(this.oInput, 'keypress', this._checkChanged, this);
		//this._checkChanged(null, this);
		new Updater([100], this._checkChanged, this);
		
	},
	
	_checkChanged: function(oSelf) {
		var new_val = oSelf.oInput.value;
		if (new_val != oSelf.old_value ) {
		 	
			oSelf.old_value = new_val;
			if (oSelf.fCallback) oSelf.fCallback(new_val);
		}
		
	}
		
		
};


PreviewHandler = function(input_id, preview_id, params) {
	this.oSelf = this;
	this.oPreviewDiv = $(preview_id);
	if (params) {
		this.oIndicator = $(params['indicator']);
		this.fCallback = params['callback'];
	}
	this.subject_mode =  ($(input_id).nodeName != "TEXTAREA");
	// assume that if we're not multiline, we don't want to process links
	this.bInsertLinks = this.bMultiline;
	this._listener = new FormValueListener(input_id, Bind(this._handleChange, this));
	
	
};
PreviewHandler.prototype.oPreviewDiv = null;
PreviewHandler.prototype.fCallback = undefined;
PreviewHandler.prototype.oIndicator = null;
PreviewHandler.prototype._request = null;
PreviewHandler.prototype.sanitize_params = '';

PreviewHandler.prototype._handleChange = function(val) {
		// We've gotten a change event from the form element
		this.oIndicator.style.display = '';
		
		// Send along subject mode only if we're not a textarea
		/*otherparams = this.sanitize_params + (this.subject_mode ? '&subject_mode=1' : '');
		
		// Make sure our calls are in order
		if (this._request && YAHOO.util.Connect.isCallInProgress(this._request)) {
			YAHOO.util.Connect.abort(this._request);
		}
		this._request = YAHOO.util.Connect.asyncRequest('POST', HOST_NAME + '/sanitize/', {
			success: Bind(this._update, this),
			failure: Bind(this._handleFailure, this)
		}, 'input='+encodeURIComponent(val)+otherparams);*/
		
		
		
		this._update(fluther.fakeSanitize(val, this.subject_mode, this.sanitize_params));
		
};
PreviewHandler.prototype._update = function(o) {
		this.oIndicator.style.display = "none";
		//var sanitized = eval("("+o.responseText+")");
		var sanitized = o; 
		
		this.oPreviewDiv.innerHTML = sanitized;

        //Make preview links open in new tabs.
        links = YAHOO.util.Selector.query("a", this.oPreviewDiv);
        for(var i in links) {
            LinksNewTab(links[i]);
        }         
        
		// Fire the callback
		this.fCallback(this);
};
PreviewHandler.prototype._handleFailure = function(o) {
		this.oIndicator.style.display = "none";
};

var HilightEffect = function(el, color) {
	var newColor = color || '#BE3600';
	var startColor = YD.getStyle(el, 'background-color');
	this.startEffect = new YAHOO.util.ColorAnim(el, {backgroundColor: { to: newColor} }, 0.5, YAHOO.util.Easing.easeIn);
	this.endEffect = new YAHOO.util.ColorAnim(el, {backgroundColor: { to: startColor} }, 0.5, YAHOO.util.Easing.easeOut);

	this._init();
};
HilightEffect.prototype.finishAnim = function() {
	this.endEffect.animate();
};
HilightEffect.prototype._init = function() {
	this.startEffect.onComplete.subscribe(Bind(this.finishAnim, this));	
	
	this.startEffect.animate();
}


var fluther = {
	
	init: function() {
		// Setup animations
		
		fluther.textiler = new Textiler();
		fluther.restore_state();
		
		if (YD.inDocument("ask-fluther-button")) {
			YE.on('ask-fluther-button', 'click', function() {
			window.location = "/qask/";
			});
		}
		
		if ( YD.inDocument("join") && YD.inDocument("login") ) { //check if we're logged in
			var loginlinks = YD.getElementsByClassName("loginlink");
			for(i in loginlinks) {
				YE.on(loginlinks[i], "click", function(e){
					YE.stopEvent(e);
					YD.setStyle("login-container","display", "");
					YD.setStyle("join-container","display", "none");
					var el = $('login_username');
					el.focus();
					new HilightEffect(el);
				}); 
			}
			var joinlinks = YD.getElementsByClassName("joinlink");
			for(i in joinlinks) {
				YE.on(joinlinks[i], "click", function(e){
					YE.stopEvent(e);
					YD.setStyle("join-container","display", "");
					YD.setStyle("login-container","display", "none");
					var el = $('join_username');
					el.focus();
					new HilightEffect(el);
				}); 
			}
			
			
			var loginform = $('loginform');
			YE.on(loginform, "submit",  fluther.ajax_login);
			//Event.observe(loginform, 'submit', fluther.ajax_login);
			var joinform = $('joinform');
			YE.on(joinform, 'submit', fluther.ajax_join);
			
		} else { //Only if we're logged in
			
			var logoutlinks = YD.getElementsByClassName("logoutlink");
			YE.on(logoutlinks, "click", function(e) {
				FB.ensureInit(function() {
					FB.Connect.logout();
				});
			});
					
			YE.on( $('ask-link'), "click", function(e){
				if (YD.inDocument("id_subject")) {
					YE.stopEvent(e);
					var el = $('id_subject');
					el.focus();
					new HilightEffect(el);
				}
			});
		}
		//$('id_subject').focus(); //Initial focus
		
        //******** Banner controls
        YE.on('next-button','click', function(e) {
            YE.stopEvent(e);
            BannerMove(1);            
        });
        YE.on('prev-button','click', function(e) {
            YE.stopEvent(e);
            BannerMove(-1);
        });

        YE.on('join-button','click', function(e) {
            YE.stopEvent(e);
            FlutherPageTrack("/BannerJoinButton/"+$('banner').className);
            document.location.href="/join/#content";
        });

		//*******close-tip
		YE.on('close-usertip', 'click', function(e){
			YE.stopEvent(e);
			var tip_params = $('close-usertip').href.split("?")[1];
			YC.asyncRequest('POST',HOST_NAME + '/closetip/', {
					success: function(o) {
						YD.setStyle('usertip','display','none');
					},
					failure: function(o) {
						YD.setStyle('usertip','display','none');
					},
	   				timeout: 6000
					},
				tip_params //this is a string like "name=join"
				);
		});
		
		// Setup all the custom inits
		for (var i in EXTENSIONS) {
			EXTENSIONS[i]();
		}
	
	},
	
	clean_subject: function(s) {
		if (s.length < 1) {
			return s;
		}
		
		s = s.substr(0,1).toUpperCase() + s.substr(1);
		if (/[^?!."]$/.test(s)) {
			s = s + '?';
		}
		
	 	var multiple_punc = /(?:(\?)\?*)|(?:(\!)\!*)$/;
		s = s.replace(multiple_punc, "$1$2");
	
		return s;
		
	},
	
	fakeSanitize: function(s, subject_mode, discussion_param) {
		var linebreaks, insert_links;
		if (subject_mode) {
			linebreaks = false;
			insert_links = false;
			s = this.clean_subject(s);
		}
		else {
			linebreaks = true;
			insert_links = true;
		}	
		
		return fluther.textiler.process(s, insert_links, linebreaks, discussion_param);
	},
	
	ajax_login: function(e) {
		YE.stopEvent(e);
		FlutherPageTrack("/sidebar/login_form");
		YC.setForm("loginform"); //this sets the form data into the params
		YC.asyncRequest('POST',HOST_NAME + '/ajaxlogin/', {
			 success: function(o) {
			 	//response = o.responseText.parseJSON();
				var response = eval('(' + o.responseText + ')');
				
				if (response.errortext){ //there were errors in login
					//TODO check for scripts?
					$("login-errors").innerHTML = response.errortext;
				} else if (response.success) {
					FlutherPageTrack("/sidebar/login_success");
					//Now do a smart refresh that saves state									
					fluther.capture_state();
					window.location.reload(true);
				}
			 }
		});	
	
	},
	
	ajax_join: function(e) {
		YE.stopEvent(e);
		FlutherPageTrack("/sidebar/join_form");
		datapairs = YC.setForm("joinform");
		// Save the 'next' URL in case we need to do a captcha
		datapairs += '&next='+encodeURIComponent(window.location);
		YC.asyncRequest('POST', HOST_NAME + '/join/?ajax', {
			 success: function(o) {
			 	//response = o.responseText.parseJSON();
				var response = eval('(' + o.responseText + ')');
				if (response.errors){ //there were errors in login
					var errorDom = YD.create('div', {className: 'formerror'}, [
						YD.create('div', {}, [
							YD.create('img', {src: MEDIA_URL + "images/v2/alerticon_big.png", alt:"", className:"trans-png"}),
							"Oh, no! There are errors!"
							])
					]);
					// insert the form error message at the top of the contents
					var contents = $('joinform').parentNode;
					contents.insertBefore(errorDom, contents.firstChild);
					
					var errorfields = $$("errortext", "span", "joinform");
					for (field in errorfields) {
						errorfield = errorfields[field];
						error_name = errorfield.id.slice(12); //chop off the errors_join
						if (error_name in response.errors) {
							errorfield.innerHTML = response.errors[error_name];
							if (error_name == "password" || error_name == "passwordVerify") {
								$("join_password").value = "";
								$("join_verify").value = "";
								
							}
						} else {
							errorfield.innerHTML = "";
						}
					}

				} else if (response.redirect) {
					fluther.capture_state();
					// Simulate a full-page redirect for captcha
					window.location = response.redirect;
				}
				else if (response.success) {
					FlutherPageTrack("/welcome");
					//Now do a smart refresh that saves state									
					fluther.capture_state();
					//Track the registation
					window.location.reload(true);
				}
			 }
		}, datapairs);	
	},
	
	//capture the state of all the forms as a JSON string
	capture_state: function() {

		var form_els = YD.getElementsByClassName("save", "form");
		var forms = new Array();
		for (i in form_els) {
			forms[i] = YC.setForm(form_els[i]);
		}
		var data = {'forms':forms, 'location': window.location.pathname};
		//set a time 5 minutes in the future
		var myDate=new Date()
		myDate.setMinutes(myDate.getMinutes()+5)
		$T.setCookie("init_state",  $T.JSONEncode(data), myDate);
	},
	
	//restore the state--inversie of capture state
	//Using splits is a little lame... that's only because YUI only gives
	//form to us as a string...
	restore_state: function() {
		var init_state_string = $T.getCookie("init_state");
	 	if (init_state_string != null && init_state_string != "") {
		 	var init_state = $T.JSONParse(init_state_string);
			// Only restore if we're on the page saved in the cookie
			if (init_state.location == window.location.pathname) {
			 	try {
					var form_els = YD.getElementsByClassName("save", "form");
					for(var i in form_els) {
						var form_vals = init_state.forms[i].split("&");
						for (var v in form_vals) {
							form_els[i][v].value = decodeURIComponent(form_vals[v].split("=")[1]);
						}
					}
					// we're successful, now we clear it out
					$T.deleteCookie("init_state","/");
				}
				catch(e) {}
			}
	 	}		
	}

};

// LIBRARY FIXES
// from http://blog.stevenlevithan.com/archives/cross-browser-split
if (!window.cbSplit) {

	var cbSplit = function (str, separator, limit) {
		// if `separator` is not a regex, use the native `split`
		if (Object.prototype.toString.call(separator) !== "[object RegExp]")
			return cbSplit._nativeSplit.call(str, separator, limit);

		var	output = [],
			lastLastIndex = 0,
			flags = (separator.ignoreCase ? "i" : "") +
				(separator.multiline  ? "m" : "") +
				(separator.sticky     ? "y" : ""),
			separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues
			separator2, match, lastIndex, lastLength;

		str = str + ""; // type conversion
		if (!cbSplit._compliantExecNpcg)
			separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt

		// behavior for `limit`: if it's...
		// - `undefined`: no limit.
		// - `NaN` or zero: return an empty array.
		// - a positive number: use `Math.floor(limit)`.
		// - a negative number: no limit.
		// - other: type-convert, then use the above rules.
		if (limit === undefined || +limit < 0) {
			limit = Infinity;
		} else {
			limit = Math.floor(+limit);
			if (!limit)
				return [];
		}

		while (match = separator.exec(str)) {
			lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser

			if (lastIndex > lastLastIndex) {
				output.push(str.slice(lastLastIndex, match.index));

				// fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
				if (!cbSplit._compliantExecNpcg && match.length > 1) {
					match[0].replace(separator2, function () {
						for (var i = 1; i < arguments.length - 2; i++) {
							if (arguments[i] === undefined)
								match[i] = undefined;
						}
					});
				}

				if (match.length > 1 && match.index < str.length)
					Array.prototype.push.apply(output, match.slice(1));

				lastLength = match[0].length;
				lastLastIndex = lastIndex;

				if (output.length >= limit)
					break;
			}

			if (separator.lastIndex === match.index)
				separator.lastIndex++; // avoid an infinite loop
		}

		if (lastLastIndex === str.length) {
			if (!separator.test("") || lastLength)
				output.push("");
		} else {
			output.push(str.slice(lastLastIndex));
		}

		return output.length > limit ? output.slice(0, limit) : output;
	};

	cbSplit._compliantExecNpcg = /()??/.exec("")[1] === undefined; // "NPCG": nonparticipating capturing group
	cbSplit._nativeSplit = String.prototype.split;

}


// for convenience...
String.prototype.split = function (separator, limit) {
	return cbSplit(this, separator, limit);
};
String.prototype.startsWith = function(str) {return (this.indexOf(str) === 0);}

function Textiler() {
	
	this.glyphs = [
			//[new RegExp("(?<!\\w)\\b",'g'), "&#8220;"], // double quotes
            [/'"'/g, "&#8221;"],                                       // double quotes
            [/\b'/g, '&#8217;'],                                     // single quotes
            //[new RegExp("\'(?<!\w)\\b", 'g'), '&#8216;'],                              // single quotes
            [/'/g, "&#8217;"],                                       // single single quote
            [/(\b|^)( )?\.{3}/g, "$1&#8230;"],                       // ellipsis
            [/\b---\b/g, "&#8212;&#8212;"],                          // double em dash
            [/\s?--\s?/g, "&#8212;"],                                // em dash
            [/(\d+)-(\d+)/g, "$1&#8211;$2"],                         // en dash (1954-1999)
            [/(\d+)-(\W)/g, "$1&#8212;$2"],                          // em dash (1954--)
            [/\s-\s/g, " &#8211; "],                                 // en dash
            [/(\d+) ?x ?(\d+)/g, "$1&#215;$2"],                      // dimension sign
            [/\b ?(\((tm|TM)\))/g, "&#8482;"],                       // trademark
            [/\b ?(\([rR]\))/g, "&#174;"],                           // registered
            [/\b ?(\([cC]\))/g, "&#169;"],                           // copyright
			[/(?:(?:(\d) )|(?:\b( ?)))1\/4/g, "$1$2&#188;"],		// vulgar fraction 1/4
			[/(?:(?:(\d) )|(?:\b( ?)))1\/2/g, "$1$2&#189;"],		 // vulgar fraction 1/2
			[/(?:(?:(\d) )|(?:\b( ?)))3\/4/g, "$1$2&#190;"],		 // vulgar fraction 3/4
			[/(?:(?:(\d) )|(?:\b( ?)))1\/3/g, "$1$2&#8531;"],		 // vulgar fraction 1/3
			[/(?:(?:(\d) )|(?:\b( ?)))2\/3/g, "$1$2&#8532;"]		 // vulgar fraction 2/3
			];
	
	this.amazon_associate_id = "fluthercom-20";
	this.word_split_re = /(\s+|<|>)/;
	this.punctuation_re = /^((?:\(|\&lt;|<|-)*)(.*?)((?:\.|\,|\)|\n|&gt;|<|>|&lt;)*)$/;
	this.naked_link_re = /^www\.|(^(?!http:\/\/)[0-9a-zA-Z].*(\.com|\.net|\.org|\.edu))$/;
	this.chunk_re = /(<a.*?>.*?<\/a>)/;
	this.all_punctuation = "[\\!\"#\\$%&\'()\\*\\+,\\./:;<=>\\-\\?@\\[\\\\\\]\\^_`{\\|}\\~]";
	this.atsign_re = /(@\w+)(.*)/;
	this.link_re = /"([^"]+?)":((?=[a-zA-Z0-9.\/#])(?:(?:ftp|https?|telnet|nntp):\/\/(?:\w+(?::\w+)?@)?[-\w]+(?:\.\w[-\w]*)+|(?:mailto:)?[-\+\w]+\@[-\w]+(?:\.\w[-\w]*)+|(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+(?:com\b|edu\b|biz\b|gov\b|in(?:t|fo)\b|mil\b|net\b|org\b|museum\b|aero\b|coop\b|name\b|pro\b|[a-z][a-z]\b))?(?::\d+)?(?:\/?[^\.!,\?;:"'<>()\[\]{}\s]*(?:[\.!,?;:]+[^\.!,\?;:"'<>()\[\]{}\s]+)*)?)/g; 	
	this.searches = {
			'imdb': 'http://www.imdb.com/Find?for={{query}}',
			'google':'http://www.google.com/search?q={{query}}',
			'wiki': 'http://www.wikipedia.org/wiki/Special:Search?search={{query}}&go=Go',
			'fluther': 'http://www.fluther.com/search/res/?query={{query}}',
			'isbn': 'http://www.amazon.com/exec/obidos/ASIN/{{query}}/' + this.amazon_associate_id,
			'amazon': 'http://www.amazon.com/exec/obidoc/external-search?mode=blended&keyword={{query}}&tag=' + this.amazon_associate_id
	}
	
	Textiler.prototype.glyphs_and_links = function(s) {
			
		return this.glyph_replace(s);
	};

	Textiler.prototype.urlize = function(text) {
		var has_link = false;
		var words = text.split(this.word_split_re);
		for (i in words) {
			var word = words[i];
			var match = this.punctuation_re.exec(word);
			if (match) {
				var lead = match[1];
				var middle = match[2];
				var trail = match[3];
				
				//console.log("b,m,t ", lead, middle, trail);
				var middle_is_url, url;
				
				if (this.disc_link_func && middle[0] == '@'){
					var match = middle.match(this.atsign_re);
					if (match) {
						middle = match[1];
						var newtrail = match[2];
						trail += newtrail;
						
						var quip_link = this.disc_link_func(middle);
						if (quip_link) {
							middle = '<a href="'+quip_link+'" class="atlink">' + middle + "</a>";
							has_link = true;
						}
					}
				}
				else {
					if (this.naked_link_re.test(middle)) {
						middle_is_naked_url = true;
						url = "http://" + middle;
					}
					else {
						middle_is_naked_url = false;
						url = middle;
					}
					if (middle_is_naked_url || middle.startsWith('http://') || middle.startsWith('https://')) {
						middle = '<a href="' + url + '">' + middle + "</a>";
						has_link = true;
					}
				}
				
				var newWord = lead + middle + trail;
				if (newWord != word) {
					words[i] = newWord;
				}
			}
		}
		
		text = words.join('');
		
		return [text, has_link];
		
	};
	Textiler.prototype.paragraph = function(s) {
		// Sanitize code before we insert any tags
		s = s.replace(/&/g, '&amp;');
		s = s.replace(/</g, '&lt;');
		s = s.replace(/>/g, '&gt;');
                
		// Split the lines on paragraphs
		var lines = s.split(/\n{2,}/);
		
		
		var output = [];
		for (var i in lines) {
			var line = lines[i];
			var open_tag, close_tag;
			
			// Strip the line?
			
			if (this.linebreaks) {
					open_tag = '<p>';
				close_tag = '</p>';
			}
			else {
				open_tag = close_tag = '';
			}
			
			// Create linebreaks
			if (this.linebreaks) {
				line = line.replace(/(<br \/>|\n)+/g, "<br />\n");
			}
			
			// Remove <br /> from inside broken HTML tags
			line = line.replace(/(<[^>]*)<br \/>\n(.*?>)/, "$1 $2");
			
			// Inline formatting
			line = this.inline(line);
			
			output.push(open_tag + line + close_tag);
			
		}
		
		return output.join('\n\n');
	}
	
	Textiler.prototype.glyph_replace = function(s) {
		var text, has_link;
		if (this.do_links) {
			var returns = this.urlize(s);
			text = returns[0];
			has_link = returns[1];
		}
		else {
			text = s;
			has_link = false;
		}
		
		if (has_link) {
			var chunks_out = [];
			var chunks_in = text.split(this.chunk_re);
			
			for (var i in chunks_in) {
				if (!this.chunk_re.test(chunks_in[i])) {
					chunks_in[i] = this.do_glyphs(chunks_in[i]);
				}
				chunks_out.push(chunks_in[i]);
			}
			
			text = chunks_out.join('');
		}
		else {
			text = this.do_glyphs(text);
		}
		
		return text;
	};
	
	Textiler.prototype.do_glyphs = function(s) {
		
		for (var i in this.glyphs) {
			s = s.replace(this.glyphs[i][0], this.glyphs[i][1]);
		}
		return s;
	};
	
	Textiler.prototype.process_links = function(s) {
		if (!this.do_links) {
			return s;
		}
		
		var r = s;
		
		re = this.link_re;
		var match, href, parts, query, repl, match, text, link, proto;
		while ((match = re.exec(s)) != null) {
			group = match[0];
			link_text = match[1];
			href = match[2];
			parts = href.split(':', 2);
			proto = parts[0];
			
			if (parts.length == 2) {
				query = parts[1];
			}
			else {
				query = link_text;
			}
			query = query.replace(' ', '+');
				
			// Look for smart search
			if (proto in this.searches) {
				link = this.searches[proto].replace('{{query}}', query);
			}
			else {
				link = href;
			}
			
			repl = '<a href="' + link + '">' + link_text + "</a>";
			r = r.replace(group, repl);	
		}
		
		return r;
	}
	
	Textiler.prototype.inline = function(r) {
		
		qtags = [['\\*', 'strong'],
				 ['\\-\\-', 'small'],
				 ['\\-', 'del'], 
				 ['@', 'code'],
				 ['_', 'em']];
		for (var i=0;i<qtags.length;i++) {
			var ttag = qtags[i][0]; htag = qtags[i][1];
			var fc = "[^\\s"+ttag+']'; // Forbidden start/end characters
			var re = new RegExp("(^|\\s|"+ // Start of line or whitespace
							this.all_punctuation+ // or punctuation
							')'+
							ttag+'('+fc+'|'+fc+".*?"+fc+')'+ttag+ //tags surrounding text
							"($|\\s|"+ // followed eol or whitespace
							this.all_punctuation+ // or punctutation
							")");
			
			while (re.test(r)) {
				r = r.replace(re,'$1<'+htag+'>'+'$2'+'</'+htag+'>$3');
			}
		}
	
		r = this.process_links(r);
		
		// links
	
		r = this.glyphs_and_links(r);
		
		return r;
	}
	
	Textiler.prototype.process = function(s, do_links, linebreaks, disc_link_func) {
		this.do_links = (do_links == undefined) ? true : do_links;
		this.linebreaks = (linebreaks == undefined) ? true: linebreaks;
		this.disc_link_func = disc_link_func;
		
		var r = s;
		
		r = this.paragraph(r);
		
		return r;
	};
}

//shortcuts
$ = YAHOO.util.Dom.get;
YD = YAHOO.util.Dom;
YE = YAHOO.util.Event;
YC = YAHOO.util.Connect;
YE.onAvailable('DOMReady', fluther.init);