Source for "Gmail Signature Float"

By Tim Jarrett
Has no other scripts.


Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// Gmail Signature Float
// version 0.9 BETA!
// 2007-10-21
// Copyright (c) 2006-2007, Tim Jarrett 
// Contact: tim |at| tim-jarrett |dot| com
// Released under the GPL license
// http://www.gnu.org/copyleft/gpl.html
//
// --------------------------------------------------------------------
//
// This is a Greasemonkey user script.
//
// To install, you need Greasemonkey: http://greasemonkey.mozdev.org/
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to "Install User Script".
// Accept the default configuration and install.
//
// IF YOU ARE UPGRADING FROM A PREVIOUS VERSION OF GMAIL SIGNATURE
// FLOAT, go to Tools/Manage User Scripts and manually uninstall the
// previous version before installing this one.  Sorry, this is a
// limitation of Greasemonkey.
//
// To uninstall, go to Tools/Manage User Scripts,
// select "Gmail Signature Float", and click Uninstall.
//
// --------------------------------------------------------------------
//
// WHAT IT DOES:
//
// mail.google.com (Gmail):
// - Adds your signature above the quoted text, rather than below it
// - Updates the Settings page text to reflect this change in
//   functionality
//
// --------------------------------------------------------------------
//
// PROPS TO:
//
//	Robson Braga Araujo (http://www.userscripts.org/people/868) and
//  his "Gmail: Random Signature" script.  I lifted his method for
//  searching through Gmail's functions.  I'm not sure if it helped...
//  but it is being used in this update.
//
// --------------------------------------------------------------------
//
// CHANGELOG:
// 0.9
//	  - Added option to not include signatures on replies
//    - Added option to not include signatures on forwards
//
// 0.8.2
//    - Fixed script so that pressing the "Compose" button from the contact list pages
//      appropriately allows HTML and clears dashes (thanks for the bug report Raymon)
//
// 0.8.1
//	  - Fixed 'Settings' to work for other languages
//    - Slightly changed way that settings are injected into the DOM to be a bit
//		more friendly
//
// 0.8
//	  - Added ability to turn of Signature Floating
//    - Added a link to the UserScripts page for this script for update checking
//
// 0.7.1
//	  - Fixed bug in function body extracting causing GSF not to work sometimes -
//       GSF should now work on Gmail and Gmail Domains
//
// 0.7
//	  - Did some house cleaning on function names to try to keep them consistent
//    - Added option to use HTML in the signature
//
// --------------------------------------------------------------------

/* Gmail Signature Float
   Copyright 2006 Tim Jarrett
   See also: <http://www.tim-jarrett.com/greasemonkey/gmailsignaturefloat.user.js>

   This software is licensed under the CC-GNU LGPL:
   <http://creativecommons.org/licenses/LGPL/2.1/>
*/

// ==UserScript==
// @name          Gmail Signature Float
// @namespace     http://www.tim-jarrett.com
// @description   Moves your signature in Gmail to the top of the message rather than the bottom
// @include       http://mail.google.com/*
// @include       https://mail.google.com/*
// @include		  https://mail.google.com/hosted*
// ==/UserScript==
(function()
{

var VERSION_NUMBER = "0.8.2";
var arr_other_funcs = new Array();

//If loading a javascript page
if ( window.location.href.match(/name=js/) ) {
	//It's a JS document - lets see if we can find our signature creation function
	var sig_func_name = "";

	var js = unsafeWindow.top.document.getElementsByName('js')[0].contentWindow;
	if (js.wrappedJSObject) js = js.wrappedJSObject;

	for ( propName in js ) {
		var propValue = js[propName];

		if ( typeof propValue != "function" ) {
			continue;

		}

		var functionBody = propValue.toString();

		if ( functionBody.indexOf('<br clear=all><br>-- <br>') != -1 ) {
			sig_func_name = propName;
			GM_log("Found " + propName + " main functions.");

		} else {
			arr_other_funcs[propName] = functionBody;

		}

	}//for

	//If we got the right file and we found the right function!!
	if ( sig_func_name != '' ) {
		//alert("Now hunting for complimentary functions");
		var regex_reply = "\\+\\s*" + escapeRegExChars(sig_func_name) + "\\s*\\(";
		var regex_comp  = "\\+\\=\\s*" + escapeRegExChars(sig_func_name) + "\\s*\\(";
		var regex_comp_to = "\\]\\s*\\=\\s*" + escapeRegExChars(sig_func_name) + "\\s*\\(";

		var func_reply  = "";
		var func_compose = "";
		var func_compose_to = "";

		for ( propName in arr_other_funcs ) {
			if ( func_reply != "" && func_compose != "" && func_compose_to != "" ) {
				break;
			}
			//GM_log("Analyzing reply function: " + propName);
			functionBody = arr_other_funcs[propName];
			if ( functionBody.search(regex_reply) != -1 ) {
				func_reply = propName;
				GM_log("Found reply function: " + propName);

			} else if ( functionBody.search(regex_comp) != -1 ) {
				func_compose = propName;
				GM_log("Found compose function: " + propName);

			} else if ( functionBody.search(regex_comp_to) != -1 ) {
				func_compose_to = propName;
				GM_log("Found compose to function: " + propName);

			}

		}//for

		if ( func_reply != "" && func_compose != "" && func_compose_to != "" ) {
			var gsf_temp_window = js;

			js[func_reply] = update_func_reply(func_reply);
			js[func_compose] = update_func_compose(func_compose);
			js[func_compose_to] = update_func_compose_to(func_compose_to);

		} else {
			GM_log("GSF could not initialize.", 2);
			//alert("GSF could not initialize.");
		}

	}

}

/**
 * Handles the onclick event for the input that indicates whether the signature should be "floated" above quoted response
 */
function update_sig_above_quote(event)
{
	var elem = event ? event.target : this;
	GM_setValue('sig_above_quote', elem.checked);
	update_signature_byline();

}//end update_sig_above_quote

function update_exclude_replies(event)
{
	var elem = event ? event.target : this;
	GM_setValue('exclude_from_replies', elem.checked);
	update_signature_byline();
	
	
}//end update_exclude_replies

/**
 * Handles the onclick event for the input that indicates whether dash removing should be used
 */
function update_remove_dashes(event)
{
	var elem = event ? event.target : this;
	GM_setValue('clear_dashes', elem.checked);
	update_signature_byline();

}//end update_remove_dashes

/**
 * Handles the onclick even for the input that indicates whether dash removing should be sued
 */
function update_allow_html(event)
{
	var elem = event ? event.target : this;
	GM_setValue('allow_html', elem.checked);
	update_signature_byline();

}//end update_allow_html

/**
 * Updates the text under "Signature" on the settings page
 */
function update_signature_byline()
{
	gsf_elem_span.style.display = "block";
	gsf_elem_span.innerHTML = "<a href='http://userscripts.org/scripts/show/3067' target='_blank'>Gmail Signature Float " + VERSION_NUMBER + "</a>";
	gsf_elem_span.innerHTML += "<ul style='padding-top: 0; margin-top: 0;'>";
	gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>" + 
									(( GM_getValue('sig_above_quote', true) ) ? 
									"inserted before quoted messages" : 
									" appended at the end of outgoing messages" ) + "</li>";

	if ( GM_getValue('exclude_from_replies', false) ) {
		gsf_elem_span.innerHTML += '<li style="margin-left: 5px; list-style-position: outside;">excluding signature from replies &amp; forwards</li>';
		
	}
									
	if ( GM_getValue('clear_dashes', false) ) {
		gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>clearing sig dashes from before signature</li>";
	}

	if ( GM_getValue('allow_html', false) ) {
		gsf_elem_span.innerHTML += "<li style='margin-left: 5px; list-style-position: outside;'>allowing HTML in signature</li>";
	}

	gsf_elem_span.innerHTML += "</ul>";


}//end update_signature_byline

//Need to update the mail settings page, I don't see a clear "name=??" for it
//so sniff for the radio button with id "sx_sg_1"
if ( window.document.getElementById("sx_sg_1") ) {
	//This sucks...but find sx_sg_1, go up the DOM tree to the main TR
	//Then comes down the DOM tree to find the appropriate TD and SPAN that we need to modify...so fragile!
	var shared_parent = window.document.getElementById("sx_sg_1").parentNode.parentNode.parentNode.parentNode.parentNode.parentNode;
	var arr_spans = shared_parent.cells[0].getElementsByTagName('SPAN');
	var gsf_elem_span = arr_spans[0];

	//Um...update the stuff beneath "Signature:"
	update_signature_byline();

	//Add in the "clear dashes from signature" option
	var elem_ta = window.document.getElementById('sx_sg_1val');
	var elem_tbody = elem_ta.parentNode.parentNode.parentNode;

	//Add in the gsf_sig_above_quote options
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tr.innerHTML += '<td><input id="gsf_sig_above_quote" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_sig_above_quote">Place Signature Above Quoted Response</label></td>';
	
	//Add in the gsf_exclude_replies option
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tbody.innerHTML += '<td><input id="gsf_exclude_from_replies" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_exclude_from_replies">Exclude Signature From Replies &amp; Forwards</label></td>';

	//Add in the gsf_clear_dashes option
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tbody.innerHTML += '<td><input id="gsf_clear_dashes" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_clear_dashes">Clear Sig Dashes From Signature (Not Recommended)</label></td>';

	//Addin the gsf_allow_html option
	var elem_tr = window.document.createElement('TR');
	elem_tbody.appendChild(elem_tr);
	elem_tbody.innerHTML += '<td><input id="gsf_allow_html" type="checkbox" /></td><td style="padding-top: 2px;"><label style="font-weight: bold" for="gsf_allow_html">Allow HTML in Signature</label></td>';

	var elem_input = window.document.getElementById('gsf_sig_above_quote');
	elem_input.checked = GM_getValue('sig_above_quote', true);
	elem_input.addEventListener('click', update_sig_above_quote, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts
	
	var elem_input = window.document.getElementById('gsf_exclude_from_replies');
	elem_input.checked = GM_getValue('exclude_from_replies', true);
	elem_input.addEventListener('click', update_exclude_replies, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts

	var elem_input = window.document.getElementById('gsf_clear_dashes');
	elem_input.checked = GM_getValue('clear_dashes', false);
	elem_input.addEventListener('click', update_remove_dashes, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts

	//Add in the "allow html in signature" option
	var elem_input = window.document.getElementById('gsf_allow_html');
	elem_input.checked = GM_getValue('allow_html', false);
	elem_input.addEventListener('click', update_allow_html, true);	//see http://dunck.us/collab/GreaseMonkeyUserScripts
}

/**
 * Rewrite the reply function
 */
function update_func_reply(func_name)
{
	//Turn the function into a string
	//Was having problems calling some original GMail functions from inside of my rewritten
	//functions, so I store all gmail functions and call them locally - gsf_escape_functions redeclares all
	//the necessary functions
	var str_func      		= gsf_escape_functions(js[func_name].toString());

	//Extract the function definition (ie function something(whatever, whatever))
	var str_func_def   		= gsf_get_func_def(func_name, str_func);

	//Extract the function body (everything between the first { after the definition and the last })
	var str_func_body  		= gsf_get_func_body(str_func);

	//make a copy of the body
	var str_func_new_body 	= str_func_body;

	//make a copy of the body
	var str_func_new_body 	= str_func_body;

	//We slice and dice this one...
	var pos_sigcall 		= str_func_new_body.lastIndexOf('gsf_temp_window.' + sig_func_name);
	var pos_lastsemi 		= str_func_new_body.lastIndexOf(';');
	var pos_lastreturn 		= str_func_new_body.lastIndexOf('return ');
	var pos_lastplus 		= str_func_new_body.lastIndexOf('+');

	//Figure out the signature function call portion
	var str_sigcall = str_func_new_body.substring(pos_sigcall, pos_lastsemi);
	str_sigcall = "signature_filter(" + str_sigcall + ")";

	//Add in the part we don't need to worry about
	var new_reply_func_body = str_func_new_body.substring(0, pos_lastreturn);
	
	//Do any signature?
	new_reply_func_body += 'if ( !GM_getValue("exclude_from_replies", false) ) { ' + "\n";

	//Put in the logic, directly into the function, to determine if we should float or not
	new_reply_func_body += 'if ( GM_getValue("sig_above_quote", true) ) return ' + str_sigcall + " + " + str_func_new_body.substring(pos_lastreturn+7, pos_sigcall-2) + ";\n";

	//Otherwise, don't change the order, but still apply the signature filter
	new_reply_func_body += 'else return ' + str_func_new_body.substring(pos_lastreturn+7, pos_sigcall-2) + "+ " + str_sigcall + ";\n";
	
	new_reply_func_body += '}//no signature in replies?' + "\n\n";

	//Wrap the function definition around the body, assign it to a variable, change it from a string to an actual function and return it
	var str_func_final = str_func_def + "\n " + new_reply_func_body + "}//end func \n var testme = " + func_name + ";";
	eval(str_func_final);

	return testme;

}//end update_func_reply

/**
 * Rewrite the compose function
 */
function update_func_compose(func_name)
{

	//Turn the function into a string
	//Was having problems calling some original GMail functions from inside of my rewritten
	//functions, so I store all gmail functions and call them locally - gsf_escape_functions redeclares all
	//the necessary functions
	var str_func      		= gsf_escape_functions(js[func_name].toString());

	//Extract the function definition (ie function something(whatever, whatever))
	var str_func_def   		= gsf_get_func_def(func_name, str_func);

	//Extract the function body (everything between the first { after the definition and the last })
	var str_func_body  		= gsf_get_func_body(str_func);

	//make a copy of the body
	var str_func_new_body 	= str_func_body;

	//The line we are interested looks like this: d += Xp(e);
	//We do some fancy regular expressions this time around
	var arr_match			= str_func_new_body.match(/[a-zA-Z]+\s*\+=/);		//arr_match[0] = d +=
	var var_name			= arr_match[0].replace(/\s*\+=/, "");				//Strip out the +=, so var_name = d

	//Find the line that handles the signature
	var regexp				= new RegExp(escapeRegExChars(var_name) + "\\s*\\+=\\s*gsf_temp_window\\." + escapeRegExChars(sig_func_name) + "\\(.*\\);");
	var arr_match			= str_func_new_body.match(regexp);
	var sig_line			= arr_match[0];

	//Figure out the parameters being passed into the signature function, only doing this to make sure we pass them in appropriately
	var pos_open_paren		= sig_line.indexOf('(');
	var pos_close_paren		= sig_line.indexOf(')');
	var parameters			= sig_line.substring(pos_open_paren+1, pos_close_paren);

	//Create the new sig line, with the call to GM_getValue to decide if we are doing the float or not
	var new_sig_line 		= 'if ( GM_getValue("sig_above_quote", true) ) ' + var_name + " = signature_filter(gsf_temp_window." + sig_func_name + "(" + parameters + ")) + " + var_name + ";\n";
		new_sig_line 		+= ' else ' + var_name + " += signature_filter(gsf_temp_window." + sig_func_name + "(" + parameters + "))";

	//Replace the old sig line with the new
	str_func_new_body 		= str_func_new_body.replace(regexp, new_sig_line);

	//Create the new function as a string
	var str_func_final 		= str_func_def + "\n GM_log('compose'); " + str_func_new_body + "\n }" + "\n var testme = " + func_name + ";";

	//Eval the string to an actual function with the varname "testme"
	eval(str_func_final);

	//Return the new function
	return testme;

}//end update_func_compose

/**
 * Rewrite the compose to function from contact list
 */
function update_func_compose_to(func_name)
{

	//Turn the function into a string
	//Was having problems calling some original GMail functions from inside of my rewritten
	//functions, so I store all gmail functions and call them locally - gsf_escape_functions redeclares all
	//the necessary functions
	var str_func      		= gsf_escape_functions(js[func_name].toString());

	//Extract the function definition (ie function something(whatever, whatever))
	var str_func_def   		= gsf_get_func_def(func_name, str_func);

	//Extract the function body (everything between the first { after the definition and the last })
	var str_func_body  		= gsf_get_func_body(str_func);

	//make a copy of the body
	var str_func_new_body 	= str_func_body;

	//Find the part that calls the signature function
	var regexp				= new RegExp("gsf_temp_window\\." + escapeRegExChars(sig_func_name) + "\\(.*\\)");
	var arr_match			= str_func_new_body.match(regexp);
	var sig_line				= arr_match[0];

	//Wrap that part in signature_filter
	var regexp				= new RegExp(escapeRegExChars(sig_line));
	str_func_new_body		= str_func_new_body.replace(regexp, "signature_filter(" + sig_line + ")");

	//Create the new function as a string
	var str_func_final 		= str_func_def + "\n " + str_func_new_body + "\n }" + "\n var testme = " + func_name + ";";

	//Eval the string to an actual function with the varname "testme"
	eval(str_func_final);

	//Return the new function
	return testme;

}//end update_func_compose

/**
 * Using this function, we can apply different functions to the signature
 */
function signature_filter(signature)
{
	if ( GM_getValue('clear_dashes', false) ) {
		signature = remove_dashes(signature);

	}

	if ( GM_getValue('allow_html', false) ) {
		signature = htmlspecialchars_decode(signature);

	}

	return signature;

}//end signature_filter

/**
 * Removes dashes from signature
 */
function remove_dashes(signature)
{
	if ( !GM_getValue('clear_dashes', false) ) {
		return signature;

	}

	var regexp_html = /^<br clear=all><br>-- <br>/;
	var regexp_plain = /^\n\n-- \n/;
	var has_html_signature = signature.search(regexp_html);
	var has_plain_signature = signature.search(regexp_plain);

	if ( has_html_signature != -1 ) {
		return signature.replace(regexp_html, '<br clear=all><br>');

	} else if ( has_plain_signature != -1 ) {
		return signature.replace(regexp_plain, "\n\n");

	} else {
		return signature;

	}

}//end removeDashes

function htmlspecialchars_decode(signature)
{
	signature = signature.replace(/&amp;/g, "&");

	signature = signature.replace(/&quot;/g, '"');

	signature = signature.replace(/&lt;/g, "<");

	signature = signature.replace(/&gt;/g, ">");

	return signature;


}//end html_unentities

/**
 * Returns the function definition portion of a function
 */
function gsf_get_func_def(func_name, str_func)
{
	var regex = new RegExp("function " + escapeRegExChars(func_name) + "\\(.*\\)\\s*{", "ig");
	var arr_match = str_func.match(regex);
	return arr_match[0];

}//end gsf_get_func_def

/**
 * Returns the body portion of the function
 */
function gsf_get_func_body(str_func)
{
	var firstpos = str_func.indexOf('{');
	var lastpos  = str_func.lastIndexOf('}');
	return str_func.substring(firstpos+1, lastpos);

}//end gsf_get_func_body

/**
 * Adds "gsf_temp_window." before all function calls in the function definition passed in
 *
 */
function gsf_escape_functions(str_func)
{
	//Get params
	var regexp = /\(.*\)/;
	var params = str_func.match(regexp)[0];
	params	= params.replace(/\(/g, '');
	params	= params.replace(/\)/g, '');
	params	= params.replace(/\s*/g, '');
	arr_params = params.split(',');

	//var regexp = /[\.]?[(a-zA-Z|0-9|\$)]+\(/g;
	var regexp = /[\.\s\(]?[a-zA-Z0-9\$\_]+\(/g;
	var arr = str_func.match(regexp);

	if ( !arr || ( arr && arr.length == 1 ) ) {
		//No external functions found
		return str_func;
	}

	//Step through the results, starting with the second entry
	for ( var i=1; i<arr.length; i++ ) {
		//As long as it's not a method call
		if ( arr[i].substring(0, 1) != "." ) {
			//Check to make sure it's not a parameter
			var call_name = arr[i].replace(/\s*/, '').replace(/\(/, '');

			if ( !in_array(call_name, arr_params) ) {
				//Escape any regexp characters
				var regexp = new RegExp(escapeRegExChars(arr[i]));

				//Check to see if we need to prefix with something
				if ( arr[i].substr(0, 1) == " " || arr[i].substr(0, 1) == "(" ) {
					var prefix = arr[i].substr(0, 1);
					var function_name = arr[i].substr(1);

				} else {
					var prefix = "";
					var function_name = arr[i];

				}

				str_func = str_func.replace(regexp, prefix + "gsf_temp_window." + function_name);

			}

		}

	}//for

	return str_func;

}//end gsf_escape_functions

/**
 * Check if needle is in haystack
 *
 * @param {Object} needle
 * @param Array haystack
 */
function in_array(needle, haystack)
{
	for ( var i=0; i<haystack.length; i++ ) {
		if ( haystack[i] == needle ) {
			return true;

		}

	}//for i

	return false;

}//end in_array

/**
 * Escapes common RegExp characters that also show up in code
 */
function escapeRegExChars(str)
{
	//$
	str = str.replace(/\$/g, "\\$");
//	GM_log('escape $: ' + str);

	//(
	str = str.replace(/\(/g, "\\" + "(");
//	GM_log('escape (: ' + str);

	//)
	str = str.replace(/\)/g, "\\)");
//	GM_log('escape ): ' + str);

	//whitespace
	str = str.replace(/\s/g, " ");

	return str;

}//end escapeRegExChars

})();