Sleazy Fork is available in English.

Improved Chub.ai (Card Title Links, Styling, and Pagination)

Makes Chub.ai card titles into links (copying existing link), styles them, and adds pagination.

当前为 2025-06-10 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Improved Chub.ai (Card Title Links, Styling, and Pagination)
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Makes Chub.ai card titles into links (copying existing link), styles them, and adds pagination.
// @author       Marcal91
// @match        https://chub.ai/*
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    function createTitleLink(cardElement) {
        // 1. Find the title element (innermost span holding the text).
        const titleSpan = cardElement.querySelector('.ant-card-head-title .ant-row span span');
        if (!titleSpan) {
            // If the specific inner span is not found, it might have already been replaced by our link.
            // Check if the link already exists within the title container.
            const titleContainer = cardElement.querySelector('.ant-card-head-title .ant-row > span');
            if (titleContainer && titleContainer.querySelector('a.chub-card-title-link')) {
                return; // Already processed
            }
            if(!titleSpan) return; // If still not found, bail.
        }

        // 2. Get the existing card link (the parent <a> of the whole card).
        const cardLink = cardElement.closest('a');
        if (!cardLink) {
            console.error("Chub.ai: Could not find parent card link for title:", cardElement);
            return;
        }

        // 3. Get the URL from the existing card link.
        const cardURL = cardLink.href;

        // 4. Create the new <a> element for the title.
        const linkElement = document.createElement('a');
        linkElement.href = cardURL;
        linkElement.textContent = titleSpan.textContent.trim();
        linkElement.target = '_blank';
        linkElement.rel = 'noopener noreferrer';
        linkElement.classList.add('chub-card-title-link');

        // 5. Replace the title span with the link.
        if (titleSpan.parentNode) {
            titleSpan.parentNode.replaceChild(linkElement, titleSpan);
        }
    }

    function addPagination() {
        const buttonContainer = document.querySelector('.flex.justify-between.mt-4');
        if (!buttonContainer) return;

        const currentURL = new URL(window.location.href);
        let currentPage = parseInt(currentURL.searchParams.get('page') || '1', 10);

        const paginationContainer = document.createElement('div');
        paginationContainer.classList.add('pagination-container');

        function createPageButton(pageNumber, text) {
            const pageButton = document.createElement('button');
            pageButton.type = 'button';
            pageButton.textContent = text || pageNumber.toString();
            pageButton.classList.add('ant-btn', 'css-s6hibu', 'ant-btn-default', 'pagination-link');

            if (!text && pageNumber === currentPage) { // Only style numeric current page
                pageButton.classList.add('current-page');
            }

            pageButton.addEventListener('click', (event) => {
                event.preventDefault();
                const url = new URL(window.location.href);
                url.searchParams.set('page', pageNumber.toString());
                window.location.href = url.toString();
            });
            return pageButton;
        }

        const firstPageButton = createPageButton(1, 'First');
        paginationContainer.appendChild(firstPageButton);

        let startPage = Math.max(1, currentPage - 2);
        let endPage = startPage + 4;
        const assumedTotalPages = 1000;

        if (endPage > assumedTotalPages) {
            endPage = assumedTotalPages;
            startPage = Math.max(1, endPage - 4);
        }
        startPage = Math.max(1, startPage);


        for (let i = startPage; i <= endPage; i++) {
            const pageButton = createPageButton(i);
            paginationContainer.appendChild(pageButton);
        }

        const randomButton = document.createElement('button');
        randomButton.type = 'button';
        randomButton.textContent = 'Random';
        randomButton.classList.add('ant-btn', 'css-s6hibu', 'ant-btn-default', 'pagination-link');
        randomButton.addEventListener('click', (event) => {
            event.preventDefault();
            const randomPage = Math.floor(Math.random() * assumedTotalPages) + 1;
            const url = new URL(window.location.href);
            url.searchParams.set('page', randomPage.toString());
            window.location.href = url.toString();
        });
        paginationContainer.appendChild(randomButton);

        const existingPagination = buttonContainer.querySelector('.pagination-container');
        if (existingPagination) {
            buttonContainer.replaceChild(paginationContainer, existingPagination);
        } else {
            buttonContainer.appendChild(paginationContainer);
        }
    }

    const observer = new MutationObserver((mutations) => {
        let cardsChanged = false;
        let buttonsChanged = false;

        mutations.forEach((mutation) => {
            mutation.addedNodes.forEach((node) => {
                if (node.nodeType === Node.ELEMENT_NODE) {
                    if (node.matches('.ant-card.char-card-class') || node.querySelector('.ant-card.char-card-class')) {
                        cardsChanged = true;
                    }
                    if (node.matches('.flex.justify-between.mt-4') || node.querySelector('.flex.justify-between.mt-4')) {
                        buttonsChanged = true;
                    }
                }
            });
        });

        if (cardsChanged) {
            document.querySelectorAll('.ant-card.char-card-class').forEach(createTitleLink);
        }
        if (buttonsChanged) {
            addPagination();
        }
    });

    observer.observe(document.body, { childList: true, subtree: true });

    let lastUrl = window.location.href;
    const urlObserver = new MutationObserver(() => {
      if (window.location.href !== lastUrl) {
        lastUrl = window.location.href;
        setTimeout(addPagination, 100);
      }
    });
    urlObserver.observe(document, {childList: true, subtree: true});

    document.querySelectorAll('.ant-card.char-card-class').forEach(createTitleLink);
    addPagination();

    const styleElement = document.createElement('style');
    styleElement.textContent = `
        .chub-card-title-link {
            color: white; /* Unvisited */
        }
        .chub-card-title-link:visited {
            color: yellow; /* Visited */
        }
        .pagination-container {
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }
        .pagination-container .pagination-link { /* Applied to all buttons in pagination */
            background-color: #141414 !important;
            border: 1px solid #424242 !important;
            color: rgba(242,228,214,0.85) !important;
            min-width: 32px;
            height: 32px;
            padding: 0px 15px !important;
            line-height: 1.5 !important;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            border-radius: 6px !important;
        }
        .pagination-container .pagination-link:not(.current-page):hover {
            background-color: #1f1f1f !important;
            border-color: #5852a5 !important;
            color: #5852a5 !important;
        }
        .pagination-container .pagination-link.current-page {
            background-color: #2e2b74 !important;
            border-color: #5852a5 !important;
            color: white !important;
            font-weight: bold;
        }

        /* --- Styles for bigger tag scrollbar --- */
        div.custom-scroll:hover::-webkit-scrollbar {
            height: 8px !important; /* Increase height from site's 2px to 8px */
            /* The site CSS already positions it with 'position: absolute; bottom: 0;' which is fine. */
        }

        div.custom-scroll:hover::-webkit-scrollbar-thumb {
            background-color: #888 !important; /* A more visible scrollbar thumb color */
            border-radius: 4px !important;   /* Rounded corners for the thumb */
        }

        /* Optional: Add a subtle track background for the scrollbar area when hovering the tag container */
        div.custom-scroll:hover::-webkit-scrollbar-track {
            background-color: rgba(50, 50, 50, 0.2) !important; /* Light grey track background */
            border-radius: 4px !important;
        }
    `;
    document.head.appendChild(styleElement);

})();