Convert Comick Sidebar to Rows

Converts sidebar sections to rows.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Convert Comick Sidebar to Rows
// @namespace    https://github.com/GooglyBlox
// @version      1.1
// @description  Converts sidebar sections to rows.
// @author       GooglyBlox
// @match        https://comick.dev/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    function injectGlobalCss() {
        const styleId = 'comick-sidebar-fix';
        if (document.getElementById(styleId)) return;

        const css = `
            body > #__next > div > div.float-right {
                display: none !important;
            }
            main[id="main"].md\\:w-8\\/12 {
                width: 100% !important;
                max-width: none !important;
                float: none !important;
            }
            main[id="main"] > div {
                width: 100% !important;
                max-width: none !important;
            }
        `;
        const style = document.createElement('style');
        style.id = styleId;
        style.textContent = css;
        document.head.appendChild(style);
    }

    async function convertSidebarContent() {
        const mainElement = document.querySelector('main[id="main"]');
        const sidebarDiv = document.querySelector('div.float-right.w-4\\/12.xl\\:w-3\\/12');
        if (!mainElement || !sidebarDiv) return;

        const animeSection = findAnimeSectionContainer();
        if (!animeSection) return;

        const seeMoreButtons = Array.from(sidebarDiv.querySelectorAll('div')).filter(div => {
            const textElement = div.querySelector('.flex-grow.font-semibold');
            return textElement && textElement.textContent.trim() === 'See More' && !div.closest('a[href]');
        });

        seeMoreButtons.forEach(button => button.click());
        await new Promise(resolve => setTimeout(resolve, 500));

        let convertedSections = [];
        const sidebarSections = sidebarDiv.querySelectorAll('#ranking');
        for (let sectionIndex = 0; sectionIndex < sidebarSections.length; sectionIndex++) {
            const section = sidebarSections[sectionIndex];
            const header = section.querySelector('.flex.items-center.justify-between');
            const content = section.querySelector('.space-y-2.pb-5.mt-4');
            if (!header || !content) continue;

            const titleElements = header.querySelectorAll('h2');
            if (titleElements.length > 1) {
                const recentlyAddedItems = content.querySelectorAll('a[href^="/comic/"]:not(.hidden)');
                if (recentlyAddedItems.length > 0) {
                    convertedSections.push(createRowSection('Recently Added', recentlyAddedItems, convertedSections.length, false, '/search'));
                }

                const completeSeriesButton = Array.from(titleElements).find(el => el.textContent.trim() === 'Complete Series');
                if (completeSeriesButton) {
                    completeSeriesButton.click();
                    await new Promise(resolve => setTimeout(resolve, 300));
                    const completeSeriesItems = content.querySelectorAll('a[href^="/comic/"]:not(.hidden)');
                    if (completeSeriesItems.length > 0) {
                        convertedSections.push(createRowSection('Complete Series', completeSeriesItems, convertedSections.length, false, '/search?status=2'));
                    }
                    const recentlyAddedButton = Array.from(titleElements).find(el => el.textContent.trim() === 'Recently Added');
                    if (recentlyAddedButton) recentlyAddedButton.click();
                }
            }
        }

        if (convertedSections.length === 0) return;

        let insertAfter = animeSection;
        convertedSections.forEach(section => {
            insertAfter.insertAdjacentElement('afterend', section);
            insertAfter = section;
        });
        sidebarDiv.remove();
    }

    function findAnimeSectionContainer() {
        const animeHeaders = Array.from(document.querySelectorAll('h2')).filter(h2 => h2.textContent.trim() === 'Adapted to Anime');
        if (animeHeaders.length === 0) return null;
        let container = animeHeaders[0];
        while (container && container.parentElement) {
            if (container.classList.contains('mt-3') || container.parentElement === document.querySelector('main[id="main"]')) break;
            container = container.parentElement;
        }
        return container;
    }

    function createRowSection(title, items, sectionIndex, includeRankings = false, viewAllUrl = '/search') {
        const section = document.createElement('div');
        section.className = 'mt-8';
        section.setAttribute('data-section-title', title);
        const header = document.createElement('div');
        header.className = 'flex justify-between items-center flex-nowrap mb-4 md:mb-6';
        const headerLeft = document.createElement('div');
        headerLeft.className = 'flex items-center';
        const titleElement = document.createElement('h2');
        const gradients = [{ light: 'rgb(209, 138, 255)', dark: 'rgb(128, 45, 217)' }, { light: 'rgb(146, 195, 255)', dark: 'rgb(56, 125, 220)' }, { light: 'rgb(74, 227, 134)', dark: 'rgb(14, 157, 64)' }, { light: 'rgb(253, 186, 100)', dark: 'rgb(221, 116, 30)' }];
        const gradient = gradients[sectionIndex % gradients.length];
        titleElement.className = 'md:font-bold line-clamp-1';
        titleElement.textContent = title;
        titleElement.style.background = `linear-gradient(to right, ${gradient.light}, ${gradient.dark})`;
        titleElement.style.webkitBackgroundClip = 'text';
        titleElement.style.backgroundClip = 'text';
        titleElement.style.color = 'transparent';
        headerLeft.appendChild(titleElement);
        const headerRight = document.createElement('div');
        headerRight.className = 'flex items-center';
        const viewAllLink = document.createElement('a');
        viewAllLink.href = viewAllUrl;
        viewAllLink.className = 'hover:cursor-pointer rounded-full text-xs mr-2 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 hover:underline';
        viewAllLink.textContent = 'View all';
        headerRight.appendChild(viewAllLink);
        headerRight.appendChild(createMenuContainer(section, title));
        header.appendChild(headerLeft);
        header.appendChild(headerRight);
        section.appendChild(header);
        section.appendChild(createSectionContent(items, includeRankings));
        return section;
    }

    function createMenuContainer(section, title) {
        const menuContainer = document.createElement('div');
        menuContainer.className = 'hover:cursor-pointer rounded-full';
        const menuButton = document.createElement('div');
        menuButton.className = 'relative inline-block text-left top-0.5';
        const button = document.createElement('button');
        button.type = 'button';
        button.setAttribute('aria-haspopup', 'menu');
        button.setAttribute('aria-expanded', 'false');
        button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" class="w-5 h-5 md:w-6 md:h-6 lg:w-7 lg:h-7 text-gray-500 hover:dark:text-gray-400 hover:text-gray-600 z-10 stroke-2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 12.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5ZM12 18.75a.75.75 0 1 1 0-1.5.75.75 0 0 1 0 1.5Z"></path></svg>`;
        const menu = document.createElement('div');
        menu.className = 'absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white dark:bg-gray-750 dark:text-gray-200 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none shadow-gray-500 dark:shadow-gray-900 hidden';
        menu.innerHTML = `<div role="none"><span class="block px-4 py-2 text-sm cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600" role="menuitem">Minimize</span></div>`;
        let menuOpen = false;
        button.addEventListener('click', (e) => {
            e.stopPropagation();
            menuOpen = !menuOpen;
            menu.classList.toggle('hidden', !menuOpen);
            button.setAttribute('aria-expanded', menuOpen.toString());
        });
        document.addEventListener('click', () => {
            if (menuOpen) {
                menu.classList.add('hidden');
                button.setAttribute('aria-expanded', 'false');
                menuOpen = false;
            }
        });
        menu.querySelector('span[role="menuitem"]').addEventListener('click', () => {
            minimizeSection(section, title);
            menu.classList.add('hidden');
            button.setAttribute('aria-expanded', 'false');
            menuOpen = false;
        });
        menuButton.appendChild(button);
        menuButton.appendChild(menu);
        menuContainer.appendChild(menuButton);
        return menuContainer;
    }

    function createSectionContent(items, includeRankings) {
        const navigationWrapper = document.createElement('div');
        navigationWrapper.className = 'navigation-wrapper relative';
        const scrollContainer = document.createElement('div');
        scrollContainer.className = 'flex overflow-x-scroll hide-scroll-bar h-32 md:h-36 lg:h-48 xl:h-56';
        const innerContainer = document.createElement('div');
        innerContainer.style.width = `${Math.min(items.length * 184, 18400)}px`;
        innerContainer.style.height = '100%';
        innerContainer.style.position = 'relative';
        Array.from(items).slice(0, 100).forEach((item, itemIndex) => {
            innerContainer.appendChild(createHorizontalCard(item, itemIndex, includeRankings));
        });
        scrollContainer.appendChild(innerContainer);
        const leftArrow = document.createElement('div');
        leftArrow.className = 'absolute top-0 left-0 z-10 bg-opacity-80 h-full w-8 lg:w-10 xl:w-14 rounded arrow arrow--left select-none dark:hover:bg-gray-500/30 hover:bg-gray-100/30 bg-opacity-60 cursor-pointer hidden';
        leftArrow.innerHTML = `<div class="top-1/2 -translate-y-1/2 absolute left-1 dark:bg-gray-600 bg-white dark:hover:bg-gray-500 hover:bg-gray-100 rounded-full p-2 md:p-3 flex items-center opacity-80"><svg class=" w-3 h-3 lg:w-4 lg:h-4 fill-current text-gray-500 dark:text-gray-200 bg-opacity-60" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M16.67 0l2.83 2.829-9.339 9.175 9.339 9.167-2.83 2.829-12.17-11.996z"></path></svg></div>`;
        const rightArrow = document.createElement('div');
        rightArrow.className = 'absolute top-0 right-0 z-10 bg-opacity-80 h-full w-8 lg:w-10 xl:w-14 rounded arrow select-none arrow--right dark:hover:bg-gray-500/30 hover:bg-gray-100/30 bg-opacity-60 cursor-pointer';
        rightArrow.innerHTML = `<div class="top-1/2 -translate-y-1/2 absolute right-1 dark:bg-gray-600 bg-white dark:hover:bg-gray-500 hover:bg-gray-100 rounded-full flex items-center opacity-80 p-2 md:p-3"><svg class="w-3 h-3 lg:w-4 lg:h-4 fill-current text-gray-500 dark:text-gray-200 hover:brightness-125 bg-opacity-60" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path d="M5 3l3.057-3 11.943 12-11.943 12-3.057-3 9-9z"></path></svg></div>`;
        leftArrow.addEventListener('click', () => scrollContainer.scrollBy({ left: -920, behavior: 'smooth' }));
        rightArrow.addEventListener('click', () => scrollContainer.scrollBy({ left: 920, behavior: 'smooth' }));
        scrollContainer.addEventListener('scroll', () => {
            const { scrollLeft, scrollWidth, clientWidth } = scrollContainer;
            leftArrow.classList.toggle('hidden', scrollLeft <= 0);
            const atEnd = scrollLeft + clientWidth >= scrollWidth - 10;
            rightArrow.style.opacity = atEnd ? '0.5' : '1';
            rightArrow.style.pointerEvents = atEnd ? 'none' : 'auto';
        });
        navigationWrapper.appendChild(scrollContainer);
        navigationWrapper.appendChild(leftArrow);
        navigationWrapper.appendChild(rightArrow);
        return navigationWrapper;
    }

    function minimizeSection(section, title) {
        const content = section.querySelector('.navigation-wrapper');
        const header = section.querySelector('.flex.justify-between');
        if (!content || !header) return;
        content.style.display = 'none';
        header.style.display = 'none';
        const minimizedView = document.createElement('div');
        minimizedView.className = 'relative mb-4';
        minimizedView.innerHTML = `<div class="absolute inset-0 flex items-center" aria-hidden="true"><div class="w-full border-t border-gray-300 dark:border-gray-600"></div></div><div class="relative flex justify-center"><button type="button" class="inline-flex items-center gap-x-1.5 rounded-full bg-white px-3 py-1.5 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:text-gray-300 dark:ring-gray-600 dark:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="-ml-1 -mr-0.5 h-5 w-5 text-gray-400"><path d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"></path></svg>${title}</button></div>`;
        minimizedView.querySelector('button').addEventListener('click', () => {
            content.style.display = '';
            header.style.display = '';
            minimizedView.remove();
        });
        section.appendChild(minimizedView);
    }

    function createHorizontalCard(originalItem, index, includeRankings = false) {
        const card = document.createElement('div');
        card.className = 'w-24 h-32 md:w-28 md:h-36 lg:w-36 lg:h-48 xl:w-44 xl:h-56 overflow-hidden';
        card.style.cssText = `position: absolute; top: 0px; left: 0px; height: 100%; width: 184px; transform: translateX(${index * 184}px);`;
        const link = document.createElement('a');
        link.href = originalItem.href;
        link.title = originalItem.title || '';
        link.style.overflow = 'visible';
        const cardInner = document.createElement('div');
        cardInner.className = 'relative w-24 h-32 md:w-28 md:h-36 lg:w-36 lg:h-48 xl:w-44 xl:h-56 overflow-hidden';
        const originalImg = originalItem.querySelector('img');
        if (originalImg) {
            const img = originalImg.cloneNode(true);
            img.className = 'select-none rounded object-cover object-top w-24 md:w-28 lg:w-36 xl:w-44 h-32 md:h-36 lg:h-48 xl:h-56 active:brightness-75';
            cardInner.appendChild(img);
        }
        const titleText = originalItem.querySelector('.text-ellipsis')?.textContent?.trim() || link.title;
        if (titleText) {
            const overlay = document.createElement('div');
            overlay.className = 'h-12 bottom-0 absolute overlay font-semibold text-gray-200 w-full text-xs md:text-xs lg:text-sm px-1 md:px-2 pt-5 text-center truncate';
            overlay.textContent = titleText;
            cardInner.appendChild(overlay);
        }
        if (includeRankings) {
            const ranking = document.createElement('div');
            const rankingColors = ['bg-red-500/90', 'bg-orange-500/90', 'bg-yellow-500/90'];
            ranking.className = `absolute top-0 left-0 flex items-center justify-center text-xl md:text-2xl font-semibold z-10 text-white w-6 h-6 md:w-8 md:h-8 rounded-tl rounded-br ${index < 3 ? rankingColors[index] : 'bg-gray-500/90'}`;
            ranking.textContent = (index + 1).toString();
            cardInner.appendChild(ranking);
        }
        link.appendChild(cardInner);
        card.appendChild(link);
        return card;
    }

    let isRunning = false;
    async function runConversion() {
        if (isRunning || document.querySelector('[data-section-title]')) {
            return;
        }
        isRunning = true;

        try {
            const sidebar = document.querySelector('div.float-right.w-4\\/12.xl\\:w-3\\/12');
            if (sidebar) {
                await convertSidebarContent();
            }
        } finally {
            isRunning = false;
        }
    }

    const handlePageChange = () => {
        injectGlobalCss();
        runConversion();
    };

    const observer = new MutationObserver(handlePageChange);

    handlePageChange();

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

})();