普通のブラウザで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 本体)
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>>([\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 + '">>>' + $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(/>>([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);
})();
- ポップアップのデザインを設定(styleタグを作成・追加)
- getElementsByTagName('dt') と getElementsByTagName('dd') で全スレッドのリストを取得
- 不完全なアンカーを修正(「URLのリンク化」と似た処理)
- <a>タグをgetElementsByTagName('a')で全部取得し、取得したタグごとに以下を繰り返す。
- <a>タグ内の文字列が 「>>24」 や 「>>24-27」 の形式であればアンカーと判断し、mouseover と click にアンカーポップアップ表示のイベントハンドラをセットする。
- 各アンカー毎に最初に実行されたときは、新しく<div>タグを作り、アンカーのすぐ下に表示する。また、innverHTMLにはアンカー先のスレッドの内容をセットする。
その際、その内容に対し必要に応じて「URLのリンク化」や「不完全なアンカーの修正」を行う。 - 2回目以降はイベントハンドラが呼び出されるたびにその<div>タグの表示/非表示を切り替える。
不具合などありましたら、コメント欄からご連絡ください。
[amazon]4873113709[/amazon]
ピンバック: 2ch Viewer ブックマークレット Ver.2 | TipsZone