ChatGPT TOC for Questions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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)


})();