Created
April 28, 2025 03:11
-
-
Save tuanchauict/b312713953d0d7b9a8111dc3cdaa1340 to your computer and use it in GitHub Desktop.
A short tampermonkey script for adding a Nav Bar for Claude.ai that helps navigate through conversation messages
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
// ==UserScript== | |
// @name Claude.ai Navigation Bar | |
// @namespace http://tampermonkey.net/ | |
// @version 1.0 | |
// @description Creates a navigation bar on the right side of Claude.ai that helps navigate through conversation messages | |
// @author You | |
// @match https://claude.ai/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// Add CSS styles | |
const style = document.createElement('style'); | |
style.textContent = ` | |
/* Navigation bar container */ | |
.custom-nav-container { | |
position: fixed; | |
right: 10px; | |
top: 50px; | |
bottom: 20px; | |
display: flex; | |
flex-direction: column; | |
gap: 10px; | |
z-index: 9999; /* High z-index to stay on top */ | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
padding: 8px; | |
border-radius: 10px; | |
background-color: rgba(255, 255, 255, 0.1); | |
} | |
/* Show navigation on hover */ | |
.custom-nav-container:hover { | |
opacity: 1 !important; | |
} | |
/* Individual navigation dot */ | |
.custom-nav-dot { | |
width: 20px; | |
height: 6px; | |
border-radius: 6px; | |
background-color: #DBDBDB; | |
cursor: pointer; | |
transition: transform 0.2s ease, background-color 0.2s ease; | |
} | |
/* Hover effect for dots */ | |
.custom-nav-dot:hover { | |
transform: scale(1.3); | |
} | |
/* Active dot style */ | |
.custom-nav-dot.active { | |
background-color: #C9B194; | |
} | |
`; | |
document.head.appendChild(style); | |
/** | |
* Create a navigation bar based on the number of sections | |
* @param {number} numSections - Number of sections to create navigation for | |
* @param {Function} callback - Function to call when a navigation dot is clicked | |
*/ | |
function createNavBar(numSections, callback) { | |
// Create navigation container if it doesn't exist | |
let navContainer = document.getElementById('custom-nav-container'); | |
if (!navContainer) { | |
navContainer = document.createElement('div'); | |
navContainer.id = 'custom-nav-container'; | |
navContainer.className = 'custom-nav-container'; | |
document.body.appendChild(navContainer); | |
} else { | |
// Clear existing navigation dots | |
navContainer.innerHTML = ''; | |
} | |
// Define sections - automatic or custom logic based on your need | |
const sections = []; | |
// Option 1: Use existing sections if they can be identified | |
// Example: Get all h1, h2 elements as section markers | |
const potentialSections = document.querySelectorAll('.mb-1.mt-1 .flex.shrink-0'); | |
if (potentialSections.length === 0) { | |
return; | |
} | |
// Use the potential sections found in the document | |
const usedSections = potentialSections.length > numSections | |
? Array.from(potentialSections).slice(0, numSections) | |
: potentialSections; | |
usedSections.forEach((section, i) => { | |
const rect = section.getBoundingClientRect(); | |
sections.push({ | |
element: section, | |
top: rect.top + window.scrollY, | |
bottom: rect.bottom + window.scrollY, | |
index: i | |
}); | |
}); | |
// Create navigation dots | |
sections.forEach((section, i) => { | |
const navDot = document.createElement('div'); | |
navDot.className = 'custom-nav-dot'; | |
navDot.dataset.index = i; | |
navDot.onclick = function() { | |
// Call the callback function with index | |
if (typeof callback === 'function') { | |
callback(i); | |
} | |
// Scroll to the section | |
if (section.element) { | |
section.element.parentElement.parentElement.parentElement.parentElement.scrollIntoView({ | |
behavior: 'smooth' | |
}); | |
} else { | |
// Scroll to position if no element | |
window.scrollTo({ | |
top: section.top, | |
behavior: 'smooth' | |
}); | |
} | |
// Update active state | |
updateActiveNavDot(i); | |
}; | |
navContainer.appendChild(navDot); | |
}); | |
// Set the first dot as active initially | |
if (sections.length > 0) { | |
updateActiveNavDot(0); | |
} | |
// Add scroll event listener to update active nav dot | |
window.addEventListener('scroll', function() { | |
const scrollPosition = window.scrollY; | |
const windowHeight = window.innerHeight; | |
const middle = scrollPosition + (windowHeight / 2); | |
// Find which section contains the middle of the viewport | |
for (let i = 0; i < sections.length; i++) { | |
const section = sections[i]; | |
if (middle >= section.top && middle < section.bottom) { | |
updateActiveNavDot(i); | |
break; | |
} | |
} | |
}); | |
return { | |
navContainer, | |
sections, | |
// Method to update the opacity | |
setOpacity: function(opacity) { | |
navContainer.style.opacity = opacity; | |
} | |
}; | |
} | |
/** | |
* Update the active navigation dot | |
* @param {number} activeIndex - Index of the active section | |
*/ | |
function updateActiveNavDot(activeIndex) { | |
const navDots = document.querySelectorAll('.custom-nav-dot'); | |
navDots.forEach((dot, index) => { | |
if (index === activeIndex) { | |
dot.classList.add('active'); | |
} else { | |
dot.classList.remove('active'); | |
} | |
}); | |
} | |
// Create a global API | |
window.RightNavBar = { | |
create: createNavBar, | |
updateActive: updateActiveNavDot | |
}; | |
/** | |
* Initialize the navigation bar | |
* @param {number} numSections - Number of sections to create navigation for | |
* @param {Function} callback - Function to call when a navigation dot is clicked | |
*/ | |
function initNavBar(numSections, callback) { | |
// Default callback if none provided | |
if (!callback) { | |
callback = function(index) { | |
console.log(`Navigation item ${index + 1} clicked!`); | |
}; | |
} | |
// Create the navigation bar | |
const navBar = window.RightNavBar.create(numSections, callback); | |
// Optional: You can customize the initial opacity if needed | |
// navBar.setOpacity(0.2); // Make it slightly visible by default | |
return navBar; | |
} | |
// Track the section count to detect changes | |
let sectionCount = 0; | |
// Initialize and update the navigation bar periodically | |
setInterval(() => { | |
const messages = document.querySelectorAll('.mb-1.mt-1 .flex.shrink-0'); | |
if (messages.length != sectionCount) { | |
sectionCount = messages.length; | |
initNavBar(sectionCount, (index) => { | |
// You can add custom callback functionality here | |
}); | |
} | |
}, 1000); | |
})(); |
Author
tuanchauict
commented
Apr 28, 2025

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment