はてブ登録時によく使うタグを列挙する Greasemonkey スクリプト


ブックマークが増えるにつれてタグも増えてきてしまって、登録時にタグを探すのが面倒になってきました。手で打てばいいんでしょうが、自分はクリックで選択することが多いので、ずらっと並んでしまうと探すのに一苦労です。自分のタグクラウドで大きくなってるやつとか、最近使ったタグとかを表示すれば、ずいぶん楽になるんじゃないかなーと思いました。


で、作りました。

「おすすめタグ」と「タグ」一覧の間に「よく使うタグ」を表示します。一行目は全体でよく使うタグを上位10件、二行目は最近よく使ってるタグをやはり最大10件表示します。
しかしいずれにも「これはひどい」があるのを見ると、さすがに我ながらいかがなものかと思いますね…。


登録のページで使われているはてなスクリプトに思いっきり依存してるし、まあやり方はヤクザっていうか適当すぎるだろうってトコもあるし、エラー処理はマトモにしてないし、動作もやや変なところがあるんですが、一応動くので恐る恐るご紹介(WindowsFirefox / 2.0.0.12 のみ確認)。自分用のつもりとはいえ、せっかく作ったので。ただし、Greasemonkey スクリプトなんてマトモに作るのは初めてなので、こんなんで本当にいいのかどうかは正直自信がありません。すみません。

ホントは、「よく使うタグ」を一つ選ぶとそれに関連するタグをさらに表示していって…みたいにして、階層的なタグ付けが楽になるようなのを作りたかったんですが、ここまでで疲れた&飽きたのでやめにします。誰かがそういうの作ってくれると大変よろこびます。


スクリプト本体ですが、ファイルを置けないのでソースコードを。ちょっと恥ずかしいです><

// ==UserScript==
// @name           HatebUserHotTag
// @namespace      http://b.hatena.ne.jp/good2nd/
// @description    pick up user's recent/popular tags.
// @include        http://b.hatena.ne.jp/add?*
// ==/UserScript==
// 
// Copyright (c), 2008, good2nd <http://b.hatena.ne.jp/good2nd/>
// License: Creative Commons by-sa 2.1 Japan
// http://creativecommons.org/licenses/by-sa/2.1/jp/
//
(function () {
	var w = unsafeWindow;
	var id = w.Hatena.id;
	var hotcnt;
	
	// タグを設定
	function selectTag(tag) {
		tag.style.backgroundColor = "#dddddd";
		tag.style.color = "black";
		tag.onmouseover = function() {};
		tag.onmouseout = function() {};
		tag.onmousedown = function() {
			w.removeTag(this.firstChild.nodeValue);
			w.updateAllTagsLists();
			updateTags();
		};
		if (w.isMSIEorGecko){
			tag.onmouseup = function() {
				var pos = w.commentInput.value.match(/((\[[^\[\]]+?\])*)/)[0].length;
				w.moveCaret(w.commentInput, pos);
				w.commentInput.focus();
			};
		}
	}

	function unselectTag(tag) {
		tag.style.backgroundColor = "white";
		tag.style.color = "#777777";
		tag.onmouseover = function() {
			this.style.backgroundColor = "#eeeeee";
		};
		tag.onmouseout = function() {
			this.style.backgroundColor = "white";
		};
		tag.onmousedown = function() {
			w.addTag(this.firstChild.nodeValue);
			w.updateAllTagsLists();
			updateTags();
		};
		if (w.isMSIEorGecko){
			tag.onmouseup = function() {
				var pos = w.commentInput.value.match(/((\[[^\[\]]+?\])*)/)[0].length;
				w.moveCaret(w.commentInput, pos);
				w.commentInput.focus();
			};
		}
	}
	
	// mypop, myhot のリストを更新
	function updateTags() {
		for (var i = 0; i < 10; i++) {
			var ts = document.getElementById("mypop"+i).wrappedJSObject;
			if (w.isAdded(ts.firstChild.nodeValue)) {
				selectTag(ts);
			} else {
				unselectTag(ts);
			}
		}
		for (var i = 0; i < hotcnt; i++) {
			var ts = document.getElementById("myhot"+i).wrappedJSObject;
			if (w.isAdded(ts.firstChild.nodeValue)) {
				selectTag(ts);
			} else {
				unselectTag(ts);
			}
		}
	}


	// id のブクマのメインページから一覧をとってきて設定
	GM_xmlhttpRequest({
		method: 'GET',
		url: 'http://b.hatena.ne.jp/' + id + '/',
		onload: function(res) {
			var dummy = document.createElement('div');
			dummy.style.display = 'none';
			document.getElementsByTagName('body')[0].appendChild(dummy);
			dummy.innerHTML = res.responseText; // parse
			
			// フォントサイズを元に大きい順に並べる
			var poplist = document.getElementById('taglist').getElementsByTagName('a');
			var pops = new Array();
			for (var i = 0; i < poplist.length; i++) {
				pops.push({size:parseFloat(poplist[i].style.fontSize), tag:poplist[i].textContent});
			}
			pops.sort(function(a,b){return a.size - b.size});
			pops.reverse(); // desc
			
			// 最近のを取得
			var spans = w.document.getElementsByTagName('span');
			var hothash = {};
			for (var i = 0; i < spans.length; i++) {
				if (spans[i].className == 'tag' && spans[i].getElementsByTagName('a') != null) {
					var t = spans[i].getElementsByTagName('a')[0].textContent;
					if (hothash[t]) {
						hothash[t]++;
					} else {
						hothash[t] = 1;
					}
				}
			}
			var hots = new Array();
			var c = 0;
			for (i in hothash) {
				if (c >= 10) {
					break;
				}
				hots.push({cnt: hothash[i], tag: i});
				c++;
			}
			hots.sort(function(a,b){return a.cnt - b.cnt});
			hots.reverse();
			hotcnt = hots.length;
			
			// 上位のタグをリストを表示
			var div = document.createElement('div');
			div.innerHTML = '<div style="font-size:10pt; margin: 8 0 3 0"><b>よく使うタグ</b></div>全体: ';
			for (var i = 0; i < 10; i++) {
				div.appendChild(w.createTagSpan(pops[i].tag, "mypop"+i));
				div.appendChild(document.createTextNode(" "));
			}
			div.innerHTML = div.innerHTML + '<br />最近: ';
			for (var i = 0; i < hots.length; i++) {
				div.appendChild(w.createTagSpan(hots[i].tag, "myhot"+i));
				div.appendChild(document.createTextNode(" "));
			}
			document.getElementById('tags_list').insertBefore(div, document.getElementById('tags_list').firstChild);

			// 初期状態を設定
			updateTags();
		},
		onerror: function() {alert('error.');}
	});

})();