Claude Style Selector v1.0.1

← Back to User Scripts

Description for Style Selector v1.0.1.

Script Content

// ==UserScript==
// @name         Claude Style Selector
// @namespace    https://github.com/tjhleeds/user-scripts/
// @version      1.0.1
// @description  Display and select Claude conversation styles from a sidebar
// @author       tjhleeds using Claude
// @match        https://claude.ai/
// @match        https://claude.ai/new
// @match        https://claude.ai/chat/*
// @match        https://claude.ai/project/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const OBSERVER_TIMEOUT_MS = 10000;
    const ELEMENT_READY_DELAY_MS = 50;
    let styles = [];
    let sidebarElement = null;

    /**
     * Wait for an element to appear in the DOM using MutationObserver
     * @param {Function} checkFn - Function that returns the element when found, or null/undefined
     * @param {number} timeoutMs - Timeout in milliseconds
     * @returns {Promise} Resolves with the element, rejects on timeout
     */
    function waitForElement(checkFn, timeoutMs = OBSERVER_TIMEOUT_MS) {
        return new Promise((resolve, reject) => {
            // Check if element already exists
            const element = checkFn();
            if (element) {
                // Add delay before resolving to ensure element is fully rendered
                setTimeout(() => resolve(element), ELEMENT_READY_DELAY_MS);
                return;
            }

            let timeoutId;
            const observer = new MutationObserver(() => {
                const element = checkFn();
                if (element) {
                    clearTimeout(timeoutId);
                    observer.disconnect();
                    // Add delay before resolving to ensure element is fully rendered
                    setTimeout(() => resolve(element), ELEMENT_READY_DELAY_MS);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            timeoutId = setTimeout(() => {
                observer.disconnect();
                reject('Timeout waiting for element');
            }, timeoutMs);
        });
    }

    function getCurrentlySelectedStyle() {
        // Check which style button is currently pressed
        const buttons = document.querySelectorAll('button[aria-pressed="true"]');
        for (const button of buttons) {
            const svgPath = button.querySelector('svg path[d*="M15.5117"]');
            if (svgPath) {
                // This is a pressed style button - find the style name
                const p = button.querySelector('p');
                return p ? p.textContent.trim() : null;
            }
        }
        return null;
    }

    async function selectStyle(styleName) {
        try {
            // Open the tools menu
            const openToolsMenuButton = document.querySelector('[aria-label="Open tools menu"]');
            if (!openToolsMenuButton) {
                throw new Error('Tools menu button not found');
            }
            openToolsMenuButton.click();

            // Wait for style button to appear AND be visible
            const styleButton = await waitForElement(() => {
                const buttons = document.querySelectorAll('button');
                for (const button of buttons) {
                    const svgPath = button.querySelector('svg path[d*="M15.5117"]');
                    if (svgPath && button.offsetParent !== null) {
                        return button;
                    }
                }
                return null;
            });

            styleButton.click();

            // Wait for the specific style to appear in the submenu and be visible
            const foundButton = await waitForElement(() => {
                const paragraphs = document.querySelectorAll('p');
                for (const p of paragraphs) {
                    if (p.textContent.trim() === styleName) {
                        const button = p.closest('button');
                        if (button && button.offsetParent !== null) return button;
                    }
                }
                return null;
            });

            foundButton.click();
            console.log('Claude Style Selector: Selected style:', styleName);

            // Wait for prompt textarea to be available and click it
            await waitForElement(() => document.querySelector('[aria-label="Write your prompt to Claude"]'), 2000);
            const promptTextarea = document.querySelector('[aria-label="Write your prompt to Claude"]');
            if (promptTextarea) {
                promptTextarea.click();
            }
        } catch (error) {
            const openToolsMenuButton = document.querySelector('[aria-label="Open tools menu"]');
            if (openToolsMenuButton) {
                openToolsMenuButton.click(); // Try to close menu on error
            }
            throw error;
        }
    }

    function updateSidebarSelection() {
        if (!sidebarElement) return;

        const currentStyle = getCurrentlySelectedStyle();
        const listItems = sidebarElement.querySelectorAll('li');

        for (const item of listItems) {
            const styleName = item.getAttribute('data-style-name');
            if (styleName === currentStyle) {
                item.style.color = '#fff';
                item.style.fontWeight = '600';
            } else {
                item.style.color = '#ccc';
                item.style.fontWeight = '400';
            }
        }
    }

    async function discoverStyles() {
        try {
            // Open the tools menu
            const openToolsMenuButton = document.querySelector('[aria-label="Open tools menu"]');
            if (!openToolsMenuButton) {
                throw new Error('Tools menu button not found');
            }
            console.log('Claude Style Selector: Opening tools menu');
            openToolsMenuButton.click();

            // Wait for style button to appear AND be visible (check offsetParent)
            console.log('Claude Style Selector: Waiting for style button to be visible');
            const styleButton = await waitForElement(() => {
                const buttons = document.querySelectorAll('button');
                for (const button of buttons) {
                    const svgPath = button.querySelector('svg path[d*="M15.5117"]');
                    // Check if button exists and is visible (has offsetParent)
                    if (svgPath && button.offsetParent !== null) {
                        console.log('Claude Style Selector: Found visible style button');
                        return button;
                    }
                }
                return null;
            });

            console.log('Claude Style Selector: Clicking style button');
            styleButton.click();

            // Wait for styles submenu to appear with visible style options
            console.log('Claude Style Selector: Waiting for style options to appear');
            await waitForElement(() => {
                const paragraphs = document.querySelectorAll('p');
                let visibleCount = 0;
                for (const p of paragraphs) {
                    const button = p.closest('button');
                    // Check if button exists, has text, and is visible
                    if (button && p.textContent.trim() && button.offsetParent !== null) {
                        visibleCount++;
                        if (visibleCount >= 2) {
                            console.log('Claude Style Selector: Found visible style options');
                            return true; // At least 2 visible style options
                        }
                    }
                }
                return null;
            });

            // Extract all style names from 

elements in buttons const paragraphs = document.querySelectorAll('p'); const discoveredStyles = []; for (const p of paragraphs) { const text = p.textContent.trim(); const button = p.closest('button'); // Only include if it's in a button (likely a style option) if (button && text && !discoveredStyles.includes(text)) { discoveredStyles.push(text); } } // Close the menu by clicking the tools button again openToolsMenuButton.click(); console.log('Claude Style Selector: Discovered styles:', discoveredStyles); return discoveredStyles; } catch (error) { throw error; } } function createSidebar() { const sidebar = document.createElement('div'); sidebar.id = 'claude-style-selector-sidebar'; sidebar.style.cssText = ` position: fixed; top: 0; right: 0; width: 200px; height: 100vh; background: #1a1a1a; border-left: 1px solid #333; padding: 20px; box-sizing: border-box; z-index: 1000; overflow-y: auto; font-family: system-ui, -apple-system, sans-serif; `; const title = document.createElement('h3'); title.textContent = 'Styles'; title.style.cssText = ` color: #fff; font-size: 16px; margin: 0 0 15px 0; font-weight: 600; `; sidebar.appendChild(title); const styleList = document.createElement('ul'); styleList.style.cssText = ` list-style: none; padding: 0; margin: 0; `; for (const styleName of styles) { const listItem = document.createElement('li'); listItem.setAttribute('data-style-name', styleName); listItem.style.cssText = ` color: #ccc; padding: 8px 0; font-size: 14px; cursor: pointer; transition: color 0.2s; `; listItem.textContent = styleName; // Add click handler listItem.addEventListener('click', async () => { try { await selectStyle(styleName); updateSidebarSelection(); } catch (error) { console.error('Claude Style Selector: Failed to select style:', error); } }); // Add hover effect listItem.addEventListener('mouseenter', () => { if (listItem.style.color === 'rgb(255, 255, 255)') return; // Don't change if selected listItem.style.color = '#fff'; }); listItem.addEventListener('mouseleave', () => { if (listItem.style.fontWeight === '600') return; // Don't change if selected listItem.style.color = '#ccc'; }); styleList.appendChild(listItem); } sidebar.appendChild(styleList); document.body.appendChild(sidebar); sidebarElement = sidebar; // Push main content left to make room for sidebar const mainContent = document.querySelector('body > div:first-child'); if (mainContent) { mainContent.style.marginRight = '200px'; } // Set initial selection state updateSidebarSelection(); console.log('Claude Style Selector: Sidebar created with', styles.length, 'styles'); } async function initialize() { try { styles = await discoverStyles(); createSidebar(); } catch (error) { console.error('Claude Style Selector: Failed to initialize:', error); } } async function waitForChatInterface() { try { await waitForElement(() => document.querySelector('[aria-label="Send message"]')); console.log('Claude Style Selector: Chat interface loaded.'); await initialize(); } catch (error) { console.error('Claude Style Selector: Chat interface not found:', error); } } // Start checking for the chat interface waitForChatInterface(); })();