display all bookmarks including no-comment @ Hatena::Bookmark

    @@ -8,6 +8,8 @@ /* Changelog - 記事につけられたスターを表示してみる + - ブックマークした時刻も表示 + - ツイートのクリック数を表示 */ /* test case @@ -31,6 +33,15 @@ _2d(d.getMonth() + 1), _2d(d.getDate()) ].join(sep || ""); + const datetime_string = d => [ + d.getFullYear(), + _2d(d.getMonth() + 1), + _2d(d.getDate()), + ].join("/") + " " + [ + _2d(d.getHours()), + _2d(d.getMinutes()), + _2d(d.getSeconds()), + ].join(":"); // init @@ -66,6 +77,10 @@ .entry-info-meta { \ display: initial; /* flex */ \ } \ + .twitter-clicks { \ + color: gray; \ + margin-left: 1em; \ + } \ ', })); @@ -109,7 +124,7 @@ user_page_path */ let created = new Date(b.created); - let date = date_string(created, "/"); + let date = datetime_string(created); let date2 = date_string(created); let legacyTagLinks = b.tags.map(tag => { return '<li><a href="/' + b.user.name + '/' + encodeURIComponent(tag) + '/">' + tag + '</a></li>'; @@ -133,11 +148,12 @@ let comment_permalink = x.querySelector(".entry-comment-permalink"); comment_permalink.parentNode.removeChild(comment_permalink); } - bookmark_container.appendChild(x.firstChild); + + return bookmark_container.appendChild(x.firstChild); } const xhr = new XMLHttpRequest(); - const resp_list = []; + let bookmarks = []; xhr.onload = (ev) => { if (ev.target.status < 400) { const resp = ev.target.response; @@ -150,18 +166,17 @@ load するたびにブックマークを追加すると、最後のブロックしか スターが展開されない。 */ - resp_list.push(resp.bookmarks); + bookmarks = bookmarks.concat(resp.bookmarks); if (resp.cursor) { load_bookmark(resp.cursor); } else { - let n = 0; - resp_list.forEach(bookmarks => { - n += bookmarks.length; - bookmarks.forEach(b => { - append_bookmark(b, bookmark_container); - }); + let comment_tags_map = {}; + bookmarks.forEach(b => { + let e = append_bookmark(b, bookmark_container); + comment_tags_map[ b.user.name ] = e.querySelector(".entry-comment-tags"); }); + // hatena star Hatena.Star.SiteConfig = { entryNodes: { "div.entry-info": { @@ -177,6 +192,42 @@ } }; new Hatena.Star.EntryLoader(); + + // Tweet clicks + function insert_clicks(n, e) { + let x = e.parentNode.insertBefore(Object.assign(d_.createElement("span"), { + innerHTML: n + " clicks", + className: "twitter-clicks", + }), e.nextSibling); + x.dataset["clicks"] = n; + return x; + } + const xhr2 = new XMLHttpRequest(); + const url = "http://b.hatena.ne.jp/api/shorturl.clicks"; + let clicks_list = []; + xhr2.onload = (ev) => { + if (ev.target.status < 400) { + let data = ev.target.response; + data.entries[0].clicks.forEach(e => { + if (e.count > 0) { + clicks_list.push(insert_clicks(e.count, comment_tags_map[ e.user ])); + } + }); + insert_clicks(clicks_list.reduce((sum, item) => { + return sum + parseInt(item.dataset["clicks"], 10); + }, 0 + ), d_.getElementById("entry_star_count")); + console.log(clicks_list.length + " clicks !!!"); + } + }; + xhr2.responseType = "json"; + xhr2.open("POST", url, true); + xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr2.send( + "entry=" + d_.documentElement.dataset['entryEid'] + "," + + bookmarks.map(b => b.user.name).join("|") + ); + } } }; @@ -195,3 +246,4 @@ load_bookmark(""); })(); +
  • /*
     * @title display all bookmarks including no-comment @ Hatena::Bookmark
     * @description コメントがないブックマークも全て表示する
     * @include http://b.hatena.ne.jp/entry/*
     * @license MIT http://opensource.org/licenses/MIT
     * @javascript_url
     */
    /*
        Changelog
        - 記事につけられたスターを表示してみる
        - ブックマークした時刻も表示
        - ツイートのクリック数を表示
    */
    /*
        test case
        http://b.hatena.ne.jp/entry/s/www.slideshare.net/enakai/it-51854916
            コメントなしブックマークが多い
        http://b.hatena.ne.jp/entry/s/kazayo.com/gozaisho-sounan/
            コメントありブックマークが多い
        http://b.hatena.ne.jp/entry/kazayo.com/gozaisho-sounan/
            コメントありの最初のブックマークよりも前に、コメントなしのブックマークがある
        http://b.hatena.ne.jp/entry/ift.tt/2wV68BP
            コメントありのブックマークがない
    */
    (() => {
        const d_ = document;
        const entry_url = encodeURIComponent(d_.documentElement.dataset['entryUrl']);
        let bookmark_container;
        const bookmark_template = d_.getElementById("autoloader-bookmark-item").innerHTML.replace(/^\s+/, "");
        const _2d = n => (n < 10 ? "0" : "") + n;
        const date_string = (d, sep) => [
            d.getFullYear(),
            _2d(d.getMonth() + 1),
            _2d(d.getDate())
        ].join(sep || "");
        const datetime_string = d => [
            d.getFullYear(),
            _2d(d.getMonth() + 1),
            _2d(d.getDate()),
        ].join("/") + " " + [
            _2d(d.getHours()),
            _2d(d.getMinutes()),
            _2d(d.getSeconds()),
        ].join(":");
    
    
        // init
        (() => {
            // inactivate auto loader
            let readmore = d_.querySelector(".js-read-more-button");
            if (readmore) {
                /*
                    https://cdn-ak.b.st-hatena.com/js/v4/bookmark.js
                    BookmarkAutoLoaderView#getLabelHeight
    
                    要素を見えなくしちゃうと、getBoundingClientRect は、top = 0 を返すので、
                    メソッドを乗っ取る
                */
                readmore.getBoundingClientRect = () => {
                    return {top: 1000000};
                };
            }
    
            d_.head.appendChild(Object.assign(d_.createElement("style"), {
                innerHTML: '                                                \
                    .hatena-star-comment-button {                           \
                        display: initial !important;                        \
                        margin-right: 8px !important;                       \
                    }                                                       \
                    .js-bookmarks-sort-tab[data-sort="recent"] > img {      \
                        width: 12px;                                        \
                        margin: 0 0.5ex;                                    \
                    }                                                       \
                    .entry-comment-readmore {                               \
                        display: none;                                      \
                    }                                                       \
                    .entry-info-meta {                                      \
                        display: initial; /* flex */                        \
                    }                                                       \
                    .twitter-clicks {                                       \
                        color: gray;                                        \
                        margin-left: 1em;                                   \
                    }                                                       \
                ',
            }));
    
            // add tab
            let comment_tabs = d_.querySelector("ul.entry-comment-tab");
            let tab = comment_tabs.appendChild(Object.assign(d_.createElement("li"), {
                className: "js-bookmarks-sort-tab",
                innerHTML: "全てのブックマーク",
            }));
            tab.dataset["sort"] = "all";
    
            // add panel
            let sort_panel = d_.querySelector("div.js-bookmarks-sort-panels");
            bookmark_container = sort_panel.appendChild(Object.assign(d_.createElement("div"), {
                className: "bookmarks-sort-panel js-bookmarks-sort-panel",
                innerHTML: '<div class="js-bookmarks js-bookmarks-all"></div>',
            }));
            bookmark_container.dataset["sort"] = "all";
            bookmark_container = bookmark_container.firstChild;
    
            tab.click();
    
            // entry url star
            d_.querySelector(".js-entry-info").appendChild(Object.assign(d_.createElement("span"), {
                id: "entry_star_count",
            }));
        })();
    
        function append_bookmark(b, bookmark_container) {
            /*
                #enable_button ~ /enable_button       -- not implement
                #is_public ~ /is_public               -- not implement
                #should_nofollow ~ /should_nofollow   -- not implement
                anchor_path
                comment_expanded
                comment_page_path
                created
                profile_image_url
                tags
                user_name
                user_page_path
            */
            let created = new Date(b.created);
            let date = datetime_string(created);
            let date2 = date_string(created);
            let legacyTagLinks = b.tags.map(tag => {
                return '<li><a href="/' + b.user.name + '/' + encodeURIComponent(tag) + '/">' + tag + '</a></li>';
            }).join("");
            let keyword_map = {
                anchor_path       : '/' + b.user.name + '/' + date2 + '#bookmark-' + b.location_id,
                comment_expanded  : b.comment_expanded,
                comment_page_path : "/entry/" + b.location_id + "/comment/" + b.user.name,
                created           : date,
                profile_image_url : b.user.image.image_url,
                tags              : legacyTagLinks,
                user_name         : b.user.name,
                user_page_path    : "/" + b.user.name,
            };
            let x = d_.createElement("div");
            x.innerHTML = bookmark_template.replace(/[{]{2,3}\s*([^}\s]+)\s*[}]{2,3}/g, (m, p) => {
                return keyword_map[p] || "";
            });
            // remove comment permalink with no comment
            if (b.comment == "") {
                let comment_permalink = x.querySelector(".entry-comment-permalink");
                comment_permalink.parentNode.removeChild(comment_permalink);
            }
    
            return bookmark_container.appendChild(x.firstChild);
        }
    
        const xhr = new XMLHttpRequest();
        let bookmarks = [];
        xhr.onload = (ev) => {
            if (ev.target.status < 400) {
                const resp = ev.target.response;
                /*
                    https://cdn-ak.b.st-hatena.com/js/v4/bookmark.star.js
                    EntryStarView.prototype.observeBookmarkListChange
    
                    はてなスターは MutationObserver が作ってくれるのだけれど、
                    Hatena.Star.EntryLoader は、static な領域を使って処理をするので、
                    load するたびにブックマークを追加すると、最後のブロックしか
                    スターが展開されない。
                */
                bookmarks = bookmarks.concat(resp.bookmarks);
                if (resp.cursor) {
                    load_bookmark(resp.cursor);
                } else {
                    let comment_tags_map = {};
                    bookmarks.forEach(b => {
                        let e = append_bookmark(b, bookmark_container);
                        comment_tags_map[ b.user.name ] = e.querySelector(".entry-comment-tags");
                    });
    
                    // hatena star
                    Hatena.Star.SiteConfig = {
                        entryNodes: {
                            "div.entry-info": {
                                uri: "h1.entry-info-title a",
                                title: "h1.entry-info-title a",
                                container: "#entry_star_count",
                            },
                            "div.js-bookmarks-all div.js-bookmark-item": {
                                uri: "a.js-bookmark-anchor-path",
                                title: "span.js-bookmark-comment",
                                container: "span.js-add-star-container"
                            }
                        }
                    };
                    new Hatena.Star.EntryLoader();
    
                    // Tweet clicks
                    function insert_clicks(n, e) {
                        let x = e.parentNode.insertBefore(Object.assign(d_.createElement("span"), {
                            innerHTML: n + " clicks",
                            className: "twitter-clicks",
                        }), e.nextSibling);
                        x.dataset["clicks"] = n;
                        return x;
                    }
                    const xhr2 = new XMLHttpRequest();
                    const url = "http://b.hatena.ne.jp/api/shorturl.clicks";
                    let clicks_list = [];
                    xhr2.onload = (ev) => {
                        if (ev.target.status < 400) {
                            let data = ev.target.response;
                            data.entries[0].clicks.forEach(e => {
                                if (e.count > 0) {
                                    clicks_list.push(insert_clicks(e.count, comment_tags_map[ e.user ]));
                                }
                            });
                            insert_clicks(clicks_list.reduce((sum, item) => {
                                return sum + parseInt(item.dataset["clicks"], 10);
                            }, 0
                            ), d_.getElementById("entry_star_count"));
                            console.log(clicks_list.length + " clicks !!!");
                        }
                    };
                    xhr2.responseType = "json";
                    xhr2.open("POST", url, true);
                    xhr2.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                    xhr2.send(
                        "entry=" + d_.documentElement.dataset['entryEid'] + "," +
                        bookmarks.map(b => b.user.name).join("|")
                    );
    
                }
            }
        };
        xhr.responseType = "json";
        function load_bookmark(cursor) {
            let url = [
                "http://b.hatena.ne.jp/api/entry/",
                entry_url,
                "/bookmarks?cursor=" + cursor,
                "&limit=500&commented_only=0"
            ].join("");
            xhr.open("GET", url, true);
            xhr.send(null);
        }
    
        load_bookmark("");
    
    })();
    
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2018/03/23 08:30:53 - 2018-03-23
  2. 2018/03/19 23:37:26 - 2018-03-19
  3. 2018/03/19 23:37:06 - 2018-03-19
  4. 2017/09/11 16:49:36 - 2017-09-11
  5. 2017/09/09 01:57:19 - 2017-09-09
  6. 2017/09/08 23:25:01 - 2017-09-08
  7. 2017/09/08 13:35:17 - 2017-09-08