display all bookmarks including no-comment @ Hatena::Bookmark
by
a-kuma3
2018-03-23 [2018/03/23 08:30:53]
コメントがないブックマークも全て表示する
@@ -1,44 +1,248 @@
/*
- * @title question:1521455576
- * @description question:1521455576
+ * @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
*/
-(function() {
- const d_ = document;
+/*
+ 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);
+ }
- Array.prototype.forEach.call(d_.body.querySelectorAll("script,style,noscript"), function(e) {
- e.parentNode.removeChild(e);
- });
-
- const text = d_.body.textContent.replace(/\s+/g, "");
- const count = text.length;
- const title = d_.title;
- const hx_list = d_.querySelectorAll("h1,h2,h3,h4,h5,h6");
-
- const panel = d_.body.appendChild(d_.createElement("textarea"));
- panel.style.position = "fixed";
- panel.style.backgroundColor = "white";
- panel.style.border = "1px solid black";
- panel.style.padding = "1ex";
- panel.style.top = "0";
- panel.style.width = "80ex";
- panel.style.height = "20em";
- function indent(tag) {
- let n = parseInt(tag.replace(/^H/, "")) - 1;
- let pad = "";
- for (let i = 0 ; i < n ; ++i) {
- pad += "\t";
- }
- return pad;
- }
- panel.value = [
- "【文字数】",
- count,
- "\n【title】",
- title,
- "\n【hタグ】",
- Array.prototype.map.call(hx_list, function(h) {return indent(h.tagName) + h.tagName + ":" + h.textContent;}).join("\n"),
- ].join("\n");
-})();
+ 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 です。