// ==UserScript==
// @name はてなブックマークメタブ展開
// @title はてなブックマークメタブ展開
// @namespace http://tampermonkey.net/
// @version 0.1
// @description コメントメタブと2階ブクマを展開します。
// @author Lhankor_Mhy
// @match https://b.hatena.ne.jp/entry/*
// @include https://b.hatena.ne.jp/entry/*
// @icon https://b.hatena.ne.jp/favicon.ico
// @grant none
// ==/UserScript==
(async () => {
// ブクマ展開(x-template再利用)
const addBookmark = function (targetElement, bookmark, eid) {
targetElement.insertAdjacentHTML(
'beforeend',
document.getElementById('autoloader-bookmark-item').textContent
.replaceAll('{{user_name}}', bookmark.user)
.replaceAll('{{bookmarked_url}}', bookmark.url)
.replaceAll('{{user_page_path}}', `/${bookmark.user}/`)
.replaceAll('{{ sort }}', `recent`)
.replaceAll('{{profile_image_url}}', `https://cdn.profile-image.st-hatena.com/users/${bookmark.user}/profile.png`)
.replaceAll('{{ #is_public }}is-hidden{{ /is_public }}', `is-hidden`)
.replaceAll('{{{comment_expanded}}}', bookmark.comment)
.replaceAll('{{{tags}}}', `<li>${bookmark.tags.join('<li>')}`)
.replaceAll('{{created}}', bookmark.timestamp)
.replaceAll('{{comment_page_path}}', `/entry/${eid}/comment/${bookmark.user}`)
.replaceAll('{{#should_nofollow}}nofollow{{/should_nofollow}}', 'nofollow')
.replaceAll('{{#enable_button}} is-enabled{{/enable_button}}', 'is-enabled')
)
};
// 2階ブクマAPIコール
const targetURLs = [location.href], bookmarksList = []
for (const targetURL of targetURLs) {
const { bookmarks, entry_url, eid } = (await (await fetch('https://b.hatena.ne.jp/entry/jsonlite/?url=' + encodeURIComponent(targetURL))).json()) ?? { bookmarks: [], entry_url: null, eid: undefined }
if (entry_url) targetURLs.push(entry_url)
bookmarksList.push([bookmarks, eid])
}
// 2階ブクマをHTMLに展開
function addUpstairsBookmark(targetElements) {
bookmarksList.forEach(([bookmarks, eid]) => {
bookmarks.forEach(bookmark => {
const targetUserBookmark = targetElements.find(element => element.dataset?.userName === bookmark.user)
if (!targetUserBookmark) return undefined
const targetElement = [targetUserBookmark, ...targetUserBookmark.querySelectorAll(`[data-user-name="${bookmark.user}"]`)].pop()
addBookmark(targetElement, bookmark, eid)
})
})
}
// メタブをHTMLに展開
function addMetaBookmark(targetElements) {
targetElements.forEach(async element => {
const targetURL = element.querySelector('[data-gtm-label="entry-recent-permalink"]').href
const { bookmarks, entry_url, eid } = (await (await fetch('https://b.hatena.ne.jp/entry/jsonlite/?url=' + encodeURIComponent(targetURL))).json()) ?? { bookmarks: [], entry_url: null }
if (!bookmarks.length) return undefined
// console.log(bookmarks, eid, entry_url)
bookmarks.forEach(
bookmark => {
addBookmark(element, bookmark, eid)
}
)
})
}
// 初回呼び出し
addUpstairsBookmark(Array.from(document.querySelectorAll(`.js-bookmarks-recent [data-user-name]`)))
addMetaBookmark(Array.from(document.querySelectorAll(`.js-bookmarks-recent [data-user-name]`)))
// ブクマ遅延読み込み監視
const targetNode = document.querySelector('.js-bookmarks-recent');
const upstairsBookmarkMutationConfig = { childList: true };
const upstairsBookmarkMutationCallback = function (mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
addUpstairsBookmark(Array.from(mutation.addedNodes).filter(node => node.dataset?.userName))
}
}
};
const upstairsBookmarkMutationObserver = new MutationObserver(upstairsBookmarkMutationCallback);
upstairsBookmarkMutationObserver.observe(targetNode, upstairsBookmarkMutationConfig);
const metaBookmarkMutationConfig = { childList: true, subtree: true };
const metaBookmarkMutationCallback = function (mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
addMetaBookmark(Array.from(mutation.addedNodes).filter(node => node.dataset?.userName))
}
}
};
const metaBookmarkMutationObserver = new MutationObserver(metaBookmarkMutationCallback);
metaBookmarkMutationObserver.observe(targetNode, metaBookmarkMutationConfig);
})()