[doc] feat: add resizable sidebar and improve layout (#2577)

## Summary
This PR adds a resizable sidebar feature and improves the documentation
layout for better user experience.

  ## Changes
- **Resizable sidebar**: Users can drag to resize the sidebar, with
preference saved in localStorage
- **Full-width layout**: Documentation now uses full screen width for
better readability
- **Responsive design**: Better layout adaptation for different screen
sizes
- **Navigation improvements**: Attempts to improve table of contents
navigation behavior

  ## Features
  - Drag handle on sidebar for resizing
  - Double-click to reset sidebar to default width
  - localStorage persistence for user preferences
  - Improved CSS for better visual experience

  ## Technical Details
  - Added `_static/custom.css` for styling improvements
  - Added `_static/js/resizable-sidebar.js` for functionality
  - Updated `conf.py` to include new CSS and JS files

  ## Testing
Tested on the documentation build with successful functionality for
sidebar resizing and layout improvements.
This commit is contained in:
Tingberer
2025-07-25 05:46:38 +08:00
committed by GitHub
parent 1df03f3abf
commit bcd336fd46
3 changed files with 474 additions and 0 deletions

217
docs/_static/custom.css vendored Normal file
View File

@ -0,0 +1,217 @@
/* Make the documentation use full screen width */
.wy-nav-content {
max-width: none !important;
width: 100% !important;
padding: 1.618em 3.236em !important;
}
/* Adjust the content wrapper - will be set by JavaScript */
.wy-nav-content-wrap {
margin-left: 300px;
transition: margin-left 0.2s ease;
width: auto !important;
position: relative !important;
background: white !important;
min-height: 100vh !important;
}
/* Make the main content area responsive */
.rst-content {
max-width: none !important;
width: 100% !important;
}
/* Optional: Adjust table widths to prevent overflow */
.rst-content table.docutils {
width: 100% !important;
table-layout: auto !important;
}
/* Optional: Better code block width handling */
.rst-content .highlight {
width: 100% !important;
}
/* Content area positioning already handled above */
/* Optional: Improve readability with some margin on very wide screens */
@media (min-width: 1400px) {
.wy-nav-content {
max-width: none !important;
margin: 0 auto !important;
}
}
/* Resizable sidebar styles */
.wy-nav-side {
position: fixed !important;
top: 0 !important;
bottom: 0 !important;
left: 0 !important;
width: 300px;
min-width: 200px;
max-width: 600px;
display: flex;
flex-direction: column;
z-index: 200 !important;
}
/* Ensure sidebar header (logo, search) adapts to width */
.wy-side-nav-search {
width: 100% !important;
box-sizing: border-box !important;
padding: 0.809em 0.809em !important;
}
.wy-side-nav-search input[type="text"] {
width: 100% !important;
box-sizing: border-box !important;
}
/* Make logo/title area responsive */
.wy-side-nav-search > div.version {
width: 100% !important;
}
.wy-side-nav-search > a {
width: 100% !important;
display: block !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* Responsive adjustments for narrow sidebar */
@media (max-width: 300px) {
.wy-side-nav-search > a {
font-size: 0.9em !important;
}
.wy-side-nav-search input[type="text"] {
font-size: 0.8em !important;
}
}
/* Ensure search input doesn't overflow */
.wy-side-nav-search form {
width: 100% !important;
margin: 0 !important;
}
/* Make search icon responsive */
.wy-side-nav-search .wy-dropdown {
width: 100% !important;
}
/* Adjust search results dropdown width */
.wy-side-nav-search .wy-dropdown-menu {
width: 100% !important;
max-width: none !important;
left: 0 !important;
right: 0 !important;
}
/* Resize handle is created by JavaScript */
/* Make sure the sidebar content doesn't overflow */
.wy-side-scroll {
width: 100% !important;
flex: 1 !important;
overflow-y: auto !important;
overflow-x: hidden !important;
padding-right: 10px !important;
box-sizing: border-box !important;
scroll-behavior: auto !important; /* Prevent smooth scrolling on sidebar itself */
}
/* Ensure proper scroll behavior for main content area */
html {
scroll-behavior: smooth !important;
}
/* Ensure anchor links work properly in main content */
.wy-nav-content-wrap {
scroll-behavior: smooth !important;
}
/* Fix scroll to target for anchor links */
.rst-content {
scroll-behavior: smooth !important;
}
/* Fix anchor scroll offset to account for fixed header */
.rst-content .section {
scroll-margin-top: 60px;
}
/* Fix anchor scroll offset for headers */
.rst-content h1, .rst-content h2, .rst-content h3, .rst-content h4, .rst-content h5, .rst-content h6 {
scroll-margin-top: 60px;
}
/* Fix anchor scroll offset for specific scroll targets */
.rst-content .headerlink {
scroll-margin-top: 60px;
}
/* Fix sidebar navigation styling */
.wy-menu-vertical {
width: 100% !important;
}
.wy-menu-vertical li {
width: 100% !important;
}
.wy-menu-vertical a {
width: 100% !important;
word-wrap: break-word !important;
white-space: normal !important;
}
/* Content area margin is handled by JavaScript */
/* Custom drag handle (more visible) */
.resize-handle {
position: absolute;
top: 0;
right: 0;
width: 8px;
height: 100%;
background: linear-gradient(to right, transparent, #ccc, transparent);
cursor: col-resize;
z-index: 1001;
opacity: 0.3;
transition: opacity 0.2s ease;
}
.resize-handle:hover {
opacity: 0.8;
background: linear-gradient(to right, transparent, #999, transparent);
}
.resize-handle::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 2px;
height: 20px;
background: #666;
transform: translate(-50%, -50%);
border-radius: 1px;
}
.resize-handle:hover::before {
background: #333;
}
/* Ensure smooth resizing */
.wy-nav-side.resizing {
user-select: none;
pointer-events: none;
}
.wy-nav-side.resizing .wy-side-scroll {
overflow: hidden;
}

251
docs/_static/js/resizable-sidebar.js vendored Normal file
View File

@ -0,0 +1,251 @@
// Resizable sidebar functionality
document.addEventListener('DOMContentLoaded', function() {
const sidebar = document.querySelector('.wy-nav-side');
const content = document.querySelector('.wy-nav-content-wrap');
if (!sidebar || !content) return;
// Create resize handle
const resizeHandle = document.createElement('div');
resizeHandle.className = 'resize-handle';
sidebar.appendChild(resizeHandle);
let isResizing = false;
let startX = 0;
let startWidth = 0;
// Get initial width
const getInitialWidth = () => {
return 300; // Default width
};
// Save width to localStorage
const saveWidth = (width) => {
localStorage.setItem('sidebar-width', width);
};
// Load width from localStorage
const loadWidth = () => {
const savedWidth = localStorage.getItem('sidebar-width');
if (savedWidth) {
const width = parseInt(savedWidth, 10);
if (width >= 200 && width <= 600) {
return width;
}
}
return getInitialWidth();
};
// Apply width to sidebar and content
const applyWidth = (width) => {
// Update sidebar width
sidebar.style.width = width + 'px';
// Update content margin with !important to override any CSS
content.style.setProperty('margin-left', width + 'px', 'important');
// Also update any other content wrapper that might exist
const contentInner = document.querySelector('.wy-nav-content');
if (contentInner) {
contentInner.style.setProperty('margin-left', '0px', 'important');
}
// Force reflow and repaint
sidebar.offsetHeight;
content.offsetHeight;
// Trigger window resize event to notify other components
window.dispatchEvent(new Event('resize'));
};
// Initialize with saved width
const initialWidth = loadWidth();
applyWidth(initialWidth);
// Mouse down on resize handle
resizeHandle.addEventListener('mousedown', (e) => {
isResizing = true;
startX = e.clientX;
startWidth = parseInt(window.getComputedStyle(sidebar).width, 10);
sidebar.classList.add('resizing');
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
// Add overlay to prevent iframe issues
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
cursor: col-resize;
`;
overlay.id = 'resize-overlay';
document.body.appendChild(overlay);
e.preventDefault();
});
// Mouse move
document.addEventListener('mousemove', (e) => {
if (!isResizing) return;
const width = startWidth + e.clientX - startX;
const clampedWidth = Math.max(200, Math.min(600, width));
applyWidth(clampedWidth);
});
// Mouse up
document.addEventListener('mouseup', () => {
if (!isResizing) return;
isResizing = false;
sidebar.classList.remove('resizing');
document.body.style.cursor = '';
document.body.style.userSelect = '';
// Remove overlay
const overlay = document.getElementById('resize-overlay');
if (overlay) {
overlay.remove();
}
// Save the current width
const currentWidth = parseInt(window.getComputedStyle(sidebar).width, 10);
saveWidth(currentWidth);
});
// Handle window resize - removed to prevent infinite loop
// The sidebar width is fixed and managed by drag functionality, no need to recalculate on window resize
// Double-click to reset to default width
resizeHandle.addEventListener('dblclick', () => {
const defaultWidth = 300;
applyWidth(defaultWidth);
saveWidth(defaultWidth);
});
});
// Fix navigation issues - Using MutationObserver for reliable initialization
document.addEventListener('DOMContentLoaded', function() {
let navigationFixed = false;
function setupNavigationFix() {
if (navigationFixed) return;
// Find all links in the sidebar
const sidebarLinks = document.querySelectorAll('.wy-menu-vertical a');
// Only proceed if we have sidebar links
if (sidebarLinks.length === 0) return;
console.log('Setting up navigation fix...');
sidebarLinks.forEach(function(link) {
const href = link.getAttribute('href');
// Clone the link to remove all existing event listeners
const newLink = link.cloneNode(true);
// Add our own click handler
newLink.addEventListener('click', function(e) {
console.log('Link clicked:', href);
// If it's an anchor link within the same page
if (href && href.startsWith('#') && href !== '#') {
e.preventDefault();
e.stopPropagation();
const targetId = href.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Calculate offset for fixed header
const headerHeight = 60;
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerHeight;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
// Update URL hash
if (history.pushState) {
history.pushState(null, null, '#' + targetId);
} else {
location.hash = '#' + targetId;
}
}
}
// For external links, navigate normally
else if (href && !href.startsWith('#') && !href.startsWith('javascript:')) {
console.log('Navigating to external link:', href);
window.location.href = href;
}
});
// Replace the old link with the new one
link.parentNode.replaceChild(newLink, link);
});
navigationFixed = true;
// Handle initial page load with hash
if (window.location.hash) {
// Use requestAnimationFrame for better timing
requestAnimationFrame(() => {
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
const headerHeight = 60;
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - headerHeight;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth'
});
}
});
}
}
// Try to set up navigation fix immediately
setupNavigationFix();
// If it didn't work, use MutationObserver to watch for when sidebar links are added
if (!navigationFixed) {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// Check if sidebar links were added
const sidebarLinks = document.querySelectorAll('.wy-menu-vertical a');
if (sidebarLinks.length > 0) {
setupNavigationFix();
if (navigationFixed) {
observer.disconnect();
}
}
}
});
});
// Start observing the document for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
// Fallback timeout in case MutationObserver doesn't work
setTimeout(function() {
if (!navigationFixed) {
setupNavigationFix();
}
observer.disconnect();
}, 5000);
}
});

View File

@ -93,6 +93,12 @@ html_static_path = ["_static"]
# Add the JavaScript file
html_js_files = [
"js/runllm-widget.js",
"js/resizable-sidebar.js",
]
# Add custom CSS file for full-width layout
html_css_files = [
"custom.css",
]
exclude_patterns += ["README.md", "README_vllm0.7.md"]