Skip to content

Instantly share code, notes, and snippets.

@tuanchauict
Created April 28, 2025 03:11
Show Gist options
  • Save tuanchauict/b312713953d0d7b9a8111dc3cdaa1340 to your computer and use it in GitHub Desktop.
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
// ==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);
})();
@tuanchauict
Copy link
Author

image

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