The Code
This user script runs in both the Reply and Compose frames in Gmail. These can be identified by their view=cw and view=page query string parameters.
The script listens for click events on the Send buttons. When the user clicks one of them, the script gets the contents of the message text box and scans each line of the message for any occurrences of specific keywords. Since messages can contain quoted text copied from a previous message, we intentionally ignore lines that start with >.
If we find any occurrences of attachment or files, we check whether there are any attachments. If there are no attachments, we prompt the user to confirm that she really wants to send the message.
TIP
You can access forms, form elements, images, and links by their elements' name attributes as well as their id attributes. If the element you need to modify is one of these types but doesn't have a unique id attribute, check to see if it has a unique name attribute instead.
Save the following user script as missingattachments.user.js:
// ==UserScript==
// @name Missing Attachment
// @namespace http://youngpup.net/
// @description Warn before sending Gmail messages without attachments
// @include http*://mail.google.com/mail/?*view=cv*
// @include http*://mail.google.com/mail/?*view=page*
// ==/UserScript==
// based on code by Aaron Boodman
// and included here with his gracious permission
// add more keywords here if necessary
var words = ["attach", "attachment", "attached", "file", "files"];
// creates a regex like of the form /\b(foo|bar|baz)\b/i
var regex = new RegExp("\\b(" + words.join("|") + ")\\b", "i");
var form = document.getElementById("compose_form");
document.addEventListener("click", function(e) {
if (e.target.id != "send") { return true; }
var allLines = form.elements.namedItem('msgbody').value.split("\n");
for (var i = 0, line; line = allLines[i]; i++) {
// by convention, reply lines start with ">". Some people like
// to be clever and use other characters. If you encounter this,
// you can test for those characters as well.
if (line[0] == ">") { continue; }
if (!line.match(regex)) { continue; }
if (isFileAttached()) { continue; }
if (!window.confirm("WARNING\n\n" +
"This message mentions attachments, but none " +
"are included.\n\n" +
"Really send?\n\n" +
"Suspicious line:\n" +
"\"" + line + "\"")) {
e.stopPropagation();
}
break;
}
}, true);
function isFileAttached() {
var iter = document.evaluate(".//input[@type='file']",
form, null, XPathResult.ANY_TYPE, null);
var input;
while (input = iter.iterateNext()) {
if (input.value != "") {
return true;
}
}
return false;
}
The word-boundary regular expression assertion \b is the best way to tell the difference between foo and foobar. The word boundary matches when a word character (a-z, A-Z, 0-9, -and _) is preceded or followed by a nonword character.
It's fast and easy to create regular expressions in JavaScript using the literal form (i.e., /foobar/). But you might need to construct an expression dynamically, from a string that you don't know beforehand. To do this, create an instance of the RegExp object, which takes a string argument. This creates an additional problem: backslashes have special meaning inside JavaScript strings. To insert a backslash in the regular expression defined as a string, you need two backslashes. So, /hello\?/ becomes "hello\\?", and /c:\\/ becomes "c:\\\\".