您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhanced NotebookLM: Auto-collapse sidebar, resizable Studio panel, UI improvements
// ==UserScript== // @name Better NotebookLM // @namespace http://tampermonkey.net/ // @version 0.5 // @description Enhanced NotebookLM: Auto-collapse sidebar, resizable Studio panel, UI improvements // @author djshigel // @license MIT // @match https://notebooklm.google.com/* // @match https://notebooklm.google/* // @icon https://notebooklm.google.com/favicon.ico // @grant none // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const CONFIG = { defaultStudioWidth: 30, // Default Studio panel width (%) expandStudioWidth: 53, // Width to expand Studio panel when min-inline-size is detected (%) minPanelWidth: 20, // Minimum panel width (%) maxPanelWidth: 80, // Maximum panel width (%) dragHandleWidth: 8, // Drag handle width (px) rightPanelOffset: 88, // Fixed offset to prevent right panel overflow (px) }; // ======================== // Auto-collapse sidebar // ======================== function collapseSidebar() { const panel = document.querySelector('section.source-panel:not(.panel-collapsed)'); if (panel) { const toggleButton = panel.querySelector('button.toggle-source-panel-button'); if (toggleButton) { toggleButton.click(); console.log('Better NotebookLM: Sidebar collapsed'); return true; } } const collapsedPanel = document.querySelector('section.source-panel.panel-collapsed'); if (collapsedPanel) { return true; } return false; } // ======================== // Studio panel resizer // ======================== let dragHandlers = { mousedown: null, mousemove: null, mouseup: null }; let resizerInitialized = false; function destroyStudioResizer() { const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); const gutter = studioPanel?.previousElementSibling; const chatPanel = gutter?.previousElementSibling; if (gutter && gutter.dataset.resizerInitialized) { // Remove event listeners if (dragHandlers.mousedown) { gutter.removeEventListener('mousedown', dragHandlers.mousedown); } if (dragHandlers.mousemove) { document.removeEventListener('mousemove', dragHandlers.mousemove); } if (dragHandlers.mouseup) { document.removeEventListener('mouseup', dragHandlers.mouseup); } // Reset gutter styles gutter.style.cursor = ''; gutter.style.backgroundColor = ''; gutter.style.width = ''; delete gutter.dataset.resizerInitialized; // Remove ALL custom styles from panels if (studioPanel) { studioPanel.removeAttribute('style'); } if (chatPanel) { chatPanel.removeAttribute('style'); } // Reset initialization flag resizerInitialized = false; console.log('Better NotebookLM: Studio resizer destroyed'); } } function observeStudioCollapse() { let lastCollapseState = null; const observer = new MutationObserver(() => { const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); if (!studioPanel) return; // Check for min-inline-size and handle appropriately if (studioPanel.style.minInlineSize) { const minInlineValue = studioPanel.style.minInlineSize; // Always remove min-inline-size studioPanel.style.minInlineSize = ''; // Only expand for specific large values (like 37.5vw that NotebookLM uses) // This indicates actual content like video or report is being shown if ((minInlineValue.includes('vw') && parseFloat(minInlineValue) >= 30) || (minInlineValue.includes('%') && parseFloat(minInlineValue) >= 40)) { const chatPanel = document.querySelector('section.chat-panel'); if (!studioPanel.classList.contains('panel-collapsed') && chatPanel) { // Add transition for smooth animation studioPanel.style.transition = 'all 0.3s ease'; chatPanel.style.transition = 'all 0.3s ease'; // Apply expanded width studioPanel.style.flex = ''; studioPanel.style.maxWidth = `calc(${CONFIG.expandStudioWidth}% - ${CONFIG.rightPanelOffset}px)`; studioPanel.style.width = `calc(${CONFIG.expandStudioWidth}% - ${CONFIG.rightPanelOffset}px)`; chatPanel.style.flex = `0 0 ${100 - CONFIG.expandStudioWidth}%`; chatPanel.style.maxWidth = `${100 - CONFIG.expandStudioWidth}%`; // Save the expanded width localStorage.setItem('betterNotebookLM_studioWidth', CONFIG.expandStudioWidth.toString()); // Remove transition after animation setTimeout(() => { studioPanel.style.transition = ''; chatPanel.style.transition = ''; }, 300); console.log(`Better NotebookLM: Detected ${minInlineValue} - expanded Studio to ${CONFIG.expandStudioWidth}%`); } } else { console.log(`Better NotebookLM: Removed min-inline-size (${minInlineValue})`); } } const isCollapsed = studioPanel.classList.contains('panel-collapsed'); // Only act if state actually changed if (lastCollapseState !== isCollapsed) { lastCollapseState = isCollapsed; if (isCollapsed) { // Destroy resizer when collapsed destroyStudioResizer(); // Remove flex and max-width from chat panel when Studio is collapsed const chatPanel = document.querySelector('section.chat-panel'); if (chatPanel) { chatPanel.style.flex = ''; chatPanel.style.maxWidth = ''; console.log('Better NotebookLM: Removed chat panel custom styles on Studio collapse'); } console.log('Better NotebookLM: Studio collapsed - resizer destroyed, width forced to 56px'); } else { // Re-initialize resizer when expanded setTimeout(() => { resizerInitialized = false; // Reset flag before re-initializing resizerInitialized = initStudioResizer(); // Restore saved size if available const savedWidth = localStorage.getItem('betterNotebookLM_studioWidth'); if (savedWidth && resizerInitialized) { const gutter = studioPanel.previousElementSibling; const chatPanel = gutter?.previousElementSibling; if (chatPanel && studioPanel) { const width = parseFloat(savedWidth); studioPanel.style.flex = ''; studioPanel.style.maxWidth = `calc(${width}% - ${CONFIG.rightPanelOffset}px)`; studioPanel.style.width = `calc(${width}% - ${CONFIG.rightPanelOffset}px)`; chatPanel.style.flex = `0 0 ${100 - width}%`; chatPanel.style.maxWidth = `${100 - width}%`; console.log('Better NotebookLM: Studio expanded - resizer and size restored'); } } else { console.log('Better NotebookLM: Studio expanded - resizer restored'); } }, 100); } } }); // Observe the entire document for class changes observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'style'], subtree: true }); return observer; } function initStudioResizer() { // Find the gutter between chat and Studio panels const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); if (!studioPanel) return false; // Don't initialize if Studio is collapsed if (studioPanel.classList.contains('panel-collapsed')) { console.log('Better NotebookLM: Studio is collapsed, skipping resizer init'); return false; } // Find the gutter that's immediately before the Studio panel const gutter = studioPanel.previousElementSibling; if (!gutter || !gutter.classList.contains('panel-gutter')) return false; // Skip if already initialized if (gutter.dataset.resizerInitialized) return true; // Apply resize handle styles gutter.style.cursor = 'col-resize'; gutter.style.position = 'relative'; gutter.style.userSelect = 'none'; gutter.style.width = CONFIG.dragHandleWidth + 'px'; gutter.style.backgroundColor = 'transparent'; gutter.style.transition = 'background-color 0.2s'; // Visual feedback on hover gutter.addEventListener('mouseenter', () => { gutter.style.backgroundColor = 'rgba(66, 133, 244, 0.2)'; }); gutter.addEventListener('mouseleave', () => { if (!gutter.dataset.dragging) { gutter.style.backgroundColor = 'transparent'; } }); // Drag handling let isDragging = false; let startX = 0; let startRightWidth = 0; let chatPanel = null; let rightPanel = studioPanel; let container = null; const handleMouseDown = (e) => { isDragging = true; startX = e.clientX; gutter.dataset.dragging = 'true'; gutter.style.backgroundColor = 'rgba(66, 133, 244, 0.4)'; // Get adjacent panels chatPanel = gutter.previousElementSibling; container = gutter.parentElement; if (rightPanel && container) { const containerWidth = container.offsetWidth; startRightWidth = (rightPanel.offsetWidth / containerWidth) * 100; } // Prevent cursor and text selection during drag document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; // Add overlay to prevent iframe interference const overlay = document.createElement('div'); overlay.id = 'resize-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; cursor: col-resize; `; document.body.appendChild(overlay); e.preventDefault(); }; const handleMouseMove = (e) => { if (!isDragging || !chatPanel || !rightPanel || !container) return; const containerWidth = container.offsetWidth; const deltaX = e.clientX - startX; const deltaPercent = (deltaX / containerWidth) * 100; let newRightWidth = startRightWidth - deltaPercent; // Limit width newRightWidth = Math.max(CONFIG.minPanelWidth, Math.min(CONFIG.maxPanelWidth, newRightWidth)); const newLeftWidth = 100 - newRightWidth; // Update panel sizes chatPanel.style.flex = ''; chatPanel.style.maxWidth = `${newLeftWidth}%`; rightPanel.style.flex = ''; rightPanel.style.maxWidth = `calc(${newRightWidth}% - ${CONFIG.rightPanelOffset}px)`; rightPanel.style.width = `calc(${newRightWidth}% - ${CONFIG.rightPanelOffset}px)`; // Save to localStorage localStorage.setItem('betterNotebookLM_studioWidth', newRightWidth.toString()); e.preventDefault(); }; const handleMouseUp = () => { if (!isDragging) return; isDragging = false; delete gutter.dataset.dragging; gutter.style.backgroundColor = 'transparent'; // Restore cursor and text selection document.body.style.cursor = ''; document.body.style.userSelect = ''; // Remove overlay const overlay = document.getElementById('resize-overlay'); if (overlay) overlay.remove(); }; // Store handlers globally for cleanup dragHandlers.mousedown = handleMouseDown; dragHandlers.mousemove = handleMouseMove; dragHandlers.mouseup = handleMouseUp; gutter.addEventListener('mousedown', handleMouseDown); document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // Mark as initialized gutter.dataset.resizerInitialized = 'true'; // Apply saved width const savedWidth = localStorage.getItem('betterNotebookLM_studioWidth'); if (savedWidth && chatPanel && rightPanel) { const width = parseFloat(savedWidth); rightPanel.style.flex = `0 0 ${width}%`; rightPanel.style.maxWidth = `calc(${width}% - ${CONFIG.rightPanelOffset}px)`; rightPanel.style.width = `calc(${width}% - ${CONFIG.rightPanelOffset}px)`; chatPanel.style.flex = `0 0 ${100 - width}%`; chatPanel.style.maxWidth = `${100 - width}%`; } console.log('Better NotebookLM: Studio resizer initialized'); return true; } // ======================== // Main processing // ======================== let sidebarProcessed = false; let retryCount = 0; const maxRetries = 20; function processElements() { // Process sidebar if (!sidebarProcessed) { sidebarProcessed = collapseSidebar(); } // Initialize Studio resizer if (!resizerInitialized) { const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); // If Studio exists and is collapsed, mark as complete but don't initialize if (studioPanel && studioPanel.classList.contains('panel-collapsed')) { resizerInitialized = true; console.log('Better NotebookLM: Studio is collapsed, skipping resizer'); } else { resizerInitialized = initStudioResizer(); } } // Check completion if ((sidebarProcessed && resizerInitialized) || retryCount >= maxRetries) { if (retryCount >= maxRetries) { console.log('Better NotebookLM: Maximum retries reached'); } else { console.log('Better NotebookLM: All features enabled'); } return true; } return false; } // Periodic element check const checkInterval = setInterval(() => { retryCount++; if (processElements()) { clearInterval(checkInterval); // Start observing Studio collapse state after initialization observeStudioCollapse(); } }, 500); // Handle page navigation let lastUrl = location.href; const urlObserver = new MutationObserver(() => { const currentUrl = location.href; if (currentUrl !== lastUrl) { lastUrl = currentUrl; console.log('Better NotebookLM: Page changed, reinitializing...'); // Reset flags sidebarProcessed = false; resizerInitialized = false; retryCount = 0; setTimeout(() => { const retryInterval = setInterval(() => { retryCount++; if (!sidebarProcessed) { sidebarProcessed = collapseSidebar(); } if (!resizerInitialized) { resizerInitialized = initStudioResizer(); } if ((sidebarProcessed && resizerInitialized) || retryCount >= 10) { clearInterval(retryInterval); observeStudioCollapse(); } }, 500); }, 1000); } }); // Monitor URL changes urlObserver.observe(document.body, { childList: true, subtree: true }); // ======================== // Style injection // ======================== const style = document.createElement('style'); style.textContent = ` /* Force logo margins */ labs-tailwind-logo img { margin-left: 5px !important; margin-right: 5px !important; } /* Force Source panel expanded width */ section.source-panel:not(.panel-collapsed) { width: 395px !important; min-width: 395px !important; max-width: 395px !important; flex: 0 0 395px !important; } /* Panel gutter hover effect */ .panel-gutter[data-resizer-initialized="true"] { position: relative; z-index: 100; } .panel-gutter[data-resizer-initialized="true"]::before { content: ''; position: absolute; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); width: 3px; background: rgba(66, 133, 244, 0.5); opacity: 0; transition: opacity 0.2s; } .panel-gutter[data-resizer-initialized="true"]:hover::before { opacity: 1; } /* Disable animations during drag to prevent jitter */ .panel-gutter[data-dragging="true"] ~ *, .panel-gutter[data-dragging="true"] ~ * * { transition: none !important; } /* Source panel collapsed state - hide scrollbar until hover */ .source-panel.panel-collapsed .source-panel-content { overflow: hidden !important; } .source-panel.panel-collapsed .source-panel-content:hover { overflow-y: auto !important; overflow-x: hidden !important; } /* Force Studio panel width when collapsed */ section.studio-panel.panel-collapsed, section[class*="studio"].panel-collapsed, section[class*="right-panel"].panel-collapsed { width: 56px !important; min-width: 56px !important; max-width: 56px !important; flex: 0 0 56px !important; } /* Remove min-inline-size from Studio panel */ section.studio-panel, section[class*="studio"], section[class*="right-panel"] { min-inline-size: unset !important; } /* Chat panel should use available space when Studio is collapsed */ section.chat-panel:has(~ .panel-collapsed) { flex: 1 1 auto !important; max-width: none !important; } /* Sticky Studio panel buttons to prevent overflow */ .studio-panel button.toggle-studio-panel-button, .studio-panel button[aria-label*="Studio"], .studio-panel button[aria-label*="メモ"], .studio-panel button[aria-label*="note"] { position: sticky; right: 0; z-index: 10; } /* Studio panel header sticky positioning */ .studio-panel .panel-header, .studio-panel .studio-header { position: sticky; top: 0; z-index: 10; background: inherit; } `; document.head.appendChild(style); console.log('Better NotebookLM: Initialization complete'); // Additional monitoring for min-inline-size changes as a backup const styleObserver = new MutationObserver(() => { const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); if (studioPanel && studioPanel.style.minInlineSize) { const minInlineValue = studioPanel.style.minInlineSize; // Always remove min-inline-size studioPanel.style.minInlineSize = ''; // Only expand for specific large values if ((minInlineValue.includes('vw') && parseFloat(minInlineValue) >= 30) || (minInlineValue.includes('%') && parseFloat(minInlineValue) >= 40)) { const chatPanel = document.querySelector('section.chat-panel'); if (!studioPanel.classList.contains('panel-collapsed') && chatPanel) { // Add transition for smooth animation studioPanel.style.transition = 'all 0.3s ease'; chatPanel.style.transition = 'all 0.3s ease'; // Apply expanded width studioPanel.style.flex = `0 0 ${CONFIG.expandStudioWidth}%`; studioPanel.style.maxWidth = `calc(${CONFIG.expandStudioWidth}% - ${CONFIG.rightPanelOffset}px)`; studioPanel.style.width = `calc(${CONFIG.expandStudioWidth}% - ${CONFIG.rightPanelOffset}px)`; chatPanel.style.flex = `0 0 ${100 - CONFIG.expandStudioWidth}%`; chatPanel.style.maxWidth = `${100 - CONFIG.expandStudioWidth}%`; // Save the expanded width localStorage.setItem('betterNotebookLM_studioWidth', CONFIG.expandStudioWidth.toString()); // Remove transition after animation setTimeout(() => { studioPanel.style.transition = ''; chatPanel.style.transition = ''; }, 300); console.log(`Better NotebookLM (backup): Detected ${minInlineValue} - expanded to ${CONFIG.expandStudioWidth}%`); } } } }); // Start monitoring after initial setup setTimeout(() => { const studioPanel = document.querySelector('section.studio-panel, [class*="studio"], [class*="right-panel"]'); if (studioPanel) { styleObserver.observe(studioPanel, { attributes: true, attributeFilter: ['style'] }); } }, 2000); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址