Created
April 7, 2025 16:56
-
-
Save MinhOmega/3588d2bbfea4f822e8ed1ee20b94caec to your computer and use it in GitHub Desktop.
[Minified] - ChatGPT-Grok-TOC.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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")}))}(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | |
| }); | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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