守破離でいこう! -Let's go with SyuHaRi!-

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

以下、コード全文です。

※最新のコードはこちら

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);

ラベル: , , , ,

automatically translated by Google Translate Hack!

naoki 20:57 | 0 comments |
HaloScan: |

2008/02/10

クールに画像をズームする[FancyZoom]

クールに画像をズームできるFancyZoomというJavascriptによるスクリプトを発見したのでメモです。

画像をかっこよくズームするのに、Lightboxをはじめ、多数のスクリプトがありますが、久しぶりにビビっときたので使ってみました。

使い方は簡単で、

  1. サイトよりZipファイルをダウンロードします
  2. FancyZoom.jsの冒頭の以下の部分を適当に置き換えます

    var zoomImagesURI = '/images-global/zoom/';

  3. Webサイトにファイルをアップロードします。
  4. HTMLの<head>セクションに次の行を加えます。

    <script src="/path/to/FancyZoom.js" type="text/javascript"></script>
    <script src="/path/to/FancyZoomHTML.js" type="text/javascript"></script>

    ※/path/to/部分は適当に置き換えます
  5. <body>タグにonload="setupZoom()"を加えます

あとは、次のように画像がリンク先になっているものを検出し自動でやってくれます。

<a href="image.jpg"><img src="image-thumbnail.jpg" /></a> will zoom up image.jpg when clicked.

ちなみに、ズームしたくないものがある場合は、以下のようにrel="nozoom"を指定します。

<a href="image.jpg" rel="nozoom"><img src="image-thumbnail.jpg" /></a> will zoom up image.jpg when clicked.

また、aタグのtitle属性を指定することで、ズームウィンドウにキャプションを設定することもできます。

<a href="image.jpg" rel="nozoom" title="will zoom up image.jpg when clicked."><img src="image-thumbnail.jpg" /></a> will zoom up image.jpg when clicked.

ラベル:

automatically translated by Google Translate Hack!

naoki 22:31 | 0 comments |
HaloScan: |

2007/06/11

Enter キーでの Submit を抑止する方法

ときに、EnterキーでFormがSubmitされてしまうと、よろしくない場合があります。
そんなときは、以下のようにするとEnterキーによるSubmitが抑止されます。

function enterSubmit(event) {
  event = event || window.event;
  if (event.keyCode == 13) {
    if (event.srcElement) {
      if (event.srcElement.type != 'submit' && 
          event.srcElement.type != 'textarea') {
        return false;
      }
    } else if (event.target) {
      if (event.target.type != 'submit' && 
          event.target.type != 'textarea') {
        return false;
      }
    }
  }
}

<form action="#" onkeydown="return enterSubmit(event)">
  <input type="text" />
  <input type="submit" />
</form>

これでまたひとつ、すっきりしましたね。

ラベル:

automatically translated by Google Translate Hack!

naoki 21:35 | 0 comments |
HaloScan: |

2007/01/18

javascriptでwindowを閉じる

javascriptでwindowを閉じるには

window.close();
なわけですが、window.open()で開かれたウィンドウ以外は警告が表示されます。
そこで、以下のようにすると警告が表示されずに閉じてしまいます。
var w = window.open('', '_top');
w.opener = window;
w.close();

ラベル:

automatically translated by Google Translate Hack!

naoki 14:50 | 0 comments |
HaloScan: |