您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a zoomed-out preview scrollbar to T3 Chat, showing only the chat log.
// ==UserScript== // @name T3 Chat Zoomed-Out Scrollbar // @namespace http://tampermonkey.net/ // @version 2.6 // @description Adds a zoomed-out preview scrollbar to T3 Chat, showing only the chat log. // @author Dimava, T3 Chat, Gemini 2.0 Flash // @match https://t3.chat/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; const PREVIEW_SCROLLBAR_ID = "t3-chat-preview-scrollbar"; const PREVIEW_CONTENT_ID = "t3-chat-preview-content"; const PREVIEW_THUMB_ID = "t3-chat-preview-thumb"; const PREVIEW_SCALE = 0.1; // Scale factor for the preview const THUMB_HEIGHT_VH = 10; // Thumb height in viewport height units const SCROLLBAR_OFFSET_PX = 20; // Offset from the right edge const THROTTLE_DELAY_MS = 3000; // Throttle updates to every 3 seconds let lastContentUpdate = 0; // Timestamp of the last content update let scrollParent; let previewContent; let contentObserver; let thumb; let logDiv; let scrollbar; // Cleanup function function cleanup() { if (scrollbar) scrollbar.remove(); window.removeEventListener("resize", updatePreviewContent); if (scrollParent) scrollParent.removeEventListener("scroll", updateThumbPosition); stopHeightPolling(); if (contentObserver) contentObserver.disconnect(); } // Call cleanup if it exists on the window if (window.t3ChatPreviewScrollbarCleanup) { window.t3ChatPreviewScrollbarCleanup(); } // Function to update the preview content (throttled) function updatePreviewContent() { if (!logDiv || !previewContent) return; const now = Date.now(); if (now - lastContentUpdate >= THROTTLE_DELAY_MS) { // Clone the logDiv content const clonedLogDiv = logDiv.cloneNode(true); // Apply styles to remove interactive elements const allElements = clonedLogDiv.querySelectorAll("*"); allElements.forEach((el) => { el.style.pointerEvents = "none"; el.style.userSelect = "none"; }); // Clear existing content and append the cloned content while (previewContent.firstChild) { previewContent.removeChild(previewContent.firstChild); } previewContent.appendChild(clonedLogDiv); // Set preview content width const logWidth = logDiv.getBoundingClientRect().width; previewContent.style.width = `${logWidth}px`; lastContentUpdate = now; } } // Function to update the thumb position function updateThumbPosition() { if (!logDiv || !scrollParent || !thumb || !scrollbar || !previewContent) return; const scrollHeight = scrollParent.scrollHeight; const clientHeight = scrollParent.clientHeight; const scrollTop = scrollParent.scrollTop; const scrollbarHeight = scrollbar.offsetHeight; const thumbHeightPx = (THUMB_HEIGHT_VH / 100) * window.innerHeight; // Constant thumb height let thumbPosition = (scrollbarHeight - thumbHeightPx) * (scrollTop / scrollHeight); // Proportional thumb position // Correct thumb position to allow scrolling to the very bottom thumbPosition = Math.min( thumbPosition, scrollbarHeight - thumbHeightPx ); thumb.style.height = `${thumbHeightPx}px`; thumb.style.top = `${thumbPosition}px`; // Move the preview content up to align with the thumb const thumbCenter = thumbPosition + thumbHeightPx / 2; previewContent.style.top = `-${scrollTop * PREVIEW_SCALE - thumbCenter}px`; } // Function to handle thumb dragging function handleThumbDrag(event) { if (!logDiv || !scrollParent || !thumb || !scrollbar || !previewContent) return; const scrollHeight = scrollParent.scrollHeight; const clientHeight = scrollParent.clientHeight; const scrollbarHeight = scrollbar.offsetHeight; let startY = event.clientY; let startTop = thumb.offsetTop; function drag(e) { const deltaY = e.clientY - startY; let newTop = startTop + deltaY; const h = (THUMB_HEIGHT_VH / 100) * window.innerHeight; // Keep thumb within scrollbar bounds newTop = Math.max(0, Math.min(newTop, scrollbarHeight - thumb.offsetHeight)); thumb.style.top = `${newTop}px`; // Calculate scroll position based on thumb position let scrollPosition = (newTop / (scrollbarHeight - h)) * scrollHeight; // Correct scroll position to allow scrolling to the very bottom scrollPosition = Math.min( scrollPosition, scrollHeight - clientHeight ); scrollParent.scrollTop = scrollPosition; // Move the preview content up to align with the thumb const thumbCenter = newTop + (THUMB_HEIGHT_VH / 100) * window.innerHeight / 2; previewContent.style.top = `-${scrollPosition * PREVIEW_SCALE - thumbCenter}px`; } function stopDrag() { document.removeEventListener("mousemove", drag); document.removeEventListener("mouseup", stopDrag); } document.addEventListener("mousemove", drag); document.addEventListener("mouseup", stopDrag); event.preventDefault(); // Prevent text selection during drag } // Function to create the preview scrollbar function createPreviewScrollbar() { scrollbar = document.createElement("div"); scrollbar.id = PREVIEW_SCROLLBAR_ID; scrollbar.style.cssText = ` position: fixed; top: 0; right: ${SCROLLBAR_OFFSET_PX}px; /* Offset from the right edge */ width: 50px; /* Initial width, will be updated */ height: 100vh; background-color: rgba(0, 0, 0, 0.1); overflow: hidden; z-index: 1000; `; previewContent = document.createElement("div"); previewContent.id = PREVIEW_CONTENT_ID; previewContent.style.cssText = ` position: relative; width: 100%; /* Initial width, will be updated */ height: auto; transform: scale(${PREVIEW_SCALE}); transform-origin: top left; overflow: hidden; pointer-events: none; top: 0; /* Initial top position */ `; scrollbar.appendChild(previewContent); thumb = document.createElement("div"); thumb.id = PREVIEW_THUMB_ID; thumb.style.cssText = ` position: absolute; top: 0; left: 0; width: 100%; height: ${THUMB_HEIGHT_VH}vh; /* Constant thumb height */ background-color: rgba(66, 135, 245, 0.5); cursor: grab; `; scrollbar.appendChild(thumb); document.body.appendChild(scrollbar); // Event listener for thumb dragging thumb.addEventListener("mousedown", handleThumbDrag); // Event listener for scrollbar mousedown scrollbar.addEventListener("mousedown", handleScrollbarMousedown); } // Function to handle scrollbar click function handleScrollbarMousedown(event) { if (event.target === thumb) return; // Ignore clicks on the thumb if (!logDiv || !scrollParent || !thumb || !scrollbar) return; const scrollbarHeight = scrollbar.offsetHeight; const clickY = event.clientY - scrollbar.getBoundingClientRect().top; const thumbHeightPx = (THUMB_HEIGHT_VH / 100) * window.innerHeight; const thumbPosition = thumb.offsetTop; const thumbCenter = thumbPosition + thumbHeightPx / 2; // Calculate the difference between the click position and the thumb center const deltaY = clickY - thumbCenter; // Adjust the scroll position by the scaled difference scrollParent.scrollTop += deltaY / PREVIEW_SCALE; // Ensure scroll position stays within bounds scrollParent.scrollTop = Math.max( 0, Math.min(scrollParent.scrollTop, scrollParent.scrollHeight - scrollParent.clientHeight) ); // Update thumb position immediately updateThumbPosition(); } let heightPollingInterval; function pollElementHeight() { if (!logDiv || !scrollbar || !previewContent) return; const logWidth = logDiv.getBoundingClientRect().width; const scrollbarWidth = logWidth / 10; scrollbar.style.width = `${scrollbarWidth}px`; previewContent.style.width = `${logWidth}px`; requestAnimationFrame(pollElementHeight); } function startHeightPolling() { heightPollingInterval = requestAnimationFrame(pollElementHeight); } function stopHeightPolling() { cancelAnimationFrame(heightPollingInterval); } function initialize() { // Initialize createPreviewScrollbar(); // Get elements logDiv = document.querySelector('[role="log"]'); if (!logDiv) return; scrollParent = logDiv.parentNode; // Initial update updatePreviewContent(); updateThumbPosition(); // Event listeners contentObserver = new MutationObserver(() => { updatePreviewContent(); }); contentObserver.observe(logDiv, { childList: true, subtree: true, characterData: true, }); window.addEventListener("resize", updatePreviewContent); if (!scrollParent) return; scrollParent.addEventListener("scroll", updateThumbPosition); // Start polling for height changes startHeightPolling(); } function waitForLogDiv() { logDiv = document.querySelector('[role="log"]'); if (logDiv) { initialize(); } else { setTimeout(waitForLogDiv, 200); // Check every 200ms } } // Attach cleanup function to window window.t3ChatPreviewScrollbarCleanup = () => { cleanup(); delete window.t3ChatPreviewScrollbarCleanup; }; // Start waiting for the log div waitForLogDiv(); // Public functions window.updatePreview = updatePreviewContent; window.updateThumb = updateThumbPosition; })();