// ==UserScript==
// @name ブックマーカー図鑑ツールチップ
// @title ブックマーカー図鑑ツールチップ
// @namespace https://let.hatelabo.jp/Lhankor_Mhy/let/laGdx-DigsAA
// @version 0.3
// @description はてブで https://bookmarker-encyclopedia.netlify.app のキャッチフレーズをツールチップで表示します。アイコンの上で2秒ぐらいホバーしてください。
// @author Lhankor_Mhy
// @match https://b.hatena.ne.jp/entry/*
// @icon https://b.hatena.ne.jp/favicon.ico
// @license CC0
// @noframes
// @grant none
// ==/UserScript==
(async function () {
'use strict';
const ID = "bookmarkerEncyclopediaTooltip";
// polyfill for interestfor
const supported = Object.hasOwn(
HTMLButtonElement.prototype,
"interestForElement",
);
if (!supported) {
await import('https://cdn.jsdelivr.net/npm/interestfor@1.0.8/src/interestfor.min.js');
}
const { default: localforage } = await import('https://cdn.jsdelivr.net/npm/localforage@1.10.0/+esm');
const store = localforage.createInstance({
name: ID,
storeName: ID,
});
// insert tooltip elements and styles
document.body.insertAdjacentHTML('beforeend', `
<div id="${ID}" popover interestfor="ideologyTooltip">...</div>
<style>
#${ID} {
--ink: #1c1a17;
--paper: #f5f0e6;
--card: #fffdf7;
--gold: #b8893a;
--gold-soft: #e8d9b5;
--accent: #2f6f6b;
--danger: #b14a3b;
--muted: #8a8071;
--line: #d8cdb4;
font-size: 0.8rem;
font-weight: 700;
color: var(--gold);
max-width: 700px;
margin: -7px;
padding: 0px;
background: var(--card);
border: 3px solid var(--ink);
border-width: 0px;
border-radius: 7px;
box-shadow: 0px 0px 0 rgba(28, 26, 23, .18);
width: fit-content;
height: 0px;
overflow: hidden;
position-area: center span-right;
align-self: start;
transition: all .3s .5s allow-discrete, box-shadow .3s 1.1s allow-discrete, translate .3s 1.1s allow-discrete;
interpolate-size: allow-keywords;
translate: 10px 12px;
&:popover-open {
box-shadow: 10px 12px 0 rgba(28, 26, 23, .18);
translate: 0px 0px;
height: auto;
padding: 3.5px;
border-width: 3px;
@starting-style {
height: 0px;
box-shadow: 0px 0px 0 rgba(28, 26, 23, .18);
translate: 10px 12px;
// border-radius: 0px;
padding: 0px;
border-width: 0px;
}
}
}
#ideologyTooltip{
font-size: 0.6rem;
color: var(--ink);
max-width: 700px;
margin: 0;
background: var(--card);
border: 1px solid var(--line);
border-radius: 7px;
width: fit-content;
position-area: bottom center;
align-self: start;
}
[interestfor="${ID}"] {
interest-delay: 1.5s .1s;
--interest-delay: 1.5s .1s;
img{
transition: rotate .3s .5s,scale .3s .5s;
&:hover{
rotate: -10deg;
scale: 1.2;
}
}
}
[interestfor="ideologyTooltip"] {
interest-delay: 3s .1s;
--interest-delay: 3s .1s;
}
</style>
`);
const tooltip = document.getElementById(ID);
function init() {
// add interest invokers
document.querySelectorAll('.entry-user-icon').forEach(icon => {
icon.setAttribute('interestfor', tooltip.id);
})
}
// add interest event listener
tooltip.addEventListener("interest", async (e) => {
const icon = e.source;
const userId = icon.getAttribute('href').split('/')[1].toLowerCase();
let profile = await store.getItem(`${userId}`);
async function callAPI() {
const response = await fetch(`https://bookmarker-encyclopedia.netlify.app/api/profiles/${userId}.json`);
const profile = {
profile: response.ok ? JSON.parse(await response.text()) : null,
expired: new Date().getTime() + 1000 * 60 * 60 * 24 * 7
}; // expire in 7 days
await store.setItem(`${userId}`, profile);
return profile;
}
if (!profile || !(new Date().getTime() < profile?.expired)) {
profile = await callAPI();
}
if (!profile?.profile) {
tooltip.textContent = "プロフィールが見つかりませんでした。";
return;
}
const { profile: { generated: { catchphrase, ideologyProse } } } = profile;
tooltip.textContent = catchphrase;
const clonedIcon = icon.cloneNode(true);
clonedIcon.setAttribute('interestfor', null);
clonedIcon.setAttribute('href', `https://bookmarker-encyclopedia.netlify.app/profiles/${userId}`);
const ideologyTooltip = document.createElement('div');
ideologyTooltip.setAttribute('id', `ideologyTooltip`);
ideologyTooltip.textContent = ideologyProse;
ideologyTooltip.setAttribute('popover', '');
tooltip.insertAdjacentElement('beforeend', ideologyTooltip);
tooltip.insertAdjacentElement('afterbegin', clonedIcon);
});
tooltip.addEventListener("loseinterest", (e) => {
tooltip.textContent = "...";
});
// add mutation observer for lazy-loaded bookmarks
const targetNode = document.querySelector('.js-bookmarks-recent');
const mutationConfig = { childList: true };
const mutationCallback = function (mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
init();
}
}
};
const mutationObserver = new MutationObserver(mutationCallback);
mutationObserver.observe(targetNode, mutationConfig);
init();
})();