ChatGPT TOC for Questions

Adds a floating table of contents (TOC) for your questions on the ChatGPT chat page.

// ==UserScript==
// @name         ChatGPT TOC for Questions
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds a floating table of contents (TOC) for your questions on the ChatGPT chat page.
// @author       BruceYu
// @match        https://chatgpt.com/c/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chatgpt.com
// @grant        none
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @source       https://github.com/13ruceYu/tampermonkey-chatgpt-toc-questions
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    setTimeout(() => {
        // Create and style the floating TOC container
        const tocContainer = document.createElement('div');
        tocContainer.id = 'toc-container';
        tocContainer.style.position = 'fixed';
        tocContainer.style.top = '60px';
        tocContainer.style.right = '40px';
        tocContainer.style.width = '36px';
        tocContainer.style.height = '36px';
        tocContainer.style.overflow = 'hidden';
        tocContainer.style.backgroundColor = '#f8f8f8';
        tocContainer.style.border = '1px solid #ddd';
        tocContainer.style.zIndex = '1000';
        tocContainer.style.opacity = '0.95';
        tocContainer.style.boxShadow = '0 0 10px rgba(0,0,0,0.1)';
        tocContainer.classList.add('toc-container')
        document.body.appendChild(tocContainer);

        const tocIcon = document.createElement('div');
        tocIcon.classList.add('toc-icon')
        tocIcon.style.color = '#666';
        tocIcon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path fill="currentColor" d="M4 6h18v2H4zm0 6h18v2H4zm0 6h18v2H4zm0 6h18v2H4zM26 6h2v2h-2zm0 6h2v2h-2zm0 6h2v2h-2zm0 6h2v2h-2z"/></svg>'

        tocContainer.appendChild(tocIcon)
        $('.toc-icon').find('svg').css({ width: '36px', height: '36px', padding: '6px' })

        const tocTitle = document.createElement('h3');
        tocTitle.innerText = 'Questions';
        tocTitle.style.fontSize = '16px';
        tocTitle.style.marginTop = '0';
        tocTitle.style.marginBottom = '10px';
        tocTitle.style.color = '#333';
        tocContainer.appendChild(tocTitle);

        const tocList = document.createElement('ul');
        tocList.style.listStyleType = 'none';
        tocList.style.paddingLeft = '0';
        tocList.style.margin = '0';
        tocContainer.appendChild(tocList);

        $('#toc-container').hover(
            function () {
                $(this).css({ overflow: 'auto' }).animate({ width: '240px', height: '400px', padding: '8px 12px' }, 100);
                $(this).find('.toc-icon').css({ display: 'none' })
            }, function () {
                $(this).css({ overflow: 'hidden' }).animate({ width: '36px', height: '36px', padding: '0px' }, 100);
                $(this).find('.toc-icon').css({ display: 'block' })
            })

        function updateTOC() {
            tocList.innerHTML = ''; // Clear the list
            let questions = document.querySelectorAll('.whitespace-pre-wrap'); // Modify this to suit the selector of the questions in ChatGPT

            questions && questions.forEach((question, index) => {
                const listItem = document.createElement('li');
                listItem.style.marginBottom = '8px';
                listItem.style.textOverflow = 'ellipsis';
                listItem.style.overflow = 'hidden';
                listItem.style.whiteSpace = 'nowrap';

                const questionLink = document.createElement('a');
                questionLink.href = '#';
                questionLink.innerText = `${index + 1}. ${question.innerText}`;
                questionLink.title = `${question.innerText}`;
                questionLink.style.textDecoration = 'none';
                questionLink.style.color = '#007bff';
                questionLink.style.cursor = 'pointer';

                // Scroll to the question when clicked
                questionLink.addEventListener('click', function (e) {
                    e.preventDefault();
                    question.scrollIntoView({ behavior: 'smooth', block: 'start' });
                });

                listItem.appendChild(questionLink);
                tocList.appendChild(listItem);
            });
        }

        // Update TOC whenever a new message is added
        const observer = new MutationObserver(() => {
            updateTOC();
        });

        const chatContainer = document.querySelector('main'); // Modify if necessary based on actual ChatGPT layout
        if (chatContainer) {
            observer.observe(chatContainer, { childList: true, subtree: true });
        }

        // Initial TOC population
        updateTOC();
    }, 1000)


})();