mirror of
https://github.com/volcengine/verl.git
synced 2025-10-20 13:43:50 +08:00
[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:
217
docs/_static/custom.css
vendored
Normal file
217
docs/_static/custom.css
vendored
Normal 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
251
docs/_static/js/resizable-sidebar.js
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
@ -93,6 +93,12 @@ html_static_path = ["_static"]
|
|||||||
# Add the JavaScript file
|
# Add the JavaScript file
|
||||||
html_js_files = [
|
html_js_files = [
|
||||||
"js/runllm-widget.js",
|
"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"]
|
exclude_patterns += ["README.md", "README_vllm0.7.md"]
|
||||||
|
Reference in New Issue
Block a user