// webmail compose user js script
// http://shadow2531.com/opera/userjs/webmail_compose.js
// Cleaner (and smaller) version of this script is at http://shadow2531.com/opera/userjs/webmail_compose_clean.js
// Another version is at http://shadow2531.com/opera/userjs/webmail_compose_lite.js
// A newer version that I'm messing with is at http://shadow2531.com/opera/userjs/BeforeMailtoURL.js
// Contact: mailto:shadow2531@gmail.com?subject=webmail%20compose%20user%20js
// Allows you to associate left-clicking on mailto links with a webmail.
// Middle-click or right-click on the mailto link to override
// Just set the compose format string for your webmail. That's it.


(function() {
    // Replacement keys (case-sensitive):
    // %T = encoded TO value
    // %S = encoded Subject value
    // %M = encoded Body value
    // %C = encoded CC value
    // %B = encoded BCC value
    // %% = %
    
    // Gmail for Opera while Google fixes Opera Gmail 2.0 support for Opera
    var formatstring = "http://mail.google.com/mail/?ui=1&view=cm&fs=1&to=%T&su=%S&body=%M&cc=%C&bcc=%B";

    // Gmail 2 for Opera (when they fix Opera support). Will actually need to test this later to see if it works.
    //var formatstring = "http://mail.google.com/mail/?view=cm&fs=1&to=%T&su=%S&body=%M&cc=%C&bcc=%B#compose"

    // Gmail (perhaps obsolete since the changes)
    //var formatstring = "http://mail.google.com/mail/?view=cm&fs=1&to=%T&su=%S&body=%M&cc=%C&bcc=%B";
    
    // Gmail https (perhaps obsolete since the changes)
    //var formatstring = "https://mail.google.com/mail/?view=cm&fs=1&to=%T&su=%S&body=%M&cc=%C&bcc=%B";
    
    // Operamail (They don't send the page as utf-8, so unicode characters might not show correctly.)
    //var formatstring = "http://mymail.operamail.com/scripts/mail/Outblaze.mail?compose=1&did=1&a=1&to=%T&subject=%S&body=%M&cc=%C&bcc=%B";
    
    // Squirrelmail (Make sure to set your Squirrelmail to use utf-8 as the charset for pages so unicode characters show correctly.)
    //var formatstring = "http://your_squirrelmail_server.com/src/compose.php?send_to=%T&subject=%S&body=%M&send_to_cc=%C&send_to_bcc=%B";
    
    // Classic yahoo webmail (Yahoo turns " in fields to entities. They also don't send the page as utf-8, so unicode chars might not show correctly.)
    // var formatstring = "http://compose.mail.yahoo.com/?To=%T&Subj=%S&Cc=%C&Bcc=%B&Body=%M"; 
    
    // No need to modify anything below unless you want to play.
    
    // Before the page has a chance to touch anything, store references to functions that will be used.
    
    var call = Function.prototype.call;
    var addEventListener = Document.prototype.addEventListener;
    var indexOf = String.prototype.indexOf;
    var substring = String.prototype.substring;
    var substr = String.prototype.substr;
    var split = String.prototype.split;
    var toLowerCase = String.prototype.toLowerCase;
    var search = String.prototype.search;
    var replace = String.prototype.replace;
    var preventDefault = Event.prototype.preventDefault;
    var encodeURI = window.encodeURIComponent;
    var decodeURI = window.decodeURIComponent;
    var join = Array.prototype.join;
    var openlink = window.open;
    

    // An advanced MailtoURIParser class (That does normalizing and a bunch of other stuff.)
    // This is not its main purpose and is overkill for this script, but it works nicely.
    
    // This is only really needed for advanced links like the one at http://shadow2531.com/opera/testcases/mailto/modern_mailto_uri_scheme.html#test
    // but it works so nicely on simple links also.
    // You can create a non-advanced script without this parser with just a few lines of code.
    
    // This is a modified version of the JS one in http://shadow2531.com/opera/testcases/mailto/MailtoURIParserPack.zip
    // The main difference is, this one's set up to use the function references above.
    
    // You can comment out or remove unused parts etc.
    // You can also do some performance tweaks (but they are not needed).
    
    // It's also pretty simple to add support for other hvalues like in-reply-to etc.
    
    // MailtoURIParser class
    // Copyright Michael Anthony Puls II, http://shadow2531.com/
    // Distributed under the Boost Software License, Version 1.0.
    // See http://boost.org/LICENSE_1_0.txt
    
    function MailtoURIParser(inuri) {
        this.setURI(inuri);
    }
    
    MailtoURIParser.prototype = {
        
        // Whenever a URI is set, if it's a mailto URI, parse and store the data.
        // If it's not a mailto URI, reset the data.  setURI("") can be used to
        // clear the data.
        
        setURI : function(inuri) {
            search.call = substr.call = replace.call = join.call = call; // setting .call for each function used to the stored call above.
            if (search.call(inuri, /mailto:/i) == 0) {
                // Create a parseable string
                var parseable = substr.call(inuri, 4);
                parseable = replace.call(replace.call(parseable, /\:/, '='), /\?/, '&');
                this.storeData(parseable);
                // Store the original URI as-is.
                this.uri = inuri;
                // Store a normalized URI with just the basic hnames and no duplicate hnames.
                this.nuri = join.call(["mailto:", this.to, "?subject=", this.subject, "&body=", this.body, "&cc=", this.cc, "&bcc=", this.bcc], "");
            } else {
                this.uri = this.nuri = "mailto:";
                this.to = this.dto = this.subject = this.dsubject = this.body = this.dbody = this.cc = this.dcc = this.bcc = this.dbcc = "";
            }
        },
        
        // These getter functions can be commented out or removed since they are not used.
        getURI : function() {
            return this.uri;
        },
        getNormalizedURI : function() {
            return this.nuri;
        },
        getEncodedTO : function() {
            return this.to;
        },
        getDecodedTO : function() {
            return this.dto;
        },
        getEncodedSubject : function() {
            return this.subject;
        },
        getDecodedSubject : function() {
            return this.dsubject;
        },
        getEncodedBody : function() {
            return this.body;
        },
        getDecodedBody : function() {
            return this.dbody;
        },
        getEncodedCC : function() {
            return this.cc;
        },
        getDecodedCC : function() {
            return this.dcc;
        },
        getEncodedBCC : function() {
            return this.bcc;
        },
        getDecodedBCC : function() {
            return this.dbcc;
        },
        
        // %w = original uri
        // %n = normalized uri (mailto URI with just the basic hnames with no duplicate hnames)
        // %t = decoded TO value
        // %T = encoded TO value
        // %s = decoded Subject value
        // %S = encoded Subject value
        // %m = deocded Body value
        // %M = encoded Body value
        // %c = decoded CC value
        // %C = encoded CC value
        // %b = decoded BCC value
        // %B = encoded BCC value
        // %% = %
        // An invalid %key or a % at the end of the string is treated literally.
        
        // (decoded versions have no use for webmail purposes.)

        // By example:
        // resolveCommandFormatString("%T") would return the value of the to string.
        // resolveCommandFormatString("%%") would return %.
        
        resolveCommandFormatString : function(s) {
            var ident = '%';
            var ret = "";
            for (var i = 0; i < s.length; ++i) {
                var c = s[i];
                if (c == ident && i + 1 < s.length) {
                    var next = s[i + 1];
                    switch (next) {
                        case ident:
                            ret += ident;
                            break;
                        case 'w':
                            ret += this.uri;
                            break;
                        case 'n':
                            ret += this.nuri;
                            break;
                        case 'T':
                            ret += this.to;
                            break;
                        case 't':
                            ret += this.dto;
                            break;
                        case 'S':
                            ret += this.subject;
                            break;
                        case 's':
                            ret += this.dsubject;
                            break;
                        case 'M':
                            ret += this.body;
                            break;
                        case 'm':
                            ret += this.dbody;
                            break;
                        case 'C':
                            ret += this.cc;
                            break;
                        case 'c':
                            ret += this.dcc;
                            break;
                        case 'B':
                            ret += this.bcc;
                            break;
                        case 'b':
                            ret += this.dbcc;
                            break;
                        default:
                            ret += c;
                            ret += next;
                            break;
                    }
                    ++i;
                } else {
                    ret += c;
                }
            }
            return ret;
        },
        
        // Split the parsable data and store it. Rules are from http://shadow2531.com/opera/testcases/mailto/modern_mailto_uri_scheme.html#duplicates
        // For to, cc and bcc: Join all non-empty hvalues by %2C%20
        // For body: Join the first non-empty hvalue and all hvalues (even if they're empty) after that with %0D%0A 
        // For subject: Use only the last subject hvalue (even if it's empty and even if a previous one is not)
        
        storeData : function(parseable) {
            split.call = indexOf.call = substring.call = substr.call = toLowerCase.call = call;
            this.to = this.subject = this.body = this.cc = this.bcc = "";
            var hlist = split.call(parseable, '&');
            for (var i = 0; i < hlist.length; ++i) {
                var eq = indexOf.call(hlist[i], '=');
                if (eq == -1) {
                    continue;
                }
                var hname = toLowerCase.call(substring.call(hlist[i], 0, eq));
                var value = substr.call(hlist[i], eq + 1);
                if (hname == "to") {
                    if (value != "") {
                        if (this.to != "") {
                            this.to += "%2C%20";
                        }
                        this.to += value;
                    }
                } else if (hname == "cc") {
                    if (value != "") {
                        if (this.cc != "") {
                            this.cc += "%2C%20";
                        }
                        this.cc += value;
                    }
                } else if (hname == "bcc") {
                    if (value != "") {
                        if (this.bcc != "") {
                            this.bcc += "%2C%20";
                        }
                        this.bcc += value;
                    }
                } else if (hname == "subject") {
                    this.subject = value;
                } else if (hname == "body") {
                    if (!(value == "" && this.body == "")) {
                        if (this.body != "") {
                            this.body += "%0D%0A";
                        }
                        this.body += value;
                    }
                }
            }
            
            // Create the decode values
            
            this.dto = this.decodex(this.to);
            this.dsubject = this.decodex(this.subject);
            this.dbody = this.decodex(this.body);
            this.dcc = this.decodex(this.cc);
            this.dbcc = this.decodex(this.bcc);
            
            // Recreate the encoded values from the decoded ones to fix any chars that should not be encoded and fix ones that should, but are not
            
            this.to = this.encodex(this.dto);
            this.subject = this.encodex(this.dsubject);
            this.body = this.encodex(this.dbody);
            this.cc = this.encodex(this.dcc);
            this.bcc = this.encodex(this.dbcc);
        },
        
        // These functions normalize newlines when they encode/decode
        // encodex expects a string that already has newlines normalized like the way decodex does. (as in \r\n -> \n and then \r -> \n)
        encodex : function(s) {
            encodeURI.call = replace.call = call;
            try {
                return encodeURI.call(this, replace.call(s, /\n/g, "\r\n"));
            } catch (x) {
                return s;
            }
        },
        decodex : function(s) {
            decodeURI.call = replace.call = call;
            try {
                return replace.call(replace.call(decodeURI.call(this, s), /\r\n/g, '\n'), /\r/g, '\n');
            } catch (x) {
                return s;
            }
        }
    };
    
    // Set up left-click event listener for the whole page. If you click on a mailto link, this overrides the default value
    // and loads the webmail compose URI instead, in a new tab.
    
    addEventListener("click", function(e) {
        search.call = openlink.call = preventDefault.call = call;
        if (e && e.target && e.button == 0 && e.target.nodeName && toLowerCase.call(e.target.nodeName) == "a" && e.target.href && search.call(e.target.href, /mailto:/i) == 0) {
            preventDefault.call(e);
            var x = new MailtoURIParser(e.target.href);
            var uri = x.resolveCommandFormatString(formatstring); // formatstring is defined at the top
            openlink.call(this.defaultView, uri);
        }
    }, false);
})()