2008/05/09

Gmail 2.0 で複数の署名を切り替える

GmailGmail Template Switch

僕は普段Gmailを使っていて、Greasemonkeyスクリプトの「Gmail Template Switch」を愛用しています。


複数のアカウントで使用したり、差出人によって署名・挨拶文を変えたい場合に非常に便利なGreasemonkeyスクリプトです。


Gmail Template Switch

大抵皆そうなんだろうけど、メールを書く場合決まった形があって、あいさつ文・本文・締め言葉・署名という順番で書いている。以前作ったスクリプト で、署名は差出人に応じて自動的に切り替わるようになったけど、あいさつ文や締め言葉は辞書に登録したりして、毎回入力してたわけです。会社で使っていることもあり、社内と社外で定型文が変わってくるので、辞書に登録した語句を忘れたりしてかなり不便だった。これはさすがに面倒なので、さらに Gmail を快適にすべく、テンプレートを切り替えられる Greasemonkey スクリプトを作ってみた。これで署名が複数あっても、定型文が複数あっても平気ですな。


Gmail にテンプレート切り替え機能を付けてみた - 記憶は削除の方向で



しかしながら、Gmail Template Switch は、Gmail 2.0では動作しないため、動作が快速でラベルの色分け等便利な機能は我慢し、日本語版Gmailを使っていたわけですが、先日ついに、日本語版Gmailも 2.0になってしまいました。


というわけで、Gmail Template Switch を、Gmail 2.0 で動作するようにハックしてみました。


Gmail Template Switcher - v 2.0


既存のデータは生かしたかったので、コードはほとんど流用しています。なお、jQueryの使用で意味不明なエラーが出たので、使わないようにハックしてます。

また、Gmail 2.0は、Greasemonkeyを正式にサポートするらしく、GmailGreasemonkey10APIが公開されているので、せっかくなので使ってみました。


はじめは軽い気持ちでやってみたのですが、Greasemonkeyは初めて、かつ、Gmail 2.0 になってずいぶん変わっているようで、とても苦戦しました・・・(汗


とくに、ElementのIDがランダムで変わるのと、Reply時の入力フォームが動的に挿入される点でかなりはまりました・・。


なお、操作方法等はオリジナルとかわりません。もちろん新しい機能なんてありませんw


以下、コード全文です。


※最新のコードはこちら


Update:

Safari (Greasekit), Google Chrome, Opera で動作を確認しました。

Update2:

Google Chrome Extension 版を作成しました。


gmailtemplateswitcherv20.user.js
// ==UserScript==
// @name Gmail Template Switcher - v 2.0
// @namespace http://www.r-stone.net/blogs/ishikawa
// @description Append the function to apply the mail template, when writing a mail. Modify source code of [Gmail Template Switch] from http://d.hatena.ne.jp/re_guzy by re_guzy
// @version 0.1.20080510.0
// @include http://mail.google.com/*
// @include https://mail.google.com/*
// @exclude http://mail.google.com/mail/help/*
// @exclude https://mail.google.com/mail/help/*
//
// Copyright (c) 2007-2008, re_guzy <goodspeed.xii@gmail.com>
// Distributed under the MIT license
// http://opensource.org/licenses/mit-license.php
// http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
//
// Notice : To Uninstall this script, remove "gtssettings0-9" from Gmail contact list.
// Feature: When writing a mail, append a combobox to action. By selecting action,
// apply template to mail, or add template or remove template. Template
// is saved to "contact list" named starting with "gtssettings".
// Require: Greasemonkey 0.7.20080121.0
// ==/UserScript==

const DEBUG = false;
const KEY_TOKEN = "gts_token";
const KEY_CACHE = "gts_cache";
const CONTACT_NAME = "gtssettings";
const CONTACT_ID_RE = /\["\w+","(\w+)","gtssettings\d","gtssettings\d",/;
const MSGBODY_RE = /([\s\S]*)\n?(?:^---)(\n[\s\S]+)/m;
const LOCATION_RE = /(https?:\/\/[^\/]+\/(a\/[^\/]+\/)?).*/;
const SELECTOR = {
'gts' : 'descendant::*[local-name() = "select" or local-name() = "SELECT"][@id = "id_gts_template"]',
'input_form' : 'descendant::*[local-name() = "form" or local-name() = "FORM"]',
'body' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "body"]',
'subject' : 'descendant::*[local-name() = "input" or local-name() = "INPUT"][@name = "subject"]',
'to' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "to"]',
'from' : 'descendant::*[local-name() = "select" or local-name() = "SELECT"][@name = "from"] | descendant::*[local-name() = "input" or local-name() = "INPUT"][@name = "from"]',
'cc' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "cc"]',
'bcc' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "bcc"]',
'gts_undo' : 'descendant::*[contains(concat(" ",@class," "), " gts_undo_option ")]',
'gts_first' : 'descendant::*[contains(concat(" ",@class," "), " gts_option_first ")]',
'discard' : 'descendant::*[local-name() = "button" or local-name() = "BUTTON"][count(preceding-sibling::*[local-name() = "button" or local-name() = "BUTTON"]) = 2]',
'labels' : 'descendant::*[local-name() = "span" or local-name() = "SPAN"]',
'msg' : 'div/div/div/div/div/div/div/div/div/div/div[3]/div/div[2]/div[2]/div/div[2]/div[2]/div/table/tbody/tr[2]/td[2]'
}

var T = new Array(10);
T[0] = { 'id' : -1, 'num' : 0 };
for (var i=1;i < T.length;i++) {
T[i] = {
'id' : -1,
'num' : i,
'from' : '',
'to' : '',
'cc' : '',
'bcc' : '',
'subject' : '',
'body' : '',
'body_latter' : ''
}
}
var Ja = false;
var recentView;

//Initialize gmail and gmonkey objects
window.addEventListener('load', function() {
if (unsafeWindow.gmonkey) {
unsafeWindow.gmonkey.load('1.0', function(gmail) {

function getViewType() {
var str = '';
switch (gmail.getActiveViewType()) {
case 'tl': str = 'Threadlist'; break;
case 'cv': str = 'Conversation'; break;
case 'co': str = 'Compose'; break;
case 'ct': str = 'Contacts'; break;
case 's': str = 'Settings'; break;
default: str = 'Unknown';
}
return str;
}

function getView() {
return gmail.getActiveViewElement();
}

function getMailForm() {
var a = xpath(SELECTOR['input_form'], getView());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getGts() {
var a = xpath(SELECTOR['gts'], getView());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getGtsOptUndos() {
return a = xpath(SELECTOR['gts_undo'], getGts());
}

function getGtsOptFirst() {
var a = xpath(SELECTOR['gts_first'], getGts());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getFrom(form) {
var a = xpath(SELECTOR['from'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getTo(form) {
var a = xpath(SELECTOR['to'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getCc(form) {
var a = xpath(SELECTOR['cc'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getBcc(form) {
var a = xpath(SELECTOR['bcc'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getSubject(form) {
var a = xpath(SELECTOR['subject'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getBody(form) {
var a = xpath(SELECTOR['body'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getDiscard() {
var a = xpath(SELECTOR['discard'], getMailForm().parentNode.parentNode);
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getLabels(form) {
return a = xpath(SELECTOR['labels'], form || getMailForm());
}

function getCcLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/Cc/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function getBccLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/Bcc/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function getChangeLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/change/.exec(a[i].innerHTML) || /変更/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function switcher() {
str = getViewType();

if (str != "Compose" && str != "Conversation") {
if (recentView) {
recentView.removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
}
return;
}
recentView = getView();
window.setTimeout(function() {
initialize();
}, 600);
}

function nodeInsertedHandler(event) {
target = event.target;
if (target.nodeType == 1) {
tagName = target.tagName.toLowerCase();
if (tagName == 'form') {
log('form inserted');
window.setTimeout(function() {
initialize();
}, 500);
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
} else if (tagName == 'table') {
log('table inserted');
window.setTimeout(function() {
initialize();
}, 500);
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
}
}
}

function initialize() {
log('initialize');

try {

if (getGts()) {
log('already initialized');
return;
}

var form = getMailForm();
if (!form) {
log('form not found');
getView().addEventListener('DOMNodeInserted', nodeInsertedHandler, false);
return;
}
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);

var discard_button = getDiscard();
var label = discard_button.innerHTML;
Ja = (label == '破棄');
discard_button.parentNode.insertBefore(createSelectElement(), discard_button.nextSibling);
composeCommand(form);
} catch(e) {
log('add combobox failure because : ' + e);
return;
}
}

function createSelectElement() {
var content = document.createElement('select');
content.setAttribute("id", "id_gts_template");
content.setAttribute("style", "margin-left:10px;font-size:.8em;");
content.innerHTML = toOption('please wait...' , false , true);
content.addEventListener('change', function(event) {doCommand(event.target)}, true);
return content
}

function toOption(text, value, selected, cls, isDom) {
if (isDom) {
var attr = {
'style' : value ? null : 'color: rgb(119, 119, 119);',
'disabled' : value ? null : 'disabled',
'selected' : selected ? 'selected' : null,
'value' : value ? value : null,
'class' : cls ? cls : null
};
var elm = document.createElement('option');
for (var i in attr) {
if (attr[i]) {
elm.setAttribute(i, attr[i]);
}
}
elm.innerHTML = text;
return elm;
} else {
var attr = {
'style' : value ? null : '"color: rgb(119, 119, 119);"',
'disabled' : value ? null : '"disabled"',
'selected' : selected ? '"selected"' : null,
'value' : value ? '"' + value + '"' : null,
'class' : cls ? cls : null
};
var a = [];
for (var i in attr) {
if (attr[i]) {
a.push(i + "=" + attr[i]);
}
}
return "<option " + a.join(' ') + ">" + text + "</option>";
}
}

function composeCommand(form) {
getTemplates(function /*parseTemplate*/(notes, use_cache) {
for (var i in notes) {
if (use_cache) {
T[i] = notes[i];
} else {
var note = notes[i].note ? decode(notes[i].note, false) : "{}";
try {
T[notes[i].num] = eval(note);
T[notes[i].num].id = notes[i].id;
T[notes[i].num] = decode(T[notes[i].num], true);
} catch(e) {log("eval failed : " + e);}
}
}

recomposeSelectElement(form);
applyDefault(form);
if (notes.length == 0) {
save();
} else if (!use_cache) {
var caches = [];
for (var i in T) {
var encoded = encode(T[i], true);
if (encoded) {
caches.push(encoded.toSource());
}
}
GM_setValue(KEY_CACHE, "[" + caches.join(", ") + "]");
}
});
}

function applyDefault(form) {
var from = getFrom();
if (from) {
var fromvalue = from.value;
matched = grep(T, function(i) {
return (i.name + '').indexOf('#') == 0 && i.from == fromvalue;
});
if (matched.length > 0) {
applyTemplate(matched[0].num, form);
}
}
}

function recomposeSelectElement(form) {
var options = [];
options.push(toOption(trans("Template actions...") , "init" , true, "gts_option_first"));

var enables = grep(T, function(o) {return (o.name && o.num != 0);});
var expand = function(arrays, cmd) {
var hash = {};
for (var i in enables) {
hash[enables[i].from] = hash[enables[i].from] || [];
hash[enables[i].from].push(enables[i]);
}
for (var i in hash) {
arrays.push(toOption(' <' + (i || trans('No from')) + '>'));
for (var j in hash[i]) {
arrays.push(toOption('  ' + hash[i][j].name , cmd + '_' + hash[i][j].num));
}
}
};

var tmp = [
{'cmd':'apply', 'exp':trans("Apply"), 'func':expand},
{'cmd':'add','exp':trans("Append"), 'func':function(arrays, cmd) {
var used = grep(T , function(o) { return (o.name && o.num != 0) });
if (used.length < 9) {
arrays.push(toOption('  ' + trans("Includes from") , cmd));
arrays.push(toOption('  ' + trans("Excludes from") , cmd + '_ignore_from'));
} else {
arrays.push(toOption('  ' + trans('Quantity limit is 9')));
}
}},
{'cmd':'delete','exp':trans('Remove'), 'func':expand}
];
for (var i in tmp) {
if (tmp[i].func != expand || enables.length != 0) {
options.push(toOption('-------'));
options.push(toOption(trans('verbs', tmp[i].exp) + ':'));
tmp[i].func(options, tmp[i].cmd);
}
}

var gts = getGts();
gts.innerHTML = options.join('');
gts.value = 'init';
}

function doCommand(selectNode) {
var form = getMailForm();
if (form) {
if (selectNode.value == 'add') {
addTemplate(form, true);
} else if (selectNode.value == 'add_ignore_from') {
addTemplate(form, false);
} else if (selectNode.value.match(/apply_(\d+)/)) {
applyTemplate(RegExp.$1 , form);
} else if (selectNode.value.match(/delete_(\d+)/)) {
deleteTemplate(RegExp.$1 , form);
} else if (selectNode.value == 'undo') {
if (unsafeWindow.gts_undo) { unsafeWindow.gts_undo(); }
}
selectNode.value= 'init';
} else {
log('form not found');
}
}

function addTemplate(form, contain_from) {
var m = trans('Please input the template name.');
if (contain_from) {
m += '\n';
m += trans('If the name is started from "#", it becomes default of the corresponding "from".');
}
var name = window.prompt(m, "");
if (!name) {return;}
if (grep(T , function(o) { return (o.name == name) }).length > 0) {
alert(trans('The name already exists.'));
return;
}

var empties = grep(T , function(o) { return (o.name || o.num == 0) }, true);
var t = empties[0];

var to = getTo(form);
var b = getBody(form);
var c = getCc(form);
var bc = getBcc(form);
var f = getFrom(form);
var s = getSubject(form);
T[t.num] = {
'num' : t.num,
'id' : t.id,
'name' : name,
'from' : contain_from ? f.options[f.selectedIndex].value : "",
'to' : to.innerHTML,
'cc' : c.innerHTML,
'bcc' : bc.innerHTML,
'subject' : s.value,
'body' : b.innerHTML
};

if (MSGBODY_RE.exec(T[t.num].body)) {
T[t.num].body = RegExp.$1;
T[t.num].body_latter = RegExp.$2;
}

editContact(T[t.num], function() {
recomposeSelectElement(form);
msg(trans('appended', name));
});
}

function applyTemplate(num , form) {
if (typeof T[0]._init == 'undefined') {
T[0]._init = {};
var selectors = x('f,s,t,c,bc,bo');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
T[0]._init[j.name] = trim(j.value);
}
}

var selectors = x('f,s,t,c,bc');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
var tmp = T[0]._init[j.name];
if (j.name != 'from' || j.value != T[num][j.name]) {
if (j.type == 'textarea') {
var targetaddrs = tmp || "";
//既に含まれているものは追加しない
var notcontains = grep(T[num][j.name].split(','), function(i) {
if (trim(i).length == 0) { return; }//空白は無視
if (/([\w\.+-]+@[\w+-]+(\.[\w+-]+)+)/.exec(i)) {
return targetaddrs.indexOf(RegExp.$1) < 0;
} else {
return targetaddrs.indexOf(i) < 0;
}
});
if (tmp) { notcontains.unshift(tmp); }
j.value = notcontains.join(', ');
} else {
j.value = (j.name == 'subject' && tmp) ? tmp : T[num][j.name];
}
if (j.name == 'cc' && T[num][j.name]) {
var cc_label = getCcLabel();
if (cc_label) {
emulate_click(cc_label);
}
}
if (j.name == 'bcc' && T[num][j.name]) {
var bcc_label = getBccLabel();
if (bcc_label) {
emulate_click(bcc_label);
}
}
}
}

var change_label = getChangeLabel();
if (change_label) {
emulate_click(change_label);
}

var b = getBody(form);
b.value = grep([
T[num].body, T[0]._init.body, T[num].body_latter
], function(i) { return i; }).join("\n\n");

var selectors = x('bo,s,t');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
if ((j.name == 'body') || !j.value) {
j.focus();
j.selectionStart = 0;
j.selectionEnd = 0;
}
}

var undos = [
toOption('-------', null, null, "gts_undo_option", true),
toOption('  ' + trans("Undo"), 'undo', null, "gts_undo_option", true)
];
var gts_undos = getGtsOptUndos();
for (var i=0; i<gts_undos.length; i++) {
gts_undos[i].parentNode.removeChild(gts_undos[i]);
}
var gts_first = getGtsOptFirst();
if (gts_first) {
gts_first.parentNode.insertBefore( undos[1], gts_first.nextSibling );
gts_first.parentNode.insertBefore( undos[0], gts_first.nextSibling );
}

msg(trans('applied', T[num].name), function() {
undo(form);
msg(trans("To apply template was canceled."));
}, true);
}

function undo(form) {
if (typeof T[0]._init != 'undefined') {
var selectors = x('f,t,c,bc,bo');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
j.value = T[0]._init[j.name];
}
}
delete T[0]._init;
var gts_undos = getGtsOptUndos();
for (var i=0; i<gts_undos.length; i++) {
gts_undos[i].parentNode.removeChild(gts_undos[i]);
}
}

function emulate_click(target) {
if (target.dispatchEvent) {
var e = unsafeWindow.document.createEvent("MouseEvents");
e.initEvent("click", true, true);
target.dispatchEvent(e);
}
}

function deleteTemplate(num , form) {
var name = T[num].name;
if (confirm(trans('remove confirm', name)) != true) {
return;
}

T[num] = {'id' : T[num].id, 'num' : num};
editContact(T[num], function() {
recomposeSelectElement(form);
msg(trans("removed", name));
});
}

function getTemplates(f_parseTemplate) {
var queryUrl = 'mail/contacts/data/contacts?thumb=false&groups=false&show=ALL&psort=Name&max=300&out=js&rf=&jsx=true';
ajax(queryUrl, function(req){
contactPage = req.responseText.replace('while (true); ', '').replace(/&&&START&&&([^&&&]+)&&&END&&&/, "$1");
response = eval("(" + contactPage + ")");
if (response.Success) {
var contacts = response.Body.Contacts;
var notes = [];
for(i=0; i<contacts.length; i++) {
if (contacts[i].Name && /gtssettings(\d)/.exec(contacts[i].Name) ) {
num = RegExp.$1;
note = contacts[i].Notes;
id = contacts[i].Id;
notes.push({'num' : num, 'note' : note, 'id' : id});
}
}
var authtoken = response.Body.AuthToken.Value;
GM_setValue(KEY_TOKEN, authtoken);
f_parseTemplate(notes);
} else {
log("Contacts Request Failed: " + response.Errors[0].Text);
}
});
}

function encode(tmpl, by_escape) {
if (by_escape) {
var escaped = {};
//encodeURIだと「'」がエンコードされないので、escapeを使う
for (var i in tmpl) {
if (i.indexOf('_') != 0) {
escaped[i] = escape(tmpl[i]);
}
}
return escaped;
} else {
//連絡先は「"」で囲まれるため、JSONデータを表すのに「"」を使えない。
//連絡先から復元するときに使うデータを、REGEXでマッチさせるため「"」を「'」に置換しておく。
return tmpl.toSource().replace(/\"/g, "'");
}
}

function decode(tmpl, by_unescape) {
if (by_unescape) {
var unescaped = {};
for (var i in tmpl) {
unescaped[i] = unescape(tmpl[i]);
}
return unescaped;
} else {
//連絡先に格納するために、「'」に変換しておいた「"」を戻す
return tmpl.replace(/\\'/g, "\"");
}
}

function editContact(tmpl, f_completed) {
var authtoken = GM_getValue(KEY_TOKEN);
if (!authtoken) {
log('token not found');
return;
}
var escaped = encode(tmpl, true);
var post_data = param({
"token" : authtoken,
"tok" : authtoken,
"out" : "js",
"id" : tmpl.id,
"action" : "SET",
"Name" : CONTACT_NAME + tmpl.num,
"Emails.0.Address" : CONTACT_NAME + tmpl.num + "@gmail.com",
"Notes" : encode(escaped, false)
});
ajax("mail/contacts/update/contact", function(req) {
var response = eval("(" + req.responseText.replace('while (true); ', '').replace(/&&&START&&&([^&&&]+)&&&END&&&/, "$1") + ")");
if (response.Success) {
if (tmpl.id == -1) {
if (CONTACT_ID_RE.exec(req.responseText)) {
tmpl.id = RegExp.$1;
editContact(tmpl, f_completed);
}
} else {
if (tmpl.num != 0) {
save(f_completed);
} else {
f_completed();
}
}
} else {
log("Update Contact Request Failed: " + response.Errors[0].Text);
}
}, 'POST', {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}, post_data);

}

function log(message) {
if (unsafeWindow && unsafeWindow.console && DEBUG) {
unsafeWindow.console.log(message);
}
}

function getCookie(name) {
var re = new RegExp(name + "=([^;]+)");
var value = re.exec(document.cookie);
return (value != null) ? decodeURI(value[1]) : null;
}

function msg(message, f_clicked, is_undo) {
unsafeWindow.gts_undo = f_clicked;
var a = xpath(SELECTOR['msg'], getView().ownerDocument.body);
if (a && a.length > 0) {
var td = a[0];
var div = td.parentNode.parentNode.parentNode.parentNode;
div.style.visibility = "visible";
td.innerHTML = "GTS : " + message;
if (is_undo) {
var span = document.createElement('span');
span.setAttribute("id", "gts_und");
span.setAttribute("class", "lk");
span.innerHTML = trans('Undo link');
span.addEventListener('click', f_clicked, true);
td.appendChild(span);
}
window.setTimeout(function() {
div.style.visibility = "hidden";
}, 60000);
}
}

function save(f_saved) {
T[0]['num'] = 0;
T[0].date = new Date();
editContact(T[0], f_saved ? f_saved : function() {});
}

function ajax(request_path, f_load, get_or_post, headers, data) {
window.setTimeout(function() {
GM_xmlhttpRequest({
'method': get_or_post ? get_or_post : "GET",
'url': getBaseLocation() + request_path,
'data': data,
'headers': headers,
'onload': f_load,
'onerror': function(req) {
log("Request Failed in error code: " + req.status);
}
});
}, 0);
}

function getBaseLocation() {
if (LOCATION_RE.exec(document.location)) {//for Google Apps
return RegExp.$1;
} else {
return 'http://mail.google.com/';
}
}

function trans(msg_id, opt) {
return {
'Template actions...' : Ja ? 'テンプレートの操作...' : msg_id,
'Apply' : Ja ? '適用' : msg_id,
'Append' : Ja ? '追加' : msg_id,
'Includes from' : Ja ? '差出人を含む' : msg_id,
'Excludes from' : Ja ? '差出人を除く' : msg_id,
'Quantity limit is 9' : Ja ? '最大9個です' : msg_id,
'Remove' : Ja ? '削除' : msg_id,
'verbs' : Ja ? (opt + 'するテンプレート') : (opt + ' template'),
'Please input the template name.' : Ja ? 'テンプレート名を入力してください。' : msg_id,
'If the name is started from "#", it becomes default of the corresponding "from".' :
Ja ? '名前を「#」から始めると、対応する差出人のデフォルトになります。' : msg_id,
'The name already exists.' : Ja ? 'その名前は既に存在します。' : msg_id,
'appended' : Ja ? ("テンプレート「" + opt + "」を追加しました。")
: ('Template "' + opt + '" was appended.'),
'applied' : Ja ? ("テンプレート「"+opt+"」を適用しました。")
: ('Template "' + opt + '" was applied. '),
'remove confirm' : Ja ? ("テンプレート「" + opt + "」を削除しますか?")
: ('Is template "' + opt + '" removed?'),
'removed' : Ja ? ("テンプレート「" + opt + "」を削除しました。")
: ('Template "' + opt + '" was removed.'),
'To apply template was canceled.' : Ja ? "テンプレートの適用は取り消されました。" : msg_id,
'Undo' : Ja ? "適用の取り消し" : msg_id,
'Undo link' : Ja ? "適用取り消し" : "Undo applied",
'No from' : Ja ? "差出人なし" : msg_id
}[msg_id] || msg_id;
}

function x(prefix) {
var result = [];

for (var i in SELECTOR) {
if (typeof prefix != 'undefined') {
var a = grep(prefix.split(','), function(j) { return i.indexOf(j) == 0; });
if (a.length != 0) { result.push(SELECTOR[i]); }
} else {
result.push(SELECTOR[i]);
}
}
return result;
}

/*
* this 'grep' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function grep( elems, callback, inv ) {
// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( typeof callback == "string" )
callback = eval("false||function(a,i){return " + callback + "}");

var ret = [];

// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ )
if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
ret.push( elems[ i ] );

return ret;
}

/*
* this 'param' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function param(a) {
var s = [];

// Serialize the key/values
for ( var j in a )
// If the value is an array then the key names need to be repeated
s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );

// Return the resulting serialization
return s.join("&").replace(/%20/g, "+");
}

/*
* this 'trim' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function trim(text) {
return (text || "").replace( /^\s+|\s+$/g, "" );
}

function xpath(query, target) {
var results = document.evaluate(query, target || document, null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var nodes = [];
for (var i=0; i<results.snapshotLength; i++) {
nodes.push(results.snapshotItem(i));
}
return nodes;
}

gmail.registerViewChangeCallback(switcher);
switcher();
});
}
}, true);

61 件のコメント:

test さんのコメント...

macのsafari,MailPlaneで利用したいです。

GreaseKitへのインストールはできたのですが、
テンプレート作成のプルダウンが出てきません。

そもそも対応していないのかもしれませんが、
ご回答いただければと思います。

よろしくお願い致します。

naoki さんのコメント...

>testさん
GreaseKitなんてものがあるんですね。知りませんでした・・・汗
基本的にFirefox以外では確認していませんが、GreaseKitは少し興味があるので、時間があるときに確認してみます。

Anonymous さんのコメント...

Mac OS X のFireFox3.0.3で試みたところ,PleasWait…で止まったり,そもそもプルダウンメニューが表示されなかったりします.

とりあえず,ご報告まで.

Anonymous さんのコメント...

業務でGoogleAppsを利用し始めました
宛先と件名はテンプレート化することが出来たのですが、本文がまったくテンプ適用できません
ダウンロード等の仕方が悪かったのでしょうか・・・・

winXP/forefoxで使用しています

naoki さんのコメント...

> 宛先と件名はテンプレート化することが出来たのですが
ということは、おそらく正常に動作していると思われます。
テンプレートの使用方法に間違いはないでしょうか?
使用方法は、本家である Gmail にテンプレート切り替え機能を付けてみた - 記憶は削除の方向で に詳しく記載されています。
参考になれば幸いです。

じゃじゃーん さんのコメント...

私も本文がテンプレートに適用されなかったのですが、本文の最後に---(改行)を入れると本文も適用されるようになりました。
その後改行を消して別名で登録しても本文が適用されるようになったので、改行有り無しで本文が適用されるわけではなさそうですが、参考にしてみてください。
使いこなせればとても便利ですね。
作者殿
有益なソフトの作成ありがとうございました。

naoki さんのコメント...

>testさん
大変おそくなりましたが、Safari(GreaseKit)での動作を確認しました。
こちらからどうぞ。

yukke さんのコメント...

Template Switcher いつも便利に使わせていただいてました。これがないと、安心してメールが出せない身体になってしまいましたw
ところが、本日になってからスクリプトがうまく動きません。よく見ると、Gmailのサイトのデザインが若干変わっているようですが、これが原因でしょうか?
今後ぜひご対応をお願いいたしたいのですが・・・

naoki さんのコメント...

> yukkeさん
コメントありがとうございます。

Gmailのデザインがかわり、GTSが英語バージョンになっていました。

こちらに最新のスクリプトがありますので、お試しください。

yukke さんのコメント...

はっ早っ!w
ドロップボックスの位置は変わりましたが、機能は以前と全く同様に使うことができるようになりました。
素早いご対応、ありがとうございました。

Unknown さんのコメント...

Google ChromeのBetaリリース(2.0.172.8)やDevリリース(2.0.174.0) では 引数に"--enable-user-scripts"を付けて起動すればGreasemetalがなくてもscriptが使えるようになったそうなのですが、うまく起動しないようです。

ogu-kome さんのコメント...

はじめまして。Gmail Stemplate Swicherを早速インストールしてみました。宛先と件名はテンプレート化することが出来たのですが、本文がまったくテンプ適用できません。先に書かれている方のように改行を入れたりしてみましたが、どうしても本文だけがテンプ適用できないのです。当方はFirebox使用です。できましたら助言をお願いします。

Unknown さんのコメント...

Google Chrome user scripts への対応有難うございます。

FUJIWARA KENJI さんのコメント...

署名が表示されない現象についておうかがいします。

firefox3.5にgmail3.5に対応のGmail Template Switch-v2.0をインストールしました。
タブが表示されるようになり、テンプレートの操作ができるようになっているのですが、登録したメールアドレスと件名は切り替わるのですが、本文が登録したものが表示されません。
いろいろ試してみて、テキスト表示するとhtml形式と見られる記述が表示されました。

リッチテキスト形式でも表示させて、切り替わるようにできるでしょうか。

ishikawa.rs さんのコメント...

> ogu-kome さん
現在テキスト形式のみ対応しています。
リッチテキスト形式になっていないでしょうか・・?

> BSOD さん
いえいえ、自分も Chrome がメインになってきています・・

> Y=Ken さん
ご意見ありがとうございます。
リッチテキスト対応検討してみます。

ogu-kome さんのコメント...

>naokiさん
ありがとうございます。リッチテキスト形式になっていたため、テキスト形式にしたら使用できるようなりました。

しかし、その後、Firefoxを3.5にアップデートしたところ、テンプレート選択欄が表示されたり、されなくなったりしてしまいました。こちらについても情報がありましたら教えていただけないでしょうか?

ishikawa.rs さんのコメント...

> ogu-kome さん
タイミングによっては表示されないことがあるようです・・・。そんなときは、F5 でリロードしてしのいでください(涙

ogu-kome さんのコメント...

>naokiさん
F5で対応できました!これで十分です。ありがとうございました。

rpc さんのコメント...

署名の保存ができないです。
Google Chrome
※起動用ショートカットに「--enable-user-scripts」の追加はしました。

Firefox + Greasemonkey

ともにコンボボックスまでは表示されるのですが、「差出人を含む」「差出人を除く」どちらも保存されません。
なにかほかに設定等ありますか?

試したOSはWinXP, WinVistaです。

ishikawa.rs さんのコメント...

> rpc さん
ご指摘ありがとうございます。
現象を確認しましたので、修正しました。
最新版で再度お試しください。

rpc さんのコメント...

取り急ぎ
> Google Chrome
> ※起動用ショートカットに「--enable-user-scripts」の追加はしました。
にて正常動作を確認しました。

素早い対応ありがとうございます

yukke さんのコメント...

Windows XP + Firefox3.6にて使わせていただいております。

土曜日まではちゃんと動いていたと記憶しているのですが、2010/1/31からドロップボックスが表示されないくなってしまいました。
同じような症状の方はいらっしゃいませんか? もしかして、gmail側のシステムに変更があったのでしょうか?

FUJIWARA KENJI さんのコメント...

私も同じ症状が出たことがあります。
私の場合はfirefoxを再起動、もしくは別Windowにて起動で改善しました。

匿名 さんのコメント...

時々なっていましたが今回はGmail側の仕様変更か何かで全く動かなくなっているようです。
Twitter上ではいくつか報告がありました。

ishikawa.rs さんのコメント...

> yukke さん
> Y=Ken さん
ご指摘ありがとうございます。
調べてみたところ、どうやら Google の Gmail Greasemonkey API が動作しなくなっていることが原因のようです。
http://code.google.com/p/gmail-greasemonkey/updates/list
回避方法などは現在調査中ですが、取り急ぎご報告いたします・・。

ishikawa.rs さんのコメント...

取り急ぎ、Gmail Greasemonkey API を使わないように暫定対応致しました。
スクリプトは下記から更新してください。
http://userscripts.org/scripts/show/26426

yukke さんのコメント...

いつもながらの素早いご対応、ありがとうございます。とりあえず、暫定版でちゃんと動作することを確認させていただきました。
にしても、APIが動かないのが原因だったとは…。早く復旧するといいですね。

匿名 さんのコメント...

対応ありがとうございます^^
いつもべんりに使わせていただいています♪

匿名 さんのコメント...

更新ありがとうございます!

ずけ☆ さんのコメント...

素早い対応ありがとうございます!
使い慣れたFirefox使えなくて
困ってました。。助かりましたっ!

mas10 さんのコメント...

原因不明で困っていたのですが、お陰で大変助かりました。ありがとうございます!

yukke さんのコメント...

スクリプトが公開されているサーバがダウンしているようですね。

yukke さんのコメント...

私の環境では、今日(2010/08/27)の午後から、Gmailのメール作成画面の表示が変更になったためとおもわれますが、スクリプトが起動しなくなってしまいました。

ishikawa.rs さんのコメント...

> yukke さん
いつもありがとうございます。
ご指摘の件ですが、こちらの環境では今のところ問題なく動作しているようです・・。
Firefox 3.6.8 with GTS 0.2.20100202.0
Chrome 7.0.503.0 dev with GTS 0.0.2

お手数ですが、yukke さんの環境をお知らせ頂けると幸いです。

yukke さんのコメント...

お騒がせして申し訳ありません。firefoxの再起動、システムの再起動を試しても症状が出るので、てっきりGmailの仕様変更があったのかとおもってしまいました。
どうやら、別のアドオンの不具合のせいで他のアドオンも動いていなかったようです。そのアドオンを削除したところ、GTSもちゃんと動くようになりました。
firefox3.6.8 + GTS 0.2.20100202.0

ご迷惑をおかけして、誠に申し訳ありませんでした。

W.B さんのコメント...

お世話になります。
Firefox ver 3.69で、独自ドメインの
GMailを使用しています。

プルダウンメニューは出てくるのですが、
Please Waitのまま動かなくなってしまいました。

お手数ですが対策等教えて
いただけませんでしょうか?

yukke さんのコメント...

いつもGTSにはお世話になっています。
先日Greasemonkeyを0.9.0にアップデートしたからでしょうか?GTSが動作しなくなってしまいました。ドロップボックスも出てこない状態です。
同じ症状のかたはいらっしゃいませんでしょうか?

yukke さんのコメント...

昨日報告しました、Greasemonkeyのバージョンアップに関連するとおもわれるGTSの動作不良ですが、先程Greasemonkeyを0.9.1にバージョンアップしましたところ、GTSが動作することを確認いたしました。
お騒がせいたしました。

ishikawa.rs さんのコメント...

> yukke さん
いつもご報告ありがとうございます!感謝です!

yukke さんのコメント...

最後の投稿が1年以上前になっていますね。ご無沙汰しています。
ここ数週間、GTS2.0がうまく動かないようです。FirefoxやGreasemonkeyの更新などを試しましたが、症状が改善しません。
具体的には、GTSメニューのドロップダウンリストが2つ表示されます。
上に表示されるリストは「Template actions...」の表示になりますが、操作しても何も起こりません。
下に表示されるリストは、いつまで待っても「please wait...」のままで操作できません。

Firefox 11.0
Greasemonkey 0.9.18
GTS2.0 V2.0 0.2.20111104.0
いずれも最新版だとおもいます。

よろしくご確認下さい。

ishikawa.rs さんのコメント...

> yukke さん
大変ご無沙汰しております…。
最近投稿できずにすみません。でも、なんとか元気にやっています(笑

GTS 更新しました。
お手数ですが、ご確認お願いいたします。
http://userscripts.org/scripts/show/26426

Twitter の方でも最新情報(?)流していますので、よろしければフォローいただけるとありがたいです。
https://twitter.com/#!/ishikawa_rs

いつもいつもご報告いただいありがとうございます!

yukke さんのコメント...

ほんとうにいつもながらの素早いご対応、ありがとうございます。また、お元気そうで何よりですw

スクリプトをアップデートしましたところ、正常に動作することを確認させて頂きました。ありがとうございます。

Twitterというやつは、アカウントはあるもののどうやって使うのかよく理解していないのですが、とりあえずフォローさせて頂きました。

ところで、ここから先は単なる希望なのですが、GreasemonkeyはGoogle chromeにも搭載されているようです。Firefoxのそれと同一であるのかは存じませんが、少なくともGTSをchromeにそのままインストールしただけでは全く変化がありませんでした。
今後chromeへの対応が可能であれば、ぜひご検討いただければとおもいます。

いつも便利なスクリプトを使わせていただいて、ありがとうございます。

ishikawa.rs さんのコメント...

> yukke さん
ご確認ありがとうございます。

Chrome 版は、Chrome ウェブストアからインストールできます。
https://chrome.google.com/webstore/detail/dcafmdckachnenigiihdmmkjaenneoca?hl=ja

実は、僕は普段 Chrome を使用しているので、今回の修正も Chrome 版の方が早かったりしています(汗

今後とも、どうよろしくお願い申し上げます。

yukke さんのコメント...

なるほど、既に発表されていたのですね。失礼いたしました。早速インストールさせていただきまして、もちろん今までの設定データで問題なく動くのを確認させて頂きました。
これで後顧の憂いなく、FFからchromeに乗り換えることができそうです。

これからもGTS・・・じゃなくてTSGにはお世話になります。

s_minami さんのコメント...

お世話になっております。
ここ数日、急にGTSがplease wait...で止まるようになってしまいました。

firefox 10.0+ windows XP
です。

こちらである程度調べたところ、
GM_wait
ファンクションで常に$がundefinedの値になるため、gtsInit();が開始されないようです。

しかし、なぜundefinedに常になるかがわからない状況です。当方の環境だけでしょうか?

ご教示願えればと思います。

yukke さんのコメント...

今さらですが、chromeでも全く同じ状況です。1週間ぐらい前からですね。ということは、スクリプト側じゃなくて、Gmail側の問題でしょうか?

ishikawa.rs さんのコメント...

> s_minami さん
> yukke さん

ご連絡ありがとうございます。

コンタクト情報取得のURLが変更になったようで、データを取得できない状態になっていました。

とりあえず、暫定対応としてテンプレートの読み込みが出来る状態にしました。
※自分の環境では動作しています。

Chrome, Greasemonkey 双方アップデートしましたので、御確認いただけると幸いです。

http://userscripts.org/scripts/show/26426
https://chrome.google.com/webstore/detail/dcafmdckachnenigiihdmmkjaenneoca?hl=ja&gl=JP

ご面倒をおかけし申し訳ございませんが、よろしくお願い致します。

yukke さんのコメント...

いつもながらの速攻レス、ありがとうございます。
スクリプトをアップデートすることで、無事に動くことを確認させて頂きました。早速のご対応、ありがとうございました。

ところで、今後TSG関係の報告も、ここで良いでしょうか?というのも、chromeのウェブストアには、レスポンスを書き込んだりする機能がないためです。
よろしくご検討ください。

ずけ☆ さんのコメント...

いつもお世話になっております。

s_minamiさん、yukkeさんと同じく一週間ほど前から同じ現象でしたが
スクリプトのアップデートで無事動作致しました。
いつもありがとうございます。

Firefox11+windows7
SRWare Iron+windows7

nantaroukun さんのコメント...

s_minamiです。連絡遅くなりすみません。
当方も無事動作を確認しました。
ご対応ありがとうございました。

しかし、アドレスが変わるとは・・・。

アップデートが可能になったらまたご連携ください。
よろしくお願いします。

s_minami さんのコメント...

お世話になっております。
その後テンプレート書き込みが可能になる件、お分かりになりましたでしょうか?

お忙しいところ大変恐縮ですが、現在の進捗など教えていただけると幸いです。

ishikawa.rs さんのコメント...

> s_minami さん

いつもお世話になっております。

現在なかなk作業時間が取れず、お待たせして大変申し訳ございません。

現在までにわかっているのが、Google 側での書き込み時の方法が変更になっており、その方法に合わせるのが困難な状況です。

でのすので、別な方法で処理を見直す必要があるのですが、現在の操作と同じようにはいかないかもしれません。

などなどもう少しお時間をいただくことが予想されます。

大変ご不便をおかけし申し訳ございませんが、もうしばらくお待ちいただけると幸いです。

以上、よろしくお願いいたします。

s_minami さんのコメント...

お世話になっております。

ご連携ありがとうございます。
催促する形になり大変申し訳ございませんでした。

時間がかかる件、了解しました。ご無理はなさらないでください。

よろしくお願いします。

yukke さんのコメント...

いつもお世話様です。yukkeです。
TSGの話題で恐縮ですが、前回も書きましたように他に報告する場もないものですから、こちらに書き込みさせていただきます。
今月の26日まではちゃんと動いていたTSGですが、27日以降件名や差出アドレスには問題ないものの、本文のひな形が反映されなくなってしまいました。
ご対応が可能であれば、よろしくお願いします。

ishikawa.rs さんのコメント...

> yukke さん
いつもご報告ありがとうございます。
Gmail のフォームに変化があったようで、うまく本文を特定できなくなっていました。
修正しましたので、アップデートしていただけると幸いです。

専用ページ作りますね…。すみません。
twitter @ishikawa_rs にご連絡いただいても構いません。ハッシュタグ #gmailtemplateswicher なんかにしましょうか。

以上、よろしくお願い致します。

yukke さんのコメント...

いえいえ、いつもながらの素早いご対応、ありがとうございます。0.0.11で正常に動作することを確認させていただきました。
ありがとうございました。

ずけ さんのコメント...

Ishikawaさん
今回の変更でまた使えなくなってしまいました。。
暫くは旧デザインで頑張ります。
取り急ぎ、報告致します。

ishikawa.rs さんのコメント...

> ずけ さん
ご報告ありがとうございます。
新ページ作りましたので、動きがありましたらこちらにてご報告いたします。
http://ishikawa.r-stone.net/p/gmail-template-switcher.html

erectile dysfunction natural remedies さんのコメント...

Generally I don't read article on blogs, but I wish to say that this write-up very pressured me to take a look at and do so! Your writing taste has been amazed me. Thank you, quite great post.

erectile dysfunction drug さんのコメント...

My spouse and I stumbled over here coming from a different page and thought I might as well check things out. I like what I see so i am just following you. Look forward to looking over your web page for a second time.

erectile dysfunction さんのコメント...

Excellent site. Plenty of helpful information here. I am sending it to a few friends ans also sharing in delicious. And obviously, thank you for your effort!