1449280991 はてなブログPC版のサブカテゴリ機能の実装について


http://www.masaemon.jp/
上記ブログ(PC版)右サイドバー下段にカテゴリ一覧を表示しているのですが、カテゴリの量が多く縦長すぎるのが悩みです。そこで以下のようにできるか教えてください。

ーーーーーーーーーー
【現在】
【東京】上野・浅草・日暮里 (34)
【東京】両国・錦糸町・小岩 (2)
【東京】中野~西荻窪 (7)
…続く

【希望】
▶東京
おでん
…続く

↓右三角をクリックすると↓

▼東京
【東京】上野・浅草・日暮里 (34)
【東京】両国・錦糸町・小岩 (2)
【東京】中野~西荻窪 (7)
…続く

ーーーーーーーーーー
【補足説明】
・月別アーカイブのように右三角(▶)をクリックするとサブカテゴリを表示します。
・今後カテゴリが増えるにあたって、いちいち編集とかせず自動でサブカテゴリが反映できたりすると尚最高です。
・カテゴリ一覧をコンパクトに使い勝手のいいものにできればその方法を回答いただいてもOKです。
・回答には、実装結果を確認できるはてなブログURLが必須です。

回答の条件
  • 1人10回まで
  • 登録:
  • 終了:2015/12/12 10:42:04
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。
id:thyself2005

ベストな回答をいただいた場合は早期終了する場合があります。どうぞヨロシクお願いいたします。

ベストアンサー

id:a-kuma3 No.1

回答回数4971ベストアンサー獲得回数2153

ポイント300pt

以下のコードを、「デザイン」の「ヘッダ」の「タイトル下」のところにペタッと。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
(function($) {

$(function() {

    var re = /【(.*)】.*\((.*)\)/;
    var forEach = Array.prototype.forEach;
    var cat_name_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            var ar = cat_name_map[cat];
            if (! ar) {
                ar = [0, 0];
            }
            ar[0] += 1;
            ar[1] += parseInt(match[2]);
            cat_name_map[cat] = ar;
        }
    });
    var cat_node_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            if (cat_name_map[cat][0] > 1) {
                var original_item = a.parentNode;
                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
                    /*
                        LI : original_item
                            |
                            V
                        LI : sub_category_parent
                            SPAN : arrow
                            SPAN : label
                            UL : sub_category_list
                                LI : new_item
                    */
                    var sub_category_parent = document.createElement("LI");
                    original_item.parentNode.insertBefore(sub_category_parent, original_item);
                    var arrow = document.createElement("SPAN");
                    arrow.innerHTML = "&#9654;";
                    arrow.style.cursor = "pointer";
                    arrow.onclick = function() {
                        var sub_category_list = this.nextSibling.nextSibling;
                        if (sub_category_list.style.display == "none") {
                            this.innerHTML = "▼";
                            sub_category_list.style.display = "";
                        } else {
                            this.innerHTML = "&#9654;";
                            sub_category_list.style.display = "none";
                        }
                    };
                    sub_category_parent.appendChild(arrow);
                    var label = document.createElement("SPAN");
                    label.innerHTML = cat + " (" + cat_name_map[cat][1] + ")";
                    sub_category_parent.appendChild(label);
                    sub_category_list = document.createElement("UL");
                    sub_category_list.style.display = "none";
                    sub_category_parent.appendChild(sub_category_list);
                    cat_node_map[ cat ] = sub_category_list;
                }
                sub_category_list.appendChild(new_item);
                original_item.parentNode.removeChild(original_item);
            }
        }
    });

});

})(jQuery);
</script>

サブカテゴリは、【】でくくられたものを拾ってます。
サブカテゴリのカテゴリ数がひとつだけの場合には、折りたたむ意味がないので、そのまま表示するようにしました。
クリックできるのは、ちょっと狭いかなという気もしますが、三角のところだけにしてます。

・回答には、実装結果を確認できるはてなブログURLが必須です。

カテゴリのデータを作るのが大変面なので、id:thyself2005 さんのはてなブログで、Bookmarklet として動作確認しました。
こういう感じになります。
f:id:a-kuma3:20151206004212p:image




追記です。

1.今後【】でくくる複数のカテゴリを追加する場合、特に追加作業等発生せずそれらもサブカテゴリ化できるということでしょうか?

【】でくくられた文字を親カテゴリだと判断するようにしてますので、カテゴリが追加された場合には それに追従します。

2.カテゴリのクリックできる範囲と見た目を月別アーカイブの体裁に揃えることはできますか?
※例えば「? 東京 (XXX)」だと東京 (XXX)も青緑色の下線リンクとなっており、▶ と東京の間の間隔が半角スペース1個分くらい空いている感じです。

月別アーカイブの見た目に寄せることはできますが、月別アーカイブの場合には「年」をクリックしたときは ▶ をクリックしたときと違って、その年の一覧ページに飛びます。
「東京」というカテゴリーは実際にはありませんので、▶ をクリックしたときと同じ動作をするようにしてみました。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
(function($) {

$(function() {

    var re = /【(.*)】.*\((.*)\)/;
    var forEach = Array.prototype.forEach;
    var cat_name_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            var ar = cat_name_map[cat];
            if (! ar) {
                ar = [0, 0];
            }
            ar[0] += 1;
            ar[1] += parseInt(match[2]);
            cat_name_map[cat] = ar;
        }
    });
    var cat_node_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            if (cat_name_map[cat][0] > 1) {
                var original_item = a.parentNode;
                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
                    /*
                        LI : original_item
                            |
                            V
                        LI : sub_category_parent
                            SPAN : arrow
                            SPAN : label
                            UL : sub_category_list
                                LI : new_item
                    */
                    var sub_category_parent = document.createElement("LI");
                    original_item.parentNode.insertBefore(sub_category_parent, original_item);
                    var arrow = document.createElement("SPAN");
                    arrow.innerHTML = "&#9654;";
                    arrow.style.marginRight = "0.5ex";
                    arrow.style.cursor = "pointer";
                    arrow.onclick = function() {
                        var sub_category_list = this.nextSibling.nextSibling;
                        if (sub_category_list.style.display == "none") {
                            this.innerHTML = "▼";
                            sub_category_list.style.display = "";
                        } else {
                            this.innerHTML = "&#9654;";
                            sub_category_list.style.display = "none";
                        }
                    };
                    sub_category_parent.appendChild(arrow);
                    var label = document.createElement("SPAN");
                    label.innerHTML = cat + " (" + cat_name_map[cat][1] + ")";
                    label.style.textDecoration = "underline";
                    label.style.color = "#0E4B84";
                    label.style.cursor = "pointer";
                    label.onclick = function() {
                        this.previousSibling.onclick();
                    };
                    sub_category_parent.appendChild(label);
                    sub_category_list = document.createElement("UL");
                    sub_category_list.style.display = "none";
                    sub_category_parent.appendChild(sub_category_list);
                    cat_node_map[ cat ] = sub_category_list;
                }
                sub_category_list.appendChild(new_item);
                original_item.parentNode.removeChild(original_item);
            }
        }
    });

});

})(jQuery);
</script>
他2件のコメントを見る
id:a-kuma3

サブカテゴリの行間が空いてて、ちょっと間延びしますね。
月別アーカイブに合わせて、追記の回答の 30行目付近に paddingBottom の指定を入れた方がすっきりします。

                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                new_item.style.paddingBottom = 0;       // ★これを追加
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
2015/12/08 17:48:33
id:thyself2005

ご回答&補足ありがとうございます。
ぽけっとしすてむさんの回答含め導入を検討しますが、導入が簡単な点、追記サポート(サブカテゴリの行間調整)も助かりそうなので、今回ベストアンサーといたします。

2015/12/12 10:41:39

その他の回答1件)

id:a-kuma3 No.1

回答回数4971ベストアンサー獲得回数2153ここでベストアンサー

ポイント300pt

以下のコードを、「デザイン」の「ヘッダ」の「タイトル下」のところにペタッと。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
(function($) {

$(function() {

    var re = /【(.*)】.*\((.*)\)/;
    var forEach = Array.prototype.forEach;
    var cat_name_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            var ar = cat_name_map[cat];
            if (! ar) {
                ar = [0, 0];
            }
            ar[0] += 1;
            ar[1] += parseInt(match[2]);
            cat_name_map[cat] = ar;
        }
    });
    var cat_node_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            if (cat_name_map[cat][0] > 1) {
                var original_item = a.parentNode;
                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
                    /*
                        LI : original_item
                            |
                            V
                        LI : sub_category_parent
                            SPAN : arrow
                            SPAN : label
                            UL : sub_category_list
                                LI : new_item
                    */
                    var sub_category_parent = document.createElement("LI");
                    original_item.parentNode.insertBefore(sub_category_parent, original_item);
                    var arrow = document.createElement("SPAN");
                    arrow.innerHTML = "&#9654;";
                    arrow.style.cursor = "pointer";
                    arrow.onclick = function() {
                        var sub_category_list = this.nextSibling.nextSibling;
                        if (sub_category_list.style.display == "none") {
                            this.innerHTML = "▼";
                            sub_category_list.style.display = "";
                        } else {
                            this.innerHTML = "&#9654;";
                            sub_category_list.style.display = "none";
                        }
                    };
                    sub_category_parent.appendChild(arrow);
                    var label = document.createElement("SPAN");
                    label.innerHTML = cat + " (" + cat_name_map[cat][1] + ")";
                    sub_category_parent.appendChild(label);
                    sub_category_list = document.createElement("UL");
                    sub_category_list.style.display = "none";
                    sub_category_parent.appendChild(sub_category_list);
                    cat_node_map[ cat ] = sub_category_list;
                }
                sub_category_list.appendChild(new_item);
                original_item.parentNode.removeChild(original_item);
            }
        }
    });

});

})(jQuery);
</script>

サブカテゴリは、【】でくくられたものを拾ってます。
サブカテゴリのカテゴリ数がひとつだけの場合には、折りたたむ意味がないので、そのまま表示するようにしました。
クリックできるのは、ちょっと狭いかなという気もしますが、三角のところだけにしてます。

・回答には、実装結果を確認できるはてなブログURLが必須です。

カテゴリのデータを作るのが大変面なので、id:thyself2005 さんのはてなブログで、Bookmarklet として動作確認しました。
こういう感じになります。
f:id:a-kuma3:20151206004212p:image




追記です。

1.今後【】でくくる複数のカテゴリを追加する場合、特に追加作業等発生せずそれらもサブカテゴリ化できるということでしょうか?

【】でくくられた文字を親カテゴリだと判断するようにしてますので、カテゴリが追加された場合には それに追従します。

2.カテゴリのクリックできる範囲と見た目を月別アーカイブの体裁に揃えることはできますか?
※例えば「? 東京 (XXX)」だと東京 (XXX)も青緑色の下線リンクとなっており、▶ と東京の間の間隔が半角スペース1個分くらい空いている感じです。

月別アーカイブの見た目に寄せることはできますが、月別アーカイブの場合には「年」をクリックしたときは ▶ をクリックしたときと違って、その年の一覧ページに飛びます。
「東京」というカテゴリーは実際にはありませんので、▶ をクリックしたときと同じ動作をするようにしてみました。

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script>
(function($) {

$(function() {

    var re = /【(.*)】.*\((.*)\)/;
    var forEach = Array.prototype.forEach;
    var cat_name_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            var ar = cat_name_map[cat];
            if (! ar) {
                ar = [0, 0];
            }
            ar[0] += 1;
            ar[1] += parseInt(match[2]);
            cat_name_map[cat] = ar;
        }
    });
    var cat_node_map = {};
    forEach.call(document.querySelectorAll("div.hatena-module-category ul.hatena-urllist li a"), function(a) {
        var match = re.exec(a.textContent);
        if (match) {
            var cat = match[1];
            if (cat_name_map[cat][0] > 1) {
                var original_item = a.parentNode;
                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
                    /*
                        LI : original_item
                            |
                            V
                        LI : sub_category_parent
                            SPAN : arrow
                            SPAN : label
                            UL : sub_category_list
                                LI : new_item
                    */
                    var sub_category_parent = document.createElement("LI");
                    original_item.parentNode.insertBefore(sub_category_parent, original_item);
                    var arrow = document.createElement("SPAN");
                    arrow.innerHTML = "&#9654;";
                    arrow.style.marginRight = "0.5ex";
                    arrow.style.cursor = "pointer";
                    arrow.onclick = function() {
                        var sub_category_list = this.nextSibling.nextSibling;
                        if (sub_category_list.style.display == "none") {
                            this.innerHTML = "▼";
                            sub_category_list.style.display = "";
                        } else {
                            this.innerHTML = "&#9654;";
                            sub_category_list.style.display = "none";
                        }
                    };
                    sub_category_parent.appendChild(arrow);
                    var label = document.createElement("SPAN");
                    label.innerHTML = cat + " (" + cat_name_map[cat][1] + ")";
                    label.style.textDecoration = "underline";
                    label.style.color = "#0E4B84";
                    label.style.cursor = "pointer";
                    label.onclick = function() {
                        this.previousSibling.onclick();
                    };
                    sub_category_parent.appendChild(label);
                    sub_category_list = document.createElement("UL");
                    sub_category_list.style.display = "none";
                    sub_category_parent.appendChild(sub_category_list);
                    cat_node_map[ cat ] = sub_category_list;
                }
                sub_category_list.appendChild(new_item);
                original_item.parentNode.removeChild(original_item);
            }
        }
    });

});

})(jQuery);
</script>
他2件のコメントを見る
id:a-kuma3

サブカテゴリの行間が空いてて、ちょっと間延びしますね。
月別アーカイブに合わせて、追記の回答の 30行目付近に paddingBottom の指定を入れた方がすっきりします。

                var new_item = document.createElement("LI");
                new_item.innerHTML = original_item.innerHTML;
                new_item.style.paddingLeft = "2em";
                new_item.style.paddingBottom = 0;       // ★これを追加
                var sub_category_list = cat_node_map[ cat ];
                if (! sub_category_list) {
2015/12/08 17:48:33
id:thyself2005

ご回答&補足ありがとうございます。
ぽけっとしすてむさんの回答含め導入を検討しますが、導入が簡単な点、追記サポート(サブカテゴリの行間調整)も助かりそうなので、今回ベストアンサーといたします。

2015/12/12 10:41:39
id:psne No.2

回答回数605ベストアンサー獲得回数334

ポイント200pt

f:id:psne:20151205223345j:image
※再掲

以下のコードをHTMLが挿入できる任意の場所に挿入します。
※記事内のみ表示される場所に貼り付けると、それ以外のページでは通常通りの表示になります。

<script>
(function(H,T,N,p,s,n,e){H.Htnpsne=H.Htnpsne||{};Htnpsne[s]=Htnpsne[s]||function(){(Htnpsne[s].q=Htnpsne[s].q||[]).push(arguments)};n=T.createElement(N);e=T.getElementsByTagName(N)[0];n.async=1;n.src=p;e.parentNode.insertBefore(n,e)})(window,document,"script","//niyari.github.io/hatenablog-modules/hb-subcat.min.js","SubCategory");
Htnpsne.SubCategory('setting', { 'pattern': '【(.+)】' });
</script>

サブカテゴリについては質問のとおりになるよう、【(親カテゴリ名)】となるように設定してありますが、

Htnpsne.SubCategory('setting', { 'pattern': '【(.+)】' });

こちらの部分を変更する事で任意の文字列で親カテゴリ、サブカテゴリを生成する事ができます。

/* 例:「親カテゴリ → サブカテゴリ」 という区切りの場合*/
Htnpsne.SubCategory('setting', { 'pattern': '(.+) → ' });

あまり例は無いと思いますが、複数のカテゴリーモジュールが設置されている場合でも動作するようになっています。

f:id:psne:20151206105804p:image

回答には、実装結果を確認できるはてなブログURLが必須です

動作確認用のはてなブログがありますが、プライベート利用の為、スクリーンショットにて回答とさせて頂きます。

他2件のコメントを見る
id:psne

こちらをデザインCSS、または

<style> ... </style>

このようにstyleタグで囲んでHTMLとして貼り付けると利用できます。

4.(略)地域別、料理ジャンルとか複数に分けてそれぞれの配下に指定したカテゴリ・サブカテゴリが表示できれば(後略)

カテゴリ生成時に、関連したクラスを出力していますので、それに合わせて追加のスクリプトとCSSにて対応できるかと思います。

class="hatena-module hatena-module-category"

class="hatena-module-title"

表示されるカテゴリのタイトル
(こちらのタイトル部分を利用)

例えば、上記のタイトル部分を取得して、表示・非表示のクラスを指定する事で実現できるかと思います。
※別機能になりますので、この回答では対応しません。

2015/12/06 19:57:10
id:thyself2005

ご回答ありがとうございます。
a-kuma3さんの回答含め導入を検討します。
取り急ぎ、本質問は終了とします。

2015/12/12 10:39:13

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません