Skip to content

Instantly share code, notes, and snippets.

@MinhOmega
Created April 7, 2025 16:56
Show Gist options
  • Select an option

  • Save MinhOmega/3588d2bbfea4f822e8ed1ee20b94caec to your computer and use it in GitHub Desktop.

Select an option

Save MinhOmega/3588d2bbfea4f822e8ed1ee20b94caec to your computer and use it in GitHub Desktop.
[Minified] - ChatGPT-Grok-TOC.js
javascript:!function(){"use strict";function n(n,t,o=0,e=0){const l=document.createElement("li"),s=(n||"").trim()||(o?"Point":"Section");if(e){l.classList.add("toc-collapse-trigger"),l.innerHTML=`<div style="width:100%;">\n<span class="toc-toggle" role="button" aria-label="Toggle section">${i}</span>\n<span class="toc-clickable-content" title="${s}">${s}</span>\n</div>`;const n=l.querySelector(".toc-toggle");n.addEventListener("click",(function(t){l.classList.contains("toc-collapsed")?(l.classList.remove("toc-collapsed"),n.innerHTML=i):(l.classList.add("toc-collapsed"),n.innerHTML=c),t.stopPropagation()})),l.querySelector(".toc-clickable-content").addEventListener("click",(function(n){t.classList.remove("toc-highlight"),t.classList.add("toc-highlight"),t.scrollIntoView({behavior:"smooth",block:"start"}),n.stopPropagation()}))}else l.innerHTML=`<div style="width:100%;">\n<span class="toc-clickable-content" title="${s}">${s}</span>\n</div>`,l.querySelector(".toc-clickable-content").addEventListener("click",(function(n){t.classList.remove("toc-highlight"),t.classList.add("toc-highlight"),t.scrollIntoView({behavior:"smooth",block:"start"}),n.stopPropagation()}));return l}function t(n){document.querySelectorAll(".toc-collapse-trigger").forEach((t=>{const o=t.querySelector(".toc-toggle");n?(t.classList.remove("toc-collapsed"),o.innerHTML=i):(t.classList.add("toc-collapsed"),o.innerHTML=c)}))}function o(){const o=document.getElementById("toc-list");if(!o)return;o.innerHTML="";const e=(r||document).querySelectorAll(".relative.group.flex.flex-col.justify-center.w-full.max-w-3xl.md\\:px-4.pb-2.gap-2.items-start"),l=(r||document).querySelectorAll("article[data-testid^='conversation-turn-']");if(0!==e.length||0!==l.length){if(e.length>0){let t=0,l=0;for(let s=0;e.length>s;s++){const a=e[s],r=document.createElement("li");r.classList.add("toc-collapse-trigger");const d=a.querySelectorAll("h3").length>0;let p="";d?(t++,p=`Response ${t}(Grok)`):(l++,p=`Turn ${l}(You)`),r.innerHTML=`\n<div style="width:100%;">\n<span class="toc-toggle" role="button" aria-label="Toggle turn">${i}</span>\n<span class="toc-clickable-content" title="${p}">${p}</span>\n</div>\n `;const u=r.querySelector(".toc-toggle");if(u.addEventListener("click",(function(n){r.classList.contains("toc-collapsed")?(r.classList.remove("toc-collapsed"),u.innerHTML=i):(r.classList.add("toc-collapsed"),u.innerHTML=c),n.stopPropagation()})),r.querySelector(".toc-clickable-content").addEventListener("click",(function(n){a.scrollIntoView({behavior:"smooth",block:"start"}),n.stopPropagation()})),d){const t=document.createElement("ul"),o=a.querySelectorAll("h3");for(let e=0;o.length>e;e++){const l=o[e];let c=0,i=l;for(;i;){if("PRE"===i.tagName||"CODE"===i.tagName){c=1;break}i=i.parentElement}if(c)continue;const s=n(l.textContent,l,0,1),a=document.createElement("ul");let r=l.nextElementSibling,d=0,p=null,u=null;for(;r&&"H3"!==r.tagName;){if("H4"===r.tagName)p=r,u=n(r.textContent,r,1,1),a.appendChild(u),d=1;else if("OL"===r.tagName&&u){const t=document.createElement("ul"),o=r.querySelectorAll("li");for(let e=0;o.length>e;e++){const l=o[e],c=l.querySelector("strong");if(c){const o=n(c.textContent,l,1);t.appendChild(o)}}t.children.length>0&&u.appendChild(t)}else if("P"===r.tagName&&!p){const t=r.querySelectorAll("strong");if(t.length>0){const o=n(t[0].textContent,r,1);a.appendChild(o),d=1}}r=r.nextElementSibling}d&&s.appendChild(a),t.appendChild(s)}t.children.length>0&&r.appendChild(t)}o.appendChild(r)}}else if(l.length>0)for(let t=0;l.length>t;t++){const e=l[t],s=document.createElement("li");s.classList.add("toc-collapse-trigger");const a=e.querySelector("h6.sr-only");let r=0,d="";a&&a.textContent.indexOf("ChatGPT said:")>=0?(r=1,d=`Turn ${t+1}(AI)`):d=`Turn ${t+1}(You)`,s.innerHTML=`\n<div style="width:100%;">\n<span class="toc-toggle" role="button" aria-label="Toggle turn">${i}</span>\n<span class="toc-clickable-content" title="${d}">${d}</span>\n</div>\n `;const p=s.querySelector(".toc-toggle");if(p.addEventListener("click",(function(n){s.classList.contains("toc-collapsed")?(s.classList.remove("toc-collapsed"),p.innerHTML=i):(s.classList.add("toc-collapsed"),p.innerHTML=c),n.stopPropagation()})),s.querySelector(".toc-clickable-content").addEventListener("click",(function(n){e.scrollIntoView({behavior:"smooth",block:"start"}),n.stopPropagation()})),r){const t=document.createElement("ul"),o=e.querySelectorAll("h3:not(.sr-only)");for(let e=0;o.length>e;e++){const l=o[e];let c=0,i=l;for(;i;){if("PRE"===i.tagName||"CODE"===i.tagName){c=1;break}i=i.parentElement}if(c)continue;const s=n(l.textContent,l,0,1);t.appendChild(s)}t.children.length>0&&s.appendChild(t)}o.appendChild(s)}document.getElementById("toc-expand-all").addEventListener("click",(function(n){t(1),n.stopPropagation()})),document.getElementById("toc-collapse-all").addEventListener("click",(function(n){t(0),n.stopPropagation()}))}else o.innerHTML='<li style="opacity:0.7;font-style:italic;">Empty chat</li>'}function e(){const n=document.querySelector("main")||null,t=document.querySelector("main#main")||document.querySelector(".chat-container")||null,e=n||t;e!==r&&(r=e,d&&(d.disconnect(),d=null),r&&(d=new MutationObserver((function(){p||(p=1,u=setTimeout((function(){o(),p=0}),300))})),d.observe(r,{childList:1,subtree:1}),o()))}if(document.getElementById("toc-panel")||document.getElementById("toc-handle"))return;const l=document.createElement("style");l.textContent='\n/*Panel*/\n #toc-panel{\n position:fixed;\n top:0;\n right:0;\n width:280px;\n height:100%;\n background:#fafafa;\n box-shadow:-4px 0 8px rgba(0,0,0,0.1);\n font-family:sans-serif;\n font-size:0.8rem;\n border-left:1px solid #ddd;\n display:flex;\n flex-direction:column;\n z-index:9998;\n transform:translateX(0);\n transition:transform 0.3s ease;\n}\n #toc-panel.collapsed{\n transform:translateX(280px);\n}\n\n/*Panel Header*/\n #toc-header{\n padding:6px 10px;\n background:#ddd;\n border-bottom:1px solid #ccc;\n font-weight:bold;\n flex-shrink:0;\n display:flex;\n justify-content:space-between;\n align-items:center;\n}\n \n #toc-header-text{\n flex:1;\n}\n \n #toc-expand-all,#toc-collapse-all{\n cursor:pointer;\n padding:0 4px;\n font-size:0.8rem;\n opacity:0.8;\n display:flex;\n align-items:center;\n justify-content:center;\n}\n \n #toc-expand-all:hover,#toc-collapse-all:hover{\n opacity:1;\n}\n \n .toc-icon{\n width:14px;\n height:14px;\n display:inline-block;\n}\n\n/*TOC Items*/\n #toc-list{\n list-style:none;\n flex:1;\n overflow-y:auto;\n margin:0;\n padding:6px;\n}\n #toc-list li{\n padding:4px;\n cursor:pointer;\n border-radius:3px;\n transition:background-color 0.2s;\n white-space:nowrap;\n overflow:hidden;\n text-overflow:ellipsis;\n max-width:100%;\n}\n #toc-list li:hover{\n background:#f0f0f0;\n}\n #toc-list ul{\n margin-left:16px;\n padding:0;\n width:100%;\n}\n #toc-list ul li::before{\n content:"";\n}\n #toc-list ul ul li{\n font-size:0.75rem;\n color:#555;\n}\n \n/*Collapsible items*/\n .toc-toggle{\n display:inline-flex;\n align-items:center;\n justify-content:center;\n width:14px;\n height:14px;\n margin-right:4px;\n cursor:pointer;\n transition:transform 0.2s;\n flex-shrink:0;\n}\n \n .toc-item-content{\n display:inline-block;\n max-width:calc(100%-18px);\n overflow:hidden;\n text-overflow:ellipsis;\n vertical-align:bottom;\n pointer-events:none;\n}\n \n .toc-collapse-trigger{\n cursor:pointer;\n user-select:none;\n flex-direction:row;\n align-items:flex-start;\n}\n \n .toc-collapsed>ul{\n display:none;\n}\n \n .toc-clickable-content{\n flex:1;\n cursor:pointer;\n overflow:hidden;\n text-overflow:ellipsis;\n padding-left:2px;\n}\n\n/*Always-visible handle*/\n #toc-handle{\n position:fixed;\n top:50%;\n right:0;\n transform:translateY(-50%);\n width:30px;\n height:80px;\n background:#ccc;\n display:flex;\n align-items:center;\n justify-content:center;\n writing-mode:vertical-rl;\n text-orientation:mixed;\n cursor:pointer;\n font-weight:bold;\n user-select:none;\n z-index:9999;\n transition:background 0.2s;\n}\n #toc-handle:hover{\n background:#bbb;\n}\n\n/*Highlighting headings in the chat*/\n @keyframes highlightFade{\n 0%{background-color:#fffa99}\n 100%{background-color:transparent}\n}\n .toc-highlight{\n animation:highlightFade 1.5s forwards;\n}\n\n/*------Dark Mode Support------*/\n @media(prefers-color-scheme:dark){\n #toc-panel{\n background:#333;\n border-left:1px solid #555;\n box-shadow:-4px 0 8px rgba(0,0,0,0.7);\n}\n #toc-header{\n background:#555;\n border-bottom:1px solid #666;\n color:#eee;\n}\n #toc-list li:hover{\n background:#444;\n}\n #toc-list{\n color:#eee;\n}\n #toc-list ul ul li{\n color:#aaa;\n}\n #toc-handle{\n background:#555;\n color:#ddd;\n}\n #toc-handle:hover{\n background:#666;\n}\n}\n ',document.head.appendChild(l);const c='<svg xmlns="http://www.w3.org/2000/svg" class="toc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>',i='<svg xmlns="http://www.w3.org/2000/svg" class="toc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>',s=document.createElement("div");s.id="toc-panel",s.innerHTML=`\n<div id="toc-header">\n<span id="toc-header-text">Conversation TOC</span>\n<span id="toc-expand-all" title="Expand All">${c}</span>\n<span id="toc-collapse-all" title="Collapse All">${i}</span>\n</div>\n<ul id="toc-list"></ul>\n `,document.body.appendChild(s);const a=document.createElement("div");a.id="toc-handle",a.textContent="TOC",document.body.appendChild(a);let r=null,d=null,p=0,u=null;e(),setInterval(e,2e3),a.addEventListener("click",(function(){s.classList.toggle("collapsed")}))}();
javascript: (function () {
"use strict";
// If panel already exists, do nothing
if (document.getElementById("toc-panel") || document.getElementById("toc-handle")) {
return;
}
// --- Insert CSS with dark mode support ---
const css = document.createElement("style");
css.textContent = `
/* Panel */
#toc-panel {
position: fixed;
top: 0;
right: 0;
width: 280px;
height: 100%;
background: #fafafa;
box-shadow: -4px 0 8px rgba(0,0,0,0.1);
font-family: sans-serif;
font-size: 0.8rem;
border-left: 1px solid #ddd;
display: flex;
flex-direction: column;
z-index: 9998;
transform: translateX(0);
transition: transform 0.3s ease;
}
#toc-panel.collapsed {
transform: translateX(280px);
}
/* Panel Header */
#toc-header {
padding: 6px 10px;
background: #ddd;
border-bottom: 1px solid #ccc;
font-weight: bold;
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
#toc-header-text {
flex: 1;
}
#toc-expand-all, #toc-collapse-all {
cursor: pointer;
padding: 0 4px;
font-size: 0.8rem;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
}
#toc-expand-all:hover, #toc-collapse-all:hover {
opacity: 1;
}
.toc-icon {
width: 14px;
height: 14px;
display: inline-block;
}
/* TOC Items */
#toc-list {
list-style: none;
flex: 1;
overflow-y: auto;
margin: 0;
padding: 6px;
}
#toc-list li {
padding: 4px;
cursor: pointer;
border-radius: 3px;
transition: background-color 0.2s;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
#toc-list li:hover {
background: #f0f0f0;
}
#toc-list ul {
margin-left: 16px;
padding: 0;
width: 100%;
}
#toc-list ul li::before {
content: "";
}
#toc-list ul ul li {
font-size: 0.75rem;
color: #555;
}
/* Collapsible items */
.toc-toggle {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
margin-right: 4px;
cursor: pointer;
transition: transform 0.2s;
flex-shrink: 0;
}
.toc-item-content {
display: inline-block;
max-width: calc(100% - 18px);
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
pointer-events: none;
}
.toc-collapse-trigger {
cursor: pointer;
user-select: none;
flex-direction: row;
align-items: flex-start;
}
.toc-collapsed > ul {
display: none;
}
.toc-clickable-content {
flex: 1;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 2px;
}
/* Always-visible handle */
#toc-handle {
position: fixed;
top: 50%;
right: 0;
transform: translateY(-50%);
width: 30px;
height: 80px;
background: #ccc;
display: flex;
align-items: center;
justify-content: center;
writing-mode: vertical-rl;
text-orientation: mixed;
cursor: pointer;
font-weight: bold;
user-select: none;
z-index: 9999;
transition: background 0.2s;
}
#toc-handle:hover {
background: #bbb;
}
/* Highlighting headings in the chat */
@keyframes highlightFade {
0% { background-color: #fffa99; }
100% { background-color: transparent; }
}
.toc-highlight {
animation: highlightFade 1.5s forwards;
}
/* ------ Dark Mode Support ------ */
@media (prefers-color-scheme: dark) {
#toc-panel {
background: #333;
border-left: 1px solid #555;
box-shadow: -4px 0 8px rgba(0,0,0,0.7);
}
#toc-header {
background: #555;
border-bottom: 1px solid #666;
color: #eee;
}
#toc-list li:hover {
background: #444;
}
#toc-list {
color: #eee;
}
#toc-list ul ul li {
color: #aaa;
}
#toc-handle {
background: #555;
color: #ddd;
}
#toc-handle:hover {
background: #666;
}
}
`;
document.head.appendChild(css);
// SVG Icons
const plusSvg =
'<svg xmlns="http://www.w3.org/2000/svg" class="toc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>';
const minusSvg =
'<svg xmlns="http://www.w3.org/2000/svg" class="toc-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>';
// --- Create panel & handle ---
const panel = document.createElement("div");
panel.id = "toc-panel";
panel.innerHTML = `
<div id="toc-header">
<span id="toc-header-text">Conversation TOC</span>
<span id="toc-expand-all" title="Expand All">${plusSvg}</span>
<span id="toc-collapse-all" title="Collapse All">${minusSvg}</span>
</div>
<ul id="toc-list"></ul>
`;
document.body.appendChild(panel);
const handle = document.createElement("div");
handle.id = "toc-handle";
handle.textContent = "TOC";
document.body.appendChild(handle);
// Observed container, observer, etc.
let chatContainer = null;
let observer = null;
let isScheduled = false;
let timerId = null;
// Helper for creating TOC items with proper truncation
function createTocItem(text, element, isSubpoint = false, canHaveChildren = false) {
const li = document.createElement("li");
const txt = (text || "").trim() || (isSubpoint ? "Point" : "Section");
if (canHaveChildren) {
li.classList.add("toc-collapse-trigger");
li.innerHTML = `<div style="width: 100%;">
<span class="toc-toggle" role="button" aria-label="Toggle section">${minusSvg}</span>
<span class="toc-clickable-content" title="${txt}">${txt}</span>
</div>`;
// Add click handler specifically for the toggle
const toggle = li.querySelector(".toc-toggle");
toggle.addEventListener("click", function (ev) {
const isExpanded = !li.classList.contains("toc-collapsed");
if (isExpanded) {
// Collapse
li.classList.add("toc-collapsed");
toggle.innerHTML = plusSvg;
} else {
// Expand
li.classList.remove("toc-collapsed");
toggle.innerHTML = minusSvg;
}
ev.stopPropagation();
});
// Add click handler for the content (scroll to element)
const content = li.querySelector(".toc-clickable-content");
content.addEventListener("click", function (ev) {
element.classList.remove("toc-highlight");
// Force reflow to restart animation
element.offsetWidth;
element.classList.add("toc-highlight");
element.scrollIntoView({ behavior: "smooth", block: "start" });
ev.stopPropagation();
});
} else {
// No children, just a simple clickable item
li.innerHTML = `<div style="width: 100%;">
<span class="toc-clickable-content" title="${txt}">${txt}</span>
</div>`;
// Add click handler
const content = li.querySelector(".toc-clickable-content");
content.addEventListener("click", function (ev) {
element.classList.remove("toc-highlight");
// Force reflow to restart animation
element.offsetWidth;
element.classList.add("toc-highlight");
element.scrollIntoView({ behavior: "smooth", block: "start" });
ev.stopPropagation();
});
}
return li;
}
// Debounce the TOC build to avoid high CPU usage on rapid changes
function debounceBuildTOC() {
if (isScheduled) return;
isScheduled = true;
timerId = setTimeout(function () {
buildTOC();
isScheduled = false;
}, 300);
}
// Expand or collapse all TOC items
function toggleAllItems(expand) {
const triggers = document.querySelectorAll(".toc-collapse-trigger");
triggers.forEach((item) => {
const toggle = item.querySelector(".toc-toggle");
if (expand) {
item.classList.remove("toc-collapsed");
toggle.innerHTML = minusSvg;
} else {
item.classList.add("toc-collapsed");
toggle.innerHTML = plusSvg;
}
});
}
// Build/refresh the TOC
function buildTOC() {
const list = document.getElementById("toc-list");
if (!list) return;
list.innerHTML = "";
// Find conversation turns - support both ChatGPT and Grok formats
const grokResponses = (chatContainer || document).querySelectorAll(
".relative.group.flex.flex-col.justify-center.w-full.max-w-3xl.md\\:px-4.pb-2.gap-2.items-start",
);
const chatGptArticles = (chatContainer || document).querySelectorAll("article[data-testid^='conversation-turn-']");
if (grokResponses.length === 0 && chatGptArticles.length === 0) {
list.innerHTML = '<li style="opacity:0.7;font-style:italic;">Empty chat</li>';
return;
}
// Process Grok format if we have Grok responses
if (grokResponses.length > 0) {
let responseCount = 0;
let userCount = 0;
// Loop over all elements that could be user or AI responses
for (let i = 0; i < grokResponses.length; i++) {
const response = grokResponses[i];
const li = document.createElement("li");
li.classList.add("toc-collapse-trigger");
// Check if it's an AI response (has h3 elements and other rich formatting)
const hasH3 = response.querySelectorAll("h3").length > 0;
let label = "";
if (hasH3) {
responseCount++;
label = `Response ${responseCount} (Grok)`;
} else {
userCount++;
label = `Turn ${userCount} (You)`;
}
li.innerHTML = `
<div style="width: 100%;">
<span class="toc-toggle" role="button" aria-label="Toggle turn">${minusSvg}</span>
<span class="toc-clickable-content" title="${label}">${label}</span>
</div>
`;
// Add collapsible functionality
const toggle = li.querySelector(".toc-toggle");
toggle.addEventListener("click", function (ev) {
const isExpanded = !li.classList.contains("toc-collapsed");
if (isExpanded) {
// Collapse
li.classList.add("toc-collapsed");
toggle.innerHTML = plusSvg;
} else {
// Expand
li.classList.remove("toc-collapsed");
toggle.innerHTML = minusSvg;
}
ev.stopPropagation();
});
// On click: scroll to the response
const content = li.querySelector(".toc-clickable-content");
content.addEventListener("click", function (ev) {
response.scrollIntoView({ behavior: "smooth", block: "start" });
ev.stopPropagation();
});
// Process headings in Grok's response (only for AI responses)
if (hasH3) {
const subUl = document.createElement("ul");
const h3Elements = response.querySelectorAll("h3");
// Process each h3 element (main section titles)
for (let h = 0; h < h3Elements.length; h++) {
const h3 = h3Elements[h];
// Skip headings inside <pre> or <code>
let skip = false;
let p = h3;
while (p) {
if (p.tagName === "PRE" || p.tagName === "CODE") {
skip = true;
break;
}
p = p.parentElement;
}
if (skip) continue;
// Create section item (can have children)
const sectionLi = createTocItem(h3.textContent, h3, false, true);
// Find h4s, ordered lists, or strong elements that follow this h3 until the next h3
const h4Subpoints = document.createElement("ul");
// Get all following elements until next h3
let nextElement = h3.nextElementSibling;
let foundSubpoints = false;
let currentH4 = null;
let currentH4Li = null;
while (nextElement && nextElement.tagName !== "H3") {
// Handle H4 headings
if (nextElement.tagName === "H4") {
currentH4 = nextElement;
currentH4Li = createTocItem(nextElement.textContent, nextElement, true, true);
h4Subpoints.appendChild(currentH4Li);
foundSubpoints = true;
}
// Handle ordered lists (OL) - they should be children of the previous H4
else if (nextElement.tagName === "OL" && currentH4Li) {
const olUl = document.createElement("ul");
const liItems = nextElement.querySelectorAll("li");
for (let l = 0; l < liItems.length; l++) {
const liItem = liItems[l];
// Look for strong tag inside the LI
const strongElement = liItem.querySelector("strong");
if (strongElement) {
const listItemLi = createTocItem(strongElement.textContent, liItem, true);
olUl.appendChild(listItemLi);
}
}
if (olUl.children.length > 0) {
currentH4Li.appendChild(olUl);
}
}
// Check for strong elements inside paragraphs (when there's no H4 parent)
else if (nextElement.tagName === "P" && !currentH4) {
const strongElements = nextElement.querySelectorAll("strong");
if (strongElements.length > 0) {
// Use the first strong element as a subpoint
const strongEl = strongElements[0];
const pointLi = createTocItem(strongEl.textContent, nextElement, true);
h4Subpoints.appendChild(pointLi);
foundSubpoints = true;
}
}
nextElement = nextElement.nextElementSibling;
}
// Add the subpoints if any were found
if (foundSubpoints) {
sectionLi.appendChild(h4Subpoints);
}
subUl.appendChild(sectionLi);
}
if (subUl.children.length > 0) {
li.appendChild(subUl);
}
}
list.appendChild(li);
}
} else if (chatGptArticles.length > 0) {
// Original ChatGPT processing
// Loop over turns
for (let i = 0; i < chatGptArticles.length; i++) {
const art = chatGptArticles[i];
const li = document.createElement("li");
li.classList.add("toc-collapse-trigger");
// Check if AI
const sr = art.querySelector("h6.sr-only");
let isAI = false;
let label = "";
if (sr && sr.textContent.indexOf("ChatGPT said:") >= 0) {
isAI = true;
label = `Turn ${i + 1} (AI)`;
} else {
label = `Turn ${i + 1} (You)`;
}
li.innerHTML = `
<div style="width: 100%;">
<span class="toc-toggle" role="button" aria-label="Toggle turn">${minusSvg}</span>
<span class="toc-clickable-content" title="${label}">${label}</span>
</div>
`;
// Add collapsible functionality
const toggle = li.querySelector(".toc-toggle");
toggle.addEventListener("click", function (ev) {
const isExpanded = !li.classList.contains("toc-collapsed");
if (isExpanded) {
// Collapse
li.classList.add("toc-collapsed");
toggle.innerHTML = plusSvg;
} else {
// Expand
li.classList.remove("toc-collapsed");
toggle.innerHTML = minusSvg;
}
ev.stopPropagation();
});
// On click: scroll to turn
const content = li.querySelector(".toc-clickable-content");
content.addEventListener("click", function (ev) {
art.scrollIntoView({ behavior: "smooth", block: "start" });
ev.stopPropagation();
});
// AI subheadings
if (isAI) {
const subUl = document.createElement("ul");
const heads = art.querySelectorAll("h3:not(.sr-only)");
for (let h = 0; h < heads.length; h++) {
const hd = heads[h];
// Skip headings inside <pre> or <code>
let skip = false;
let p = hd;
while (p) {
if (p.tagName === "PRE" || p.tagName === "CODE") {
skip = true;
break;
}
p = p.parentElement;
}
if (skip) continue;
// Create section item
const sectionLi = createTocItem(hd.textContent, hd, false, true);
subUl.appendChild(sectionLi);
}
if (subUl.children.length > 0) {
li.appendChild(subUl);
}
}
list.appendChild(li);
}
}
// Setup the expand/collapse all buttons
document.getElementById("toc-expand-all").addEventListener("click", function (e) {
toggleAllItems(true);
e.stopPropagation();
});
document.getElementById("toc-collapse-all").addEventListener("click", function (e) {
toggleAllItems(false);
e.stopPropagation();
});
}
// Attach observer to new container if needed
function attachObserver() {
// Attempt to locate the main chat container
const grokContainer = document.querySelector("main") || null;
const chatGptContainer = document.querySelector("main#main") || document.querySelector(".chat-container") || null;
const c = grokContainer || chatGptContainer;
if (c !== chatContainer) {
chatContainer = c;
// Disconnect old observer if any
if (observer) {
observer.disconnect();
observer = null;
}
// Attach new observer if container found
if (chatContainer) {
observer = new MutationObserver(function () {
debounceBuildTOC();
});
observer.observe(chatContainer, { childList: true, subtree: true });
buildTOC();
}
}
}
// Attempt to attach on load
attachObserver();
// Re-check every 2s in case container changes
const reAttachInterval = setInterval(attachObserver, 2000);
// Panel toggle
handle.addEventListener("click", function () {
panel.classList.toggle("collapsed");
});
})();
#!/usr/bin/env node
/**
* TOC.js Minifier that preserves the javascript: prefix
*
* This script takes TOC.js and creates a minified version (TOC-min.js)
* while preserving the 'javascript:' prefix needed for bookmarklets.
*
* Usage:
* 1. Install dependencies: npm install terser
* 2. Run: node minify-toc.js
*/
const fs = require('fs');
const path = require('path');
const { minify } = require('terser');
// Paths
const sourcePath = path.join(__dirname, 'TOC.js');
const outputPath = path.join(__dirname, 'TOC-min.js');
// Read the source file
let jsContent;
try {
jsContent = fs.readFileSync(sourcePath, 'utf8');
console.log(`Read source file: ${sourcePath} (${jsContent.length} bytes)`);
} catch (err) {
console.error(`Error reading source file: ${err.message}`);
process.exit(1);
}
// Extract and preserve the 'javascript:' prefix
const jsPrefix = jsContent.match(/^(javascript:)/);
if (!jsPrefix) {
console.error('Error: Source file does not start with "javascript:" prefix');
process.exit(1);
}
// Remove the prefix for minification
const jsCodeWithoutPrefix = jsContent.replace(/^javascript:/, '');
// Enhanced minify options for maximum compression
const options = {
compress: {
drop_console: false,
drop_debugger: true,
ecma: 2020,
passes: 3, // More passes for better compression
unsafe: true, // Enable unsafe transformations for smaller output
unsafe_comps: true, // Optimize comparisons for smaller output
pure_getters: true, // Assume properties never have side-effects
unsafe_methods: true,
unsafe_proto: true,
hoist_funs: true,
booleans_as_integers: true // Convert true/false to 1/0
},
mangle: {
properties: {
regex: /^_/ // Only mangle properties that start with underscore
},
toplevel: true // Mangle variables in the top level scope
},
format: {
comments: false, // Remove all comments
beautify: false, // No beautification
indent_level: 0, // No indentation
ascii_only: false, // Allow non-ASCII characters
quote_style: 0, // Use double or single quotes depending on which one produces shorter output
max_line_len: 0, // No max line length
semicolons: true // Always use semicolons
}
};
// Additional manual whitespace optimization
function extraOptimize(code) {
return code
.replace(/\s+/g, ' ') // Replace multiple whitespace with single space
.replace(/\s*([{}()[\],;:])\s*/g, '$1') // Remove spaces around brackets, braces, parentheses, commas, colons and semicolons
.replace(/;\}/g, '}') // Remove unnecessary semicolons before closing braces
.replace(/\s*\+\s*/g, '+') // Remove spaces around plus signs
.replace(/\s*-\s*/g, '-') // Remove spaces around minus signs
.replace(/\s*\*\s*/g, '*') // Remove spaces around multiplication signs
.replace(/\s*\/\s*/g, '/') // Remove spaces around division signs
.replace(/\s*=\s*/g, '=') // Remove spaces around equals signs
.replace(/\s*>\s*/g, '>') // Remove spaces around greater than signs
.replace(/\s*<\s*/g, '<') // Remove spaces around less than signs
.replace(/\s*!\s*/g, '!') // Remove spaces after exclamation marks
.replace(/\s*\?\s*/g, '?') // Remove spaces around question marks
.replace(/\s*&&\s*/g, '&&') // Remove spaces around logical AND
.replace(/\s*\|\|\s*/g, '||') // Remove spaces around logical OR
.replace(/\s*:\s*/g, ':') // Remove spaces around colons
.replace(/\s+{/g, '{') // Remove spaces before opening braces
.replace(/}\s+/g, '}') // Remove spaces after closing braces
.replace(/\(\s+/g, '(') // Remove spaces after opening parentheses
.replace(/\s+\)/g, ')') // Remove spaces before closing parentheses
.trim(); // Remove any leading/trailing whitespace
}
async function minifyCode() {
try {
console.log('Minifying code...');
const result = await minify(jsCodeWithoutPrefix, options);
if (result.error) {
console.error('Error during minification:', result.error);
process.exit(1);
}
// Further optimize the code
let optimizedCode = extraOptimize(result.code);
// Add back the javascript: prefix
const minifiedWithPrefix = `${jsPrefix[0]}${optimizedCode}`;
// Write to output file
fs.writeFileSync(outputPath, minifiedWithPrefix);
// Output stats
const originalSize = jsContent.length;
const minifiedSize = minifiedWithPrefix.length;
const reduction = ((1 - minifiedSize / originalSize) * 100).toFixed(2);
console.log(`
Minification complete!
- Original size: ${originalSize} bytes
- Minified size: ${minifiedSize} bytes
- Reduction: ${reduction}%
- Saved to: ${outputPath}
`);
} catch (err) {
console.error('Error:', err);
process.exit(1);
}
}
minifyCode();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment