素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter にどうぞ。

シンプルなマウスオーバー辞書のGreasemonkeyスクリプト

英単語に1秒以上マウスオーバーしたら、その単語の意味をSPACE ALCからスクレイピングしてきて、生成したa要素のtitle属性に設定する。hrefには、取ってきたURLを設定する。

// ==UserScript==
// @name           mouseoverAlc
// @namespace      http://d.hatena.ne.jp/javascripter/
// @include        http*
// ==/UserScript==
location.href = 'javascript:' + uneval(function() {

  document.addEventListener('mousemove', onMouseMove, false);
  var lTime = null;

  function onMouseMove (e) {
    var node = e.rangeParent;
    var offset = e.rangeOffset;
    lTime = Date.now();
    setTimeout(function(t) {
      if (t != lTime) {
        return;
      }
      try {
        if (node.nodeType != Node.TEXT_NODE) return;
      } catch(e) {
        return;
      }
      if (node.parentNode.className.indexOf('_mouseoverAlc_') == 0) return;
      var rng = getWordRange(node, offset);
      if (rng == null) return;
      var elem = document.createElement('a');
      elem.className = '_mouseoverAlc_need';
      elem.href = 'http://eow.alc.co.jp/' + rng.toString() + '/';
      rng.surroundContents(elem);
      rng.detach();
      var event = document.createEvent('Event');
      event.initEvent('translate', true, false);
      window.dispatchEvent(event);
    },
    1000, lTime);
  }

  function getWordRange (textNode, n) {
    var words = textNode.wholeText.split(/(\b)/i);
    var a = b = -1;
    for (var i = x = 0; i < words.length; i++) if (n < (x += words[i].length)) {
      if (/^\w+$/i.test(words[i])) {
        a = x - words[i].length;
        b = x;
      }
      break;
    }
    if (a == -1 || b == -1) return null;
    var rng = document.createRange();
    rng.setStart(textNode, a);
    rng.setEnd(textNode, b);
    return rng;
  }
}) + '();';
window.addEventListener('translate', translate, false);

function translate () {
  Array.forEach(document.getElementsByClassName('_mouseoverAlc_need'),
  function(elem) {
    setTimeout(GM_xmlhttpRequest, 0, {
      method: 'get',
      url: elem.href,
      onload: function({
        responseText: res
      }) {
        var s = res.match(/▼検索結果([\s\S]+)▲end: 検索結果/);
        if (s == null) {
          elem.className = '_mouseoverAlc_none';
          return;
        }
        var str = s[1].replace(/<[\s\S]+?>|{.*?}/g, '');
        elem.title = str;
        elem.className = '_mouseoverAlc_done';
      }
    });
  });
}

GM_addStyle('[class^=_mouseoverAlc_]:not(:hover) { color: inherit !important; text-decoration: inherit !important; }' +
            '._mouseoverAlc_need { cursor:progress; }' +
            '._mouseoverAlc_done { cursor:help; }' +
            '._mouseoverAlc_none { cursor:auto }');