Claude Sidebar Quick Access

Adds starred and recent chat buttons to Claude's minimized sidebar

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Claude Sidebar Quick Access
// @namespace    https://greasyfork.org/en/scripts/575025-claude-sidebar-quick-access
// @version      3.6
// @description  Adds starred and recent chat buttons to Claude's minimized sidebar
// @author       Wolf68k
// @match        https://claude.ai/*
// @grant        none
// @license      CC BY-SA 4.0
// ==/UserScript==

(function () {
    'use strict';

    let starButton = null;
    let recentButton = null;
    let popup = null;
    let observer = null;

    function getStarredChats() {
        const headings = document.querySelectorAll('h2');
        for (const h2 of headings) {
            if (h2.textContent.trim().toLowerCase().startsWith('starred')) {
                const ul = h2.nextElementSibling;
                if (ul && ul.tagName === 'UL') {
                    const links = ul.querySelectorAll('a[data-dd-action-name="sidebar-chat-item"]');
                    const results = [];
                    links.forEach(link => {
                        const span = link.querySelector('span');
                        const label = span ? span.textContent.trim() : link.textContent.trim();
                        const href = link.getAttribute('href');
                        if (label && href && !results.find(r => r.href === href)) {
                            results.push({ label, href });
                        }
                    });
                    return results;
                }
            }
        }
        return [];
    }

    function getRecentChats() {
        const headings = document.querySelectorAll('h2');
        for (const h2 of headings) {
            if (h2.textContent.trim().toLowerCase().startsWith('recents')) {
                const ul = h2.parentElement && h2.parentElement.nextElementSibling;
                if (ul && ul.tagName === 'UL') {
                    const links = ul.querySelectorAll('a[data-dd-action-name="sidebar-chat-item"]');
                    const results = [];
                    links.forEach(link => {
                        const span = link.querySelector('span');
                        const label = span ? span.textContent.trim() : link.textContent.trim();
                        const href = link.getAttribute('href');
                        if (label && href && !results.find(r => r.href === href)) {
                            results.push({ label, href });
                        }
                    });
                    return results;
                }
            }
        }
        return [];
    }

    function showPopup(type, anchorButton) {
        if (popup) {
            popup.remove();
            popup = null;
            return;
        }

        const items = type === 'starred' ? getStarredChats() : getRecentChats();
        const btnRect = anchorButton.getBoundingClientRect();
        const label = type === 'starred' ? 'Starred Chats' : 'Recent Chats';
        const emptyMsg = type === 'starred' ? 'No starred chats found' : 'No recent chats found';

        popup = document.createElement('div');
        popup.id = 'claude-chat-popup';
        popup.style.cssText = `
            position: fixed;
            top: ${btnRect.top}px;
            left: ${btnRect.right + 8}px;
            background: #1e1e2e;
            border: 1px solid #444;
            border-radius: 8px;
            padding: 8px 0;
            z-index: 99999;
            min-width: 220px;
            max-width: 300px;
            max-height: 400px;
            overflow-y: auto;
            box-shadow: 0 4px 20px rgba(0,0,0,0.4);
            font-family: sans-serif;
            font-size: 13px;
        `;

        if (items.length === 0) {
            const empty = document.createElement('div');
            empty.textContent = emptyMsg;
            empty.style.cssText = 'padding: 10px 16px; color: #888;';
            popup.appendChild(empty);
        } else {
            const title = document.createElement('div');
            title.textContent = label;
            title.style.cssText = 'padding: 6px 16px 8px; color: #ddd; font-size: 15px; font-weight: 700; border-bottom: 1px solid #333; margin-bottom: 4px;';
            popup.appendChild(title);

            items.forEach(chat => {
                const item = document.createElement('a');
                item.href = chat.href;
                item.textContent = chat.label;
                item.style.cssText = `
                    display: block;
                    padding: 8px 16px;
                    color: #ddd;
                    text-decoration: none;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                    cursor: pointer;
                `;
                item.addEventListener('mouseenter', () => item.style.background = '#2a2a3e');
                item.addEventListener('mouseleave', () => item.style.background = 'transparent');
                item.addEventListener('click', () => {
                    popup.remove();
                    popup = null;
                });
                popup.appendChild(item);
            });
        }

        document.body.appendChild(popup);

        setTimeout(() => {
            document.addEventListener('click', function closePopup(e) {
                if (!popup) return;
                if (!popup.contains(e.target) && e.target !== starButton && e.target !== recentButton) {
                    popup.remove();
                    popup = null;
                    document.removeEventListener('click', closePopup);
                }
            });
        }, 0);
    }

    function findInsertionPoint() {
        const navLinks = document.querySelectorAll('a[data-dd-action-name="sidebar-nav-item"]');
        if (!navLinks.length) return null;

        // Find the last nav link inside the gap-px container
        // Some items are 2 levels deep (a -> relative group -> gap-px)
        // Others are 3 levels deep (a -> relative group -> relative -> gap-px)
        for (let i = navLinks.length - 1; i >= 0; i--) {
            const link = navLinks[i];
            let el = link;
            for (let depth = 0; depth < 5; depth++) {
                el = el.parentElement;
                if (!el) break;
                if (el.classList.contains('gap-px')) {
                    // lastGroup is the direct child of container that wraps this nav item
                    const directChild = el.children[Array.from(el.children).findIndex(c => c.contains(link))];
                    return { container: el, lastGroup: directChild || el.lastElementChild };
                }
            }
        }
        return null;
    }

    function makeButton(id, symbol, tooltipText, type) {
        const btn = document.createElement('button');
        btn.id = id;
        btn.title = tooltipText;
        btn.innerHTML = symbol;
        btn.style.cssText = `
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: start;
            gap: 12px;
            width: 100%;
            padding: 6px 8px;
            background: transparent;
            border: none;
            border-radius: 6px;
            color: #ffffff;
            font-size: 15px;
            cursor: pointer;
            text-align: left;
            transition: background 0.15s;
        `;
        btn.addEventListener('mouseenter', () => btn.style.background = 'rgba(255,255,255,0.06)');
        btn.addEventListener('mouseleave', () => btn.style.background = 'transparent');
        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            showPopup(type, btn);
        });
        return btn;
    }

    function addButtons() {
        if (document.getElementById('claude-starred-wrapper')) return;

        const result = findInsertionPoint();
        if (!result) return;

        const { container, lastGroup } = result;

        const starWrapper = document.createElement('div');
        starWrapper.id = 'claude-starred-wrapper';
        starWrapper.title = '';
        starWrapper.style.cssText = 'display: block; width: 100%;';
        starButton = makeButton('claude-starred-btn', '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" xmlns="http://www.w3.org/2000/svg" style="flex-shrink: 0;" aria-hidden="true"><path d="M10 2l2.39 4.84 5.34.78-3.87 3.77.91 5.32L10 14.27l-4.77 2.44.91-5.32L2.27 7.62l5.34-.78z" stroke-linejoin="round"/></svg>', 'Starred Chats', 'starred');
        starWrapper.appendChild(starButton);

        const recentWrapper = document.createElement('div');
        recentWrapper.id = 'claude-recent-wrapper';
        recentWrapper.title = '';
        recentWrapper.style.cssText = 'display: block; width: 100%;';
        recentButton = makeButton('claude-recent-btn', '<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" style="flex-shrink: 0;" aria-hidden="true"><path d="M4.5 4.5 A6.5 6.5 0 1 1 2.8 12.5" fill="none"/><polygon points="1.5,4.5 6,4.5 3.8,8.5" fill="currentColor" stroke="none" transform="rotate(25, 3.8, 6)"/><line x1="8.5" y1="10" x2="8.5" y2="6.5"/><line x1="8.5" y1="10" x2="10.8" y2="11.5"/></svg>', 'Recent Chats', 'recent');
        recentWrapper.appendChild(recentButton);

        lastGroup.after(starWrapper);
        starWrapper.after(recentWrapper);
    }

    function init() {
        addButtons();

        if (!observer) {
            observer = new MutationObserver(() => {
                if (!document.getElementById('claude-starred-wrapper')) {
                    addButtons();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 1000);
    }

})();