Collapsible GPT List for ChatGPT.com

Enhances the sidebar functionality by enabling the GPT List to collapse.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Collapsible GPT List for ChatGPT.com
// @namespace    https://chatgpt.com/
// @version      1.1
// @description  Enhances the sidebar functionality by enabling the GPT List to collapse.
// @author       Seth Rose
// @homepage     https://x.com/TheSethRose
// @supportURL   https://bsky.app/profile/sethrose.dev
// @supportURL   https://x.com/TheSethRose
// @match        https://chatgpt.com/*
// @match        https://chat.com/*
// @match        https://chat.openai.com/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    "use strict";

    let isFirstTargetProcessed = false;

    function getElementByXPath(xpath) {
        const result = document.evaluate(
            xpath,
            document,
            null,
            XPathResult.FIRST_ORDERED_NODE_TYPE,
            null
        );
        return result.singleNodeValue;
    }

    function makeCollapsible() {
        if (isFirstTargetProcessed) return;

        const targetXPath = "/html/body/div[1]/div[1]/div/div/div/nav/div[2]/div/div[4]";
        const targetDiv = getElementByXPath(targetXPath);
        if (!targetDiv) return;

        // Avoid duplicates
        if (
            targetDiv.previousElementSibling &&
            targetDiv.previousElementSibling.classList.contains("collapsible-toggle")
        ) {
            return;
        }

        // Create the toggle container
        const toggleButton = document.createElement("div");
        toggleButton.className =
            "collapsible-toggle z-20 select-none text-xs font-semibold text-token-text-primary " +
            "flex items-center justify-between h-[26px] px-2 pl-";
        toggleButton.style.cursor = "pointer";
        toggleButton.style.margin = "0";
        toggleButton.style.paddingBottom = "0"; // extra attempt at removing spacing

        // Inner HTML: start collapsed
        toggleButton.innerHTML = `
            <span id="toggleText">Expand GPT List</span>
            <div class="flex h-[26px] w-[26px] items-center justify-center text-token-text-secondary" style="margin:0;padding:0;">
                <button aria-label="Toggle GPTs" class="ml-3 flex rounded-lg items-center text-token-text-primary">
                    <svg class="w-6 h-6 text-gray-800 dark:text-white"
                         aria-hidden="true"
                         xmlns="http://www.w3.org/2000/svg"
                         width="24" height="24"
                         fill="none"
                         viewBox="0 0 24 24">
                        <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
                              stroke-width="2" d="m8 10 4 4 4-4"/>
                    </svg>
                </button>
            </div>
        `;

        // Style the div to be collapsible
        targetDiv.style.maxHeight = "0";
        targetDiv.style.overflow = "hidden";
        targetDiv.style.transition = "max-height 0.3s ease";
        targetDiv.style.margin = "0";
        targetDiv.style.padding = "0";

        // Insert the toggle button
        targetDiv.parentNode.insertBefore(toggleButton, targetDiv);

        // References for text/icon
        const toggleTextSpan = toggleButton.querySelector("#toggleText");
        const svgPath = toggleButton.querySelector("svg path");

        // Click event toggles everything
        toggleButton.addEventListener("click", () => {
            if (targetDiv.style.maxHeight === "0px") {
                // Expand
                targetDiv.style.maxHeight = targetDiv.scrollHeight + "px";
                toggleTextSpan.textContent = "Collapse GPT List";
                svgPath.setAttribute("d", "m16 14-4-4-4 4"); // Arrow up
            } else {
                // Collapse
                targetDiv.style.maxHeight = "0";
                toggleTextSpan.textContent = "Expand GPT List";
                svgPath.setAttribute("d", "m8 10 4 4 4-4"); // Arrow down
            }
        });

        isFirstTargetProcessed = true;
    }

    // Delay to ensure the target element is rendered
    window.addEventListener("load", () => {
        setTimeout(makeCollapsible, 500);
    });
})();