2ch Viewer ブックマークレット


普通のブラウザで2chを見るとき、アンカー先を表示するためにいちいちアンカーをクリックして新しいタブを開いたり、外部リンクへ移動するとき一つ余計なページを経由するのはちょっとめんどくさいです。そこで、Jane Style の様な専用ブラウザのユーザービリティーを目指してブックマークレットを作りました。

jQuery で書き直すと共に大幅に機能を強化した最新版はこちら

機能

  • アンカーのポップアップ表示
    アンカーをクリックしたりマウスカーソルを載せたりすると、アンカー先のスレをポップアップ表示します。以降ポップアップの表示/非表示を切り替えます。
  • 現在のページ外へのアンカーはiframeを使用して内容を取得
  • 不完全なアンカーの修正
    例)
    >24
    >>89
    >52-57
  • リンク先画像のポップアップ表示
  • URLの直リンク化

以下のブラウザで動くことを確認しています。
: Internet Explorer (8.0)
: Firefox (4.0)
: Opera (11.10)
: Chrome (11.0)
: Safari (5.0)

使い方

2ch閲覧中に下記リンクのブックマークレットを実行すると、上記の機能が有効になります。
お気に入りになどに登録して使ってください。
2ch Viewer

ソースコード(制御部)

http://b.tipszone.jp/2chViewer.js

Last update: 2011-05-03

:javascript:
(function() {
    function require(scriptURL) {
        var element = document.createElement('SCRIPT');
        element.setAttribute('type', 'text/javascript');
        element.setAttribute('charset', 'UTF-8');
        element.setAttribute('src', scriptURL);
        document.body.appendChild(element);
    }

    // The order of a script required must be this order.
    // And I use 'setTimeout' to restrain an error on IE.
    require('http://b.tipszone.jp/url2link.js');
    setTimeout(
        function() { require('http://b.tipszone.jp/2ch.js'); }
        ,200
    );
    setTimeout(
        function() { require('http://b.tipszone.jp/imagePopup.js'); }
        ,400
    );
})();
処理概要

制御部です。
今までに紹介した「URLのリンク化」や「画像ポップアップ表示」のブックマークレット、および、今回の2ch Viewer本体を順番に読み込んでいます。一つずつ順番に読み込まれるよう、setTimeoutで200ミリ秒の間隔を設定しています。
url2link.js と 2ch.js でdocument.body.innerHTMLを置換するときにイベントハンドラが解除されてしまうため、イベントハンドラをセットする imagePopup.js は最後に読み込んでいます。

ソースコード(2ch Viewer 本体)

http://b.tipszone.jp/2ch.js

Last update: 2010-09-25

(function() { // For compatibility function setEventHandler(element, eventName, handler) { if (element.addEventListener) { element.addEventListener(eventName, handler, false); } else { element.attachEvent("on" + eventName, function(event) { handler(event); }); } }

function getTextContent(node) {
    return node.innerText || node.textContent;
}

// To one-byte characters
function z2h_word(str) {
    return str.replace(/([A-Za-z0-9_])/g,
        function ($0) {
            return String.fromCharCode($0.charCodeAt(0) - 65248);
        });
}



// Fix ancher
function markAncher(n) {
    // Mark tagets recursively
    if (0 != n.childNodes.length) {
        for (var j = 0; j < n.childNodes.length; j++) {
            if ('SCRIPT' != n.childNodes[j].tagName && 'A' != n.childNodes[j].tagName) {
                // 'SCRIPT' tag is not target.(Opera treat them.)
                markAncher(n.childNodes[j]);
            }
        }
    } else if (3 == n.nodeType) {
        // Target are anchers in text nodes.
        n.nodeValue = n.nodeValue.replace(/(?:>|>)(?:>|>)?([\d1-9])/g, '\x1a>>$1');
    }
}

function fixAncher(w, noteList) {
    for (var i = 0; i < noteList.length; i++) {
        markAncher(noteList[i]);
    }
    w.document.body.innerHTML = w.document.body.innerHTML.replace(/\x1a&gt;&gt;([\d0-9]{1,4}(?:-[\d0-9]{1,4})?)/g,
        function ($0, $1) {
            $1 = z2h_word($1);  // To one-byte characters
            return '<a href="' + location.href.replace(/[^\/]*$/, "") + $1 + '">&gt;&gt;' + $1 + '</a>';
        });
}



// Ancher popup
var iframe;
function getThread(ancStart, ancEnd) {
    // Exists current page?
    if (1 == startOffset || startOffset < ancStart || (1 == ancStart && 1 == ancEnd)) {
        var rslt = "";
        for (var i = ancStart; i <= ancEnd; i++) {
            var tagNum = (1 == i) ? 0 : i - startOffset;
            rslt += dt[tagNum].innerHTML + '<br>' + dd[tagNum].innerHTML;
        }
        return rslt.replace(/<br><br>\s*?$/i, '');
    } else {
        if (!iframe) {
            iframe = document.createElement('iframe');
            iframe.style.display = 'none';
            setEventHandler(iframe, 'load', function() { updatePopup(iframe); });
            document.body.appendChild(iframe);
        }

        iframe.src = location.href.replace(/[^\/]*$/, "") + ancStart + "-" + ancEnd;

        return "now loding...";
    }
}

var popup_counter = 0;
function updatePopup(iframe){
    var ifDoc = iframe.contentWindow.document;
    var ifDT = ifDoc.getElementsByTagName('dt');
    var ifDD = ifDoc.getElementsByTagName('dd');

    // Fix
    fixAncher(iframe.contentWindow, ifDD);
    if (url2link) { url2link(iframe.contentWindow); }

    // Update contents.
    var content = "";
    for (var i = 1; i < ifDT.length; i++) {
        content += ifDT[i].innerHTML + "<br>" + ifDD[i].innerHTML;
    }
    target = document.getElementById('popup_' + popup_counter);
    target.innerHTML = content.replace(/<br><br>\s*?$/i, '');

    // Set event handler.
    if (imagePopup_sethandler) { imagePopup_sethandler(target); }
    ancherPopup_sethandler(target);
}

function createPopup(obj, ancStart, ancEnd) {
    popup_counter += 1;

    var div = document.createElement('div');
    div.id = 'popup_' + popup_counter;
    div.className = 'popup';
    div.innerHTML = getThread(ancStart, ancEnd);

    // Hide inner div recursively.
    var inDiv = div.getElementsByTagName('div');
    for (var i = 0; i < inDiv.length; i++){
        inDiv[i].style.display = 'none';
    }

    obj.parentNode.insertBefore(div, obj.nextSibling);

    // Set event handler.
    if (imagePopup_sethandler) { imagePopup_sethandler(div); }
    ancherPopup_sethandler(div);
}

// Event handler for ancher popup.
function Popup(event, stop) {
    var target = event.target || event.srcElement;
    if (target.tagName.toLowerCase() != 'a') return;
    if (getTextContent(target).indexOf('>>') != 0) return;

    if (typeof(target.nextSibling.tagName) != 'undefined' && 'div' == target.nextSibling.tagName.toLowerCase()) {
        // Switch visibility of already exists popup.
        if (target.nextSibling.style.display == 'none'){
            target.nextSibling.style.display = 'block';
        } else {
            target.nextSibling.style.display = 'none';
        }
    } else {
        // Create popup? (check range of anchor)
        rslt = getTextContent(target).match(/\d+/g);
        var ancStart = parseInt(rslt[0]);
        var ancEnd = (rslt[1]) ? parseInt(rslt[1]) : ancStart;

        // Out of range?
        if (ancStart < 1 || startOffset + dt.length <= ancEnd) { return; }

        // Ancher size
        if (ancEnd - ancStart < 0 || 5 <= ancEnd - ancStart) { return; }

        createPopup(target, ancStart, ancEnd);
    }

    if (stop) {
        if (event.preventDefault) { event.preventDefault(); }
        if (event.stopPropagation) { event.stopPropagation(); }
        event.returnValue = false;
    }
}



// SETUP

// Define style
var css = document.createElement('style');
css.setAttribute("type", "text/css");
cssStr =
    'div.popup {'+
        'padding: 2px;'+
        'margin: 2px 0px 2px 10px;'+
        'border: 1px inset #ccb;'+
        'background-color: #ffe;'+
        'font-size: 12px;'+
    '}';
if (css.styleSheet) {
    css.styleSheet.cssText = cssStr;
} else {
    css.appendChild(document.createTextNode(cssStr));
}
document.body.appendChild(css);

// Thread list
var dt = document.getElementsByTagName('dt');
var dd = document.getElementsByTagName('dd');

fixAncher(window, dd);

var startOffset = parseInt(/\d+/.exec(getTextContent(dt[1]))) - 1;

// Set event handlers.
function ancherPopup_sethandler(node) {
    var a = node.getElementsByTagName('a');
    for (var i = 0; i < a.length; i++) {
        if (typeof(a[i].innerHTML) == 'string' && a[i].innerHTML.match(/&gt;&gt;([0-9]{1,4})(-[0-9]{1,4})?/)) {
            // Ancher popup (toggle)
            setEventHandler(a[i], 'click', function(e) { Popup(e, true); });
            setEventHandler(a[i], 'mouseover', function(e) { Popup(e, false); });
        }
    }
}

ancherPopup_sethandler(document);

})();

処理概要
  1. ポップアップのデザインを設定(styleタグを作成・追加)
  2. getElementsByTagName('dt') と getElementsByTagName('dd') で全スレッドのリストを取得
  3. 不完全なアンカーを修正(「URLのリンク化」と似た処理)
  4. <a>タグをgetElementsByTagName('a')で全部取得し、取得したタグごとに以下を繰り返す。
  5. <a>タグ内の文字列が 「>>24」 や 「>>24-27」 の形式であればアンカーと判断し、mouseover と click にアンカーポップアップ表示のイベントハンドラをセットする。
イベントハンドラの処理概要
  1. 各アンカー毎に最初に実行されたときは、新しく<div>タグを作り、アンカーのすぐ下に表示する。また、innverHTMLにはアンカー先のスレッドの内容をセットする。
    その際、その内容に対し必要に応じて「URLのリンク化」や「不完全なアンカーの修正」を行う。
  2. 2回目以降はイベントハンドラが呼び出されるたびにその<div>タグの表示/非表示を切り替える。

不具合などありましたら、コメント欄からご連絡ください。

[amazon]4873113709[/amazon]

カテゴリー: 記事 タグ: , , , パーマリンク

1 Response to 2ch Viewer ブックマークレット

  1. ピンバック: 2ch Viewer ブックマークレット Ver.2 | TipsZone

コメントを残す