はてなブックマークメタブ展開
by
Lhankor_Mhy
03/28 [2024/03/28 12:21:28]
コメントメタブと2階ブクマを展開します。説明→https://realtor-readyabooks.hatenablog.com/entry/2022/12/28/153626
@@ -2,7 +2,7 @@
// @name はてなブックマークメタブ展開
// @title はてなブックマークメタブ展開
// @namespace https://let.hatelabo.jp/Lhankor_Mhy/let/jtaB0fa4gYAA
-// @version 0.12
+// @version 0.13
// @description コメントメタブと2階ブクマを展開します。説明→https://realtor-readyabooks.hatenablog.com/entry/2022/12/28/153626
// @author Lhankor_Mhy
// @match https://b.hatena.ne.jp/entry/*
@@ -10,26 +10,63 @@
// @license CC0
// @noframes
// @grant none
-// @javascript_url
+// @javascript_url
// ==/UserScript==
(async () => {
const APIURL = 'https://b.hatena.ne.jp/entry/jsonlite/?url='
- document.body.insertAdjacentHTML('beforeend',`
+ document.body.insertAdjacentHTML('beforeend', `
<style>
-.entry-comment-contents > .entry-comment-contents {
+[data-metabu] {
background-color: rgba(0,0,0,0.05);
}
+[data-metabu].focus {
+ outline:dotted 1px;
+}
+#metahatebu-popover{
+ inset: auto 0 0 auto;
+ opacity: 0.5;
+ border: none;
+ background-color: #000;
+ color: #fff;
+ position: fixed;
+}
</style>
+<dialog popover id="metahatebu-popover">メタブあります<button data-metabuprev>前</button><button data-metabunext>次</button></dialog>
`)
+ document.querySelector('.entry-comments').insertAdjacentHTML('beforeend', `
+<div class="entry-comment-contents" id="upStairsSeat"></div>
+ `)
+
+ // メタブありますポップオーバーのイベント設定
+ const metahatebuPopover = document.getElementById('metahatebu-popover');
+ metahatebuPopover.addEventListener('click', ((i = -1) => event => {
+ const metabues = document.querySelectorAll('[data-metabu]');
+ if (event.target.matches('[data-metabuprev]')) i--;
+ if (event.target.matches('[data-metabunext]')) i++;
+ i = (i + metabues.length) % metabues.length;
+ metabues[i].scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
+ metabues.forEach((metabu, j) => metabu.classList.toggle('focus', i == j));
+ })())
+
+ // ポップオーバーフォールバック
+ const metahatebuPopoverOpen = () => {
+ if (HTMLElement.showPopover) {
+ metahatebuPopover.showPopover()
+ } else {
+ metahatebuPopover.show()
+ }
+ }
// ブクマ展開(x-template再利用)
const addBookmark = function (targetElement, bookmark, eid) {
+ document.querySelector(`[href="/entry/${eid}/comment/${bookmark.user}"]`)?.closest('.entry-comment-contents > .entry-comment-contents').remove();
targetElement.insertAdjacentHTML(
'beforeend',
document.getElementById('autoloader-bookmark-item').textContent
+ .replaceAll('<div class="entry-comment-contents ', '<div data-metabu="metabu" class="entry-comment-contents ')
.replaceAll('{{user_name}}', bookmark.user)
.replaceAll('{{bookmarked_url}}', bookmark.url)
.replaceAll('{{user_page_path}}', `/${bookmark.user}/`)
@@ -57,10 +94,15 @@
// 2階ブクマをHTMLに展開
function addUpstairsBookmark(targetElements) {
+ if (upstairsBookmarksList.length) metahatebuPopoverOpen()
upstairsBookmarksList.forEach(([bookmarks, eid]) => {
bookmarks.forEach(bookmark => {
const targetUserBookmark = targetElements.find(element => element.dataset?.userName === bookmark.user)
- if (!targetUserBookmark) return undefined
+ if (!targetUserBookmark) {
+ const targetElement = document.getElementById('upStairsSeat');
+ addBookmark(targetElement, bookmark, eid);
+ return undefined
+ }
const targetElement = [targetUserBookmark, ...targetUserBookmark.querySelectorAll(`.entry-comment-contents[data-user-name="${bookmark.user}"]`)].pop()
addBookmark(targetElement, bookmark, eid)
})
@@ -70,9 +112,11 @@
// メタブをHTMLに展開
function addMetaBookmark(targetElements) {
targetElements.forEach(async element => {
- const targetURL = element.querySelector('[data-gtm-label="entry-recent-permalink"]').href
+ const targetURL = element.querySelector('[data-gtm-label="entry-recent-permalink"]')?.href
+ if (!targetURL) return undefined
const { bookmarks, entry_url, eid } = (await (await fetch(`${APIURL}${encodeURIComponent(targetURL)}`)).json()) ?? { entry_url: null }
if (!entry_url) return undefined
+ metahatebuPopoverOpen()
bookmarks.forEach(
bookmark => {
addBookmark(element, bookmark, eid)
@@ -110,4 +154,5 @@
const metaBookmarkMutationObserver = new MutationObserver(metaBookmarkMutationCallback);
metaBookmarkMutationObserver.observe(targetNode, metaBookmarkMutationConfig);
-})()
+})()
+
// ==UserScript==
// @name はてなブックマークメタブ展開
// @title はてなブックマークメタブ展開
// @namespace https://let.hatelabo.jp/Lhankor_Mhy/let/jtaB0fa4gYAA
// @version 0.13
// @description コメントメタブと2階ブクマを展開します。説明→https://realtor-readyabooks.hatenablog.com/entry/2022/12/28/153626
// @author Lhankor_Mhy
// @match https://b.hatena.ne.jp/entry/*
// @icon https://b.hatena.ne.jp/favicon.ico
// @license CC0
// @noframes
// @grant none
// @javascript_url
// ==/UserScript==
(async () => {
const APIURL = 'https://b.hatena.ne.jp/entry/jsonlite/?url='
document.body.insertAdjacentHTML('beforeend', `
<style>
[data-metabu] {
background-color: rgba(0,0,0,0.05);
}
[data-metabu].focus {
outline:dotted 1px;
}
#metahatebu-popover{
inset: auto 0 0 auto;
opacity: 0.5;
border: none;
background-color: #000;
color: #fff;
position: fixed;
}
</style>
<dialog popover id="metahatebu-popover">メタブあります<button data-metabuprev>前</button><button data-metabunext>次</button></dialog>
`)
document.querySelector('.entry-comments').insertAdjacentHTML('beforeend', `
<div class="entry-comment-contents" id="upStairsSeat"></div>
`)
// メタブありますポップオーバーのイベント設定
const metahatebuPopover = document.getElementById('metahatebu-popover');
metahatebuPopover.addEventListener('click', ((i = -1) => event => {
const metabues = document.querySelectorAll('[data-metabu]');
if (event.target.matches('[data-metabuprev]')) i--;
if (event.target.matches('[data-metabunext]')) i++;
i = (i + metabues.length) % metabues.length;
metabues[i].scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
metabues.forEach((metabu, j) => metabu.classList.toggle('focus', i == j));
})())
// ポップオーバーフォールバック
const metahatebuPopoverOpen = () => {
if (HTMLElement.showPopover) {
metahatebuPopover.showPopover()
} else {
metahatebuPopover.show()
}
}
// ブクマ展開(x-template再利用)
const addBookmark = function (targetElement, bookmark, eid) {
document.querySelector(`[href="/entry/${eid}/comment/${bookmark.user}"]`)?.closest('.entry-comment-contents > .entry-comment-contents').remove();
targetElement.insertAdjacentHTML(
'beforeend',
document.getElementById('autoloader-bookmark-item').textContent
.replaceAll('<div class="entry-comment-contents ', '<div data-metabu="metabu" class="entry-comment-contents ')
.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], upstairsBookmarksList = []
for (const targetURL of targetURLs) {
const { bookmarks, entry_url, eid } = (await (await fetch(`${APIURL}${encodeURIComponent(targetURL)}`)).json()) ?? { entry_url: null }
if (!entry_url) continue
targetURLs.push(entry_url)
upstairsBookmarksList.push([bookmarks, eid])
}
// 2階ブクマをHTMLに展開
function addUpstairsBookmark(targetElements) {
if (upstairsBookmarksList.length) metahatebuPopoverOpen()
upstairsBookmarksList.forEach(([bookmarks, eid]) => {
bookmarks.forEach(bookmark => {
const targetUserBookmark = targetElements.find(element => element.dataset?.userName === bookmark.user)
if (!targetUserBookmark) {
const targetElement = document.getElementById('upStairsSeat');
addBookmark(targetElement, bookmark, eid);
return undefined
}
const targetElement = [targetUserBookmark, ...targetUserBookmark.querySelectorAll(`.entry-comment-contents[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
if (!targetURL) return undefined
const { bookmarks, entry_url, eid } = (await (await fetch(`${APIURL}${encodeURIComponent(targetURL)}`)).json()) ?? { entry_url: null }
if (!entry_url) return undefined
metahatebuPopoverOpen()
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);
})()
- Permalink
- このページへの個別リンクです。
- RAW
- 書かれたコードへの直接のリンクです。
- Packed
- 文字列が圧縮された書かれたコードへのリンクです。
- Userscript
- Greasemonkey 等で利用する場合の .user.js へのリンクです。
- Loader
- @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
- Metadata
- コード中にコメントで @xxx と書かれたメタデータの JSON です。