ChatGPT Nav

Add navigation to ChatGPT

// ==UserScript==
// @name         ChatGPT Nav
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Add navigation to ChatGPT
// @author       You
// @license MIT
// @match        https://chatgpt.com/c/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant        none
// ==/UserScript==

(function () {
  "use strict";
  let buttonAdded = false;
  const NavButtonId = "chatgpt-nav-button";
  const NavPopListId = "chatgpt-nav-poplist";
  const mainId = "main";
  const modelSwitchButtonCssSelector = "header#page-header button[id^='radix-']";
  const offestLeft = "--offset-left";
  const isTargetUrl = () => {
    return window.location.href.startsWith("https://chatgpt.com/c/");
  };

  const addNavButton = () => {
    if (document.getElementById("chatgpt-nav-button")) {
      console.log("Navigation button already exists.");
      return;
    }
    let modelSwitchButtonEle = document.querySelector(modelSwitchButtonCssSelector);
    if (!modelSwitchButtonEle) {
      console.log("Model switch button not found.");
      return;
    }

    let modelBtRect = modelSwitchButtonEle.getBoundingClientRect();
    document.documentElement.style.setProperty(offestLeft, `${modelBtRect.left}px`);

    const navButton = document.createElement("button");
    navButton.id = NavButtonId;
    navButton.textContent = "Nav";
    navButton.style.cssText = `
      position: absolute;
      padding: 5px 10px;
      top: ${modelBtRect.bottom + 10}px;
      left: var(${offestLeft},0px);
      font-weight: bold;
      border: solid 1px gray;
      border-radius: 5px;
      cursor: pointer;
      z-index:10;
    `;

    document.body.appendChild(navButton);

    let navBtRect = navButton.getBoundingClientRect();

    const popList = document.createElement("div");
    popList.id = NavPopListId;
    popList.style.cssText = `
      position: absolute;
      top: ${navBtRect.bottom + 10}px;
      left: var(${offestLeft},0px);
      min-width: 100px;
      max-height: 400px;
      overflow-y: auto;
      background-color: #202123;
      border: 1px solid #444;
      border-radius: 5px;
      z-index: 1000;
      display: none;
      color: white;
      padding: 10px;
      z-index:11;
    `;
    //get all article[data-turn="user"], get each of there content (when too long, substring it to 20 char) and
    // insert to popList innerHtml, when click the item, jump to the article
    const updatePopList = () => {
      popList.innerHTML = ""; // Clear existing list
      const userTurns = document.querySelectorAll("article[data-turn=\"user\"]");
      userTurns.forEach((article) => {
        const content = article.textContent.trim();
        const displayContent = content.length > 100 ? content.substring(0, 100) + "..." : content;
        const listItem = document.createElement("div");
        listItem.textContent = displayContent;
        listItem.style.cssText = `
          padding: 5px 0;
          cursor: pointer;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        `;
        listItem.addEventListener("click", () => {
          article.scrollIntoView({behavior: "instant", block: "start"});
          popList.style.display = "none";
        });
        popList.appendChild(listItem);
      });
    };

    navButton.addEventListener("click", updatePopList);
    navButton.addEventListener("click", () => {
      popList.style.display = popList.style.display === "none" ? "block" : "none";
    });
    document.body.appendChild(popList);
    buttonAdded = true;
  };

  //make MutationObserver to check if HeaderCssSelector is shown , and then add nav button
  new MutationObserver((mutationsList) => {
    if (!isTargetUrl()) {
      if (buttonAdded) {
        let element = document.querySelector(`#${NavButtonId}`);
        if (element) {
          document.querySelector(`#${NavPopListId}`)?.remove();
          element.remove();
          buttonAdded = false;
        }
      }
      return;
    }
    for (const mutation of mutationsList) {
      if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
        const header = document.querySelector(mainId);
        if (header) {
          addNavButton();
          break;
        }
      }
    }
  }).observe(document.body, {childList: true, subtree: true});

})();