ブックマーカー図鑑ツールチップ

    @@ -3,7 +3,7 @@ // @title ブックマーカー図鑑ツールチップ // @namespace https://let.hatelabo.jp/Lhankor_Mhy/let/laGdx-DigsAA // @version 0.1 -// @description はてブで https://bookmarker-encyclopedia.netlify.app のキャッチフレーズをツールチップで表示します。ユーザーアイコン上で2秒ほどホバーしてください。 +// @description はてブで https://bookmarker-encyclopedia.netlify.app のキャッチフレーズをツールチップで表示します。 // @author Lhankor_Mhy // @match https://b.hatena.ne.jp/entry/* // @icon https://b.hatena.ne.jp/favicon.ico @@ -62,6 +62,7 @@ height: 0px; overflow: hidden; + &:popover-open { height: auto; opacity: 1; @@ -71,7 +72,6 @@ } } } - [interestfor="${ID}"] { interest-delay: 2s .1s; --interest-delay: 2s .1s; @@ -104,6 +104,7 @@ tooltip.textContent = catchphrase; const clonedIcon = icon.cloneNode(true); clonedIcon.setAttribute('interestfor', null); + clonedIcon.setAttribute('href', `https://bookmarker-encyclopedia.netlify.app/api/profiles/${userId}`); tooltip.insertBefore(clonedIcon, tooltip.firstChild); }); tooltip.addEventListener("loseinterest", (e) => {
  • // ==UserScript==
    // @name         ブックマーカー図鑑ツールチップ
    // @title         ブックマーカー図鑑ツールチップ
    // @namespace    https://let.hatelabo.jp/Lhankor_Mhy/let/laGdx-DigsAA
    // @version      0.1
    // @description  はてブで https://bookmarker-encyclopedia.netlify.app のキャッチフレーズをツールチップで表示します。
    // @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 = "bookmarker-encyclopedia-tooltip";
    
        // 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');
        }
    
        // insert tooltip elements and styles
        // document.querySelectorAll('a:has(img[src*="profile.png"])')
        document.body.insertAdjacentHTML('beforeend', `
            <div id="${ID}" popover>...</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;
                background: var(--card);
                border: 3px solid var(--ink);
                border-radius: 7px;
                box-shadow: 10px 12px 0 rgba(28, 26, 23, .18);
                opacity: .5;
    
                position-area: center span-right;
                align-self: start;
    
                transition: all .7s .2s allow-discrete;
                interpolate-size: allow-keywords;
                width: fit-content;
                height: 0px;
                overflow: hidden;
    
                
                &:popover-open {
                    height: auto;
                    opacity: 1;
                    @starting-style {
                        height: 0px;
                        opacity: .5;
                    }
                }
            }
            [interestfor="${ID}"] {
                interest-delay: 2s .1s;
                --interest-delay: 2s .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 = localStorage.getItem(`bookmarker-encyclopedia_profile_${userId}`);
            if (!profile) {
                const response = await fetch(`https://bookmarker-encyclopedia.netlify.app/api/profiles/${userId}.json`);
                if (!response.ok) return;
                profile = await response.text();
                localStorage.setItem(`bookmarker-encyclopedia_profile_${userId}`, profile);
            }
    
            const { generated: { catchphrase } } = JSON.parse(profile);
            tooltip.textContent = catchphrase;
            const clonedIcon = icon.cloneNode(true);
            clonedIcon.setAttribute('interestfor', null);
            clonedIcon.setAttribute('href', `https://bookmarker-encyclopedia.netlify.app/api/profiles/${userId}`);
            tooltip.insertBefore(clonedIcon, tooltip.firstChild);
        });
        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();
    })();
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2026/07/30 15:53:55 - 14 hours ago
  2. 2026/07/29 17:18:23 - 1 days ago
  3. 2026/07/29 16:47:18 - 1 days ago
  4. 2026/07/29 16:44:33 - 1 days ago
  5. 2026/07/29 16:31:45 - 1 days ago
  6. 2026/07/29 16:25:49 - 1 days ago
  7. 2026/07/29 15:57:15 - 1 days ago
  8. 2026/07/29 15:55:20 - 1 days ago
  9. 2026/07/29 15:54:28 - 1 days ago
  10. 2026/07/29 15:51:01 - 1 days ago
  11. 2026/07/29 15:50:34 - 1 days ago