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

    @@ -12,6 +12,10 @@ // @grant none // ==/UserScript== + + +// The quota has been exceeded.エラーを確認。あとで対応する + (async function () { 'use strict'; const ID = "bookmarker-encyclopedia-tooltip";
  • // ==UserScript==
    // @name         ブックマーカー図鑑ツールチップ
    // @title         ブックマーカー図鑑ツールチップ
    // @namespace    https://let.hatelabo.jp/Lhankor_Mhy/let/laGdx-DigsAA
    // @version      0.2
    // @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==
    
    
    
    // The quota has been exceeded.エラーを確認。あとで対応する
    
    (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;
                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;
                    }
                }
            }
            [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;
                    }
                }
            }
            </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 profileJSON = localStorage.getItem(`bookmarker-encyclopedia_profile_${userId}`);
    
            async function callAPI() {
                const response = await fetch(`https://bookmarker-encyclopedia.netlify.app/api/profiles/${userId}.json`);
                let profile = response.ok ? JSON.parse(await response.text()) : null;
                profile = { profile, expired: new Date().getTime() + 1000 * 60 * 60 * 24 * 7 }; // expire in 7 days
                const updatedProfileJSON = JSON.stringify(profile);
                localStorage.setItem(`bookmarker-encyclopedia_profile_${userId}`, updatedProfileJSON);
                return updatedProfileJSON;
            }
    
            if (!profileJSON || !(new Date().getTime() < JSON.parse(profileJSON)?.expired)) {
                profileJSON = await callAPI();
            }
    
            const { generated: { catchphrase } } = JSON.parse(profileJSON).profile;
            tooltip.textContent = catchphrase;
            const clonedIcon = icon.cloneNode(true);
            clonedIcon.setAttribute('interestfor', null);
            clonedIcon.setAttribute('href', `https://bookmarker-encyclopedia.netlify.app/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 です。