はてなパークス未読表示

    @@ -3,7 +3,7 @@ // @name はてなパークス未読表示 // @namespace https://let.hatelabo.jp/Lhankor_Mhy/let/lb7m9qPigaAA // @version 0.1 -// @description 未読の保存済みを右サイドに表示します。 +// @description 未読の保存済みを右サイドに表示します。はてなパークスはSPAなのでブックマークレットとして使ってもいいかも。 // @author Lhankor_Mhy // @match https://p.hatena.ne.jp/* // @icon https://p.hatena.ne.jp/icon.png
  • // ==UserScript==
    // @title        はてなパークス未読表示
    // @name         はてなパークス未読表示
    // @namespace    https://let.hatelabo.jp/Lhankor_Mhy/let/lb7m9qPigaAA
    // @version      0.1
    // @description  未読の保存済みを右サイドに表示します。はてなパークスはSPAなのでブックマークレットとして使ってもいいかも。
    // @author       Lhankor_Mhy
    // @match        https://p.hatena.ne.jp/*
    // @icon         https://p.hatena.ne.jp/icon.png
    // @license      CC0
    // @grant        none
    // ==/UserScript==
    {
        function init() {
            if (document.getElementById('unread-replies')) return;
            const { version } = JSON.parse(document.querySelector('[data-page="app"]').textContent);
            const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
            const roundSurface = document.querySelector('aside[class^=_sidebar] [class^=_roundSurface]').cloneNode();
            roundSurface.id = 'unread-replies';
            roundSurface.insertAdjacentHTML('afterbegin', '<h3>未読の保存済み</h3>');
            document.querySelector('aside[class^=_sidebar]')?.insertAdjacentElement('afterbegin', roundSurface);
            (async () => {
                const { props: { posts } } = await (
                    await fetch('https://p.hatena.ne.jp/my/favorites', {
                        headers: {
                            'X-inertia': 'true',
                            "X-Inertia-Version": version,
                            "X-XSRF-TOKEN": csrfToken
                        }
                    })
                ).json();
                posts.forEach(({ park: { slug }, id, firstUnreadReplyId, title, unreadRepliesCount }) => {
                    if (unreadRepliesCount < 1) return;
    
                    const url = `/p/${slug}/t/${id}?to_reply=${firstUnreadReplyId}`;
                    const link = document.createElement('a');
                    link.href = url;
                    link.textContent = title;
                    link.style = `display: block;`;
                    link.addEventListener('click', (e) => {
                        e.preventDefault();
                        forceVisit(url);
                    });
                    roundSurface.append(link);
                });
            })();
        }
    
        function forceVisit(targetUrl) {
            window.history.pushState({}, "", targetUrl);
            const popStateEvent = new PopStateEvent('popstate', { state: window.history.state });
            window.dispatchEvent(popStateEvent);
        }
    
        // add mutation observer for dynamic content loading
        const targetNode = document.body;
    
        const mutationConfig = { childList: true };
        const mutationCallback = function (mutationsList, observer) {
            for (const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    console.log(mutation);
                    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/09/13 14:31:32 - 22 hours ago
  2. 2026/09/13 14:25:38 - 23 hours ago
  3. 2026/09/13 14:24:47 - 23 hours ago
  4. 2026/09/13 14:24:35 - 23 hours ago