Perplexity Power Shortcuts

Keyboard Shortcuts for Perplexity.ai: GSAP-powered scrolling, edit message, focus input, set sources (Academic/Social/GitHub), Search, Research.

נכון ליום 28-10-2025. ראה הגרסה האחרונה.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Perplexity Power Shortcuts
// @namespace    http://tampermonkey.net/
// @version      1.1.0
// @description  Keyboard Shortcuts for Perplexity.ai: GSAP-powered scrolling, edit message, focus input, set sources (Academic/Social/GitHub), Search, Research.
// @author       Brian Hurd
// @match        https://perplexity.ai/*
// @match        https://www.perplexity.ai/*
// @grant        none
// @run-at       document-end
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/ScrollToPlugin.min.js
// ==/UserScript==

// Alt+t         → Scroll to top of main thread
// Alt+z         → Scroll to bottom of main thread
// Alt+a         → Scroll up one message block
// Alt+f         → Scroll down one message block
// Alt+e         → Edit lowest user message (clicks [data-testid="edit-query-button"]; simulates hover if needed; falls back to Search if none found)
// Alt+Shift+s   → Click Search mode (SVG d^="M11 2.125a8.378 8.378")
// Alt+r         → Click Research mode (SVG d^="M8.175 13.976a.876.876")
// Alt+w         → Focus chat input (#ask-input contenteditable)
// Alt+p         → Set Sources → Academic (First: Set Sources SVG d^="M3 12a9 9 0 1 0", then submenu data-testid="source-toggle-scholar")
// Alt+s         → Set Sources → Social (submenu data-testid="source-toggle-social")
// Alt+g         → Set Sources → GitHub (submenu by testid if present or text "GitHub")
// Alt+n         → Start new conversation

(function () {
    'use strict';

    // Helper: get scrollable container
    function getScrollableContainer() {
        return document.querySelector('.scrollable-container.scrollbar-subtle.flex.flex-1.basis-0.overflow-auto');
    }
    function scrollToTop() {
        const c = getScrollableContainer();
        if (c) c.scrollTop = 0;
    }
    function scrollToBottom() {
        const c = getScrollableContainer();
        if (c) c.scrollTop = c.scrollHeight;
    }

    // Scroll up/down one message
    function getMessageBlocks() {
        // Find blocks with edit button descendant, robust for Perplexity
        const blocks = [];
        document.querySelectorAll('button[data-testid="edit-query-button"]').forEach(btn => {
            let block = btn.closest('.mb-xs.group.relative.flex.items-end');
            if (!block) {
                let el = btn.parentElement;
                for (let i = 0; i < 8 && el; i++) {
                    if (el.classList.contains('mb-xs') && el.classList.contains('group')) break;
                    el = el.parentElement;
                }
                block = el;
            }
            if (block && !blocks.includes(block)) blocks.push(block);
        });
        return blocks;
    }
    function scrollUpOneMessage() {
        const sc = getScrollableContainer();
        if (!sc) return;
        const blocks = getMessageBlocks();
        if (!blocks.length) return;
        const y = sc.scrollTop;
        let targetTop = 0;
        for (let i = blocks.length - 1; i >= 0; i--) {
            const top = blocks[i].offsetTop;
            if (top < y - 10) {
                targetTop = top;
                break;
            }
        }
        sc.scrollTop = targetTop;
    }
    function scrollDownOneMessage() {
        const sc = getScrollableContainer();
        if (!sc) return;
        const blocks = getMessageBlocks();
        if (!blocks.length) return;
        const y = sc.scrollTop;
        let targetTop = sc.scrollHeight;
        for (let i = 0; i < blocks.length; i++) {
            const top = blocks[i].offsetTop;
            if (top > y + 10) {
                targetTop = top;
                break;
            }
        }
        sc.scrollTop = targetTop;
    }

    // Edit lowest user message
    function isVisible(el) {
        if (!el) return false;
        const rect = el.getBoundingClientRect();
        return !!(rect.width && rect.height && rect.bottom > 0 && rect.right > 0 &&
                  rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
                  rect.left < (window.innerWidth || document.documentElement.clientWidth));
    }
    function simulateHover(el) {
        if (!el) return;
        el.dispatchEvent(new PointerEvent('pointerenter', { bubbles: true }));
        el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
        el.dispatchEvent(new PointerEvent('mouseover', { bubbles: true }));
    }
    function editLowestMessage() {
        const btns = Array.from(document.querySelectorAll('button[data-testid="edit-query-button"]'));
        if (!btns.length) return false;
        let target = null;
        let maxBottom = -Infinity;
        for (const btn of btns) {
            const rect = btn.getBoundingClientRect();
            if (isVisible(btn) && rect.bottom > maxBottom) {
                target = btn;
                maxBottom = rect.bottom;
            }
        }
        if (!target) target = btns[btns.length - 1];
        if (!target) return false;
        const group = target.closest('.mb-xs.group.relative.flex.items-end');
        if (group) simulateHover(group);
        setTimeout(() => { target.click(); }, 120);
        return true;
    }

    // Focus chat input
    function focusChatInput() {
        const el = document.querySelector('#ask-input[contenteditable="true"]');
        if (!el) return false;
        el.focus();
        const range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        const sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
        return true;
    }

    // Search/Research mode by SVG path (segment control)
    function clickSegmentedControlBySVGPath(pathPrefix) {
        const segButtons = Array.from(document.querySelectorAll('button[role="radio"]'));
        for (const btn of segButtons) {
            const svg = btn.querySelector('svg');
            if (svg) {
                const path = svg.querySelector('path');
                if (path && path.getAttribute('d') && path.getAttribute('d').startsWith(pathPrefix)) {
                    if (btn.getAttribute('aria-checked') !== "true") {
                        btn.click();
                        return true;
                    }
                }
            }
        }
        return false;
    }

    // Sources menu helpers
    function openSourcesMenuAndClickSubmenuByTestId(testid) {
        const sourcesBtn = document.querySelector('button[data-testid="sources-switcher-button"]');
        if (!sourcesBtn) return false;
        sourcesBtn.click();
        setTimeout(() => {
            const menu = document.querySelector('div[role="menu"], ul[role="menu"]');
            if (menu) {
                const item = menu.querySelector(`[data-testid="${testid}"]`);
                if (item) item.click();
            }
        }, 250);
        return true;
    }
    function openSourcesMenuAndClickSubmenuByText(text) {
        const sourcesBtn = document.querySelector('button[data-testid="sources-switcher-button"]');
        if (!sourcesBtn) return false;
        sourcesBtn.click();
        setTimeout(() => {
            const menu = document.querySelector('div[role="menu"], ul[role="menu"]');
            if (menu) {
                const items = Array.from(menu.querySelectorAll('[role="menuitem"],button,[data-testid]'));
                const target = items.find(n => (n.innerText || n.textContent || '').toLowerCase().includes(text.toLowerCase()));
                if (target) target.click();
            }
        }, 250);
        return true;
    }

    // Keyboard handler with new mapping!
    document.addEventListener('keydown', function (e) {
        if (!e.altKey || e.repeat) return;
        // Alt+Shift+S = Search mode
        if (e.shiftKey && e.key.toLowerCase() === 's') {
            clickSegmentedControlBySVGPath('M11 2.125a8.378');
            e.preventDefault(); return;
        }
        // Alt+P = Sources → Academic
        if (!e.shiftKey && e.key.toLowerCase() === 'p') {
            openSourcesMenuAndClickSubmenuByTestId('source-toggle-scholar');
            e.preventDefault(); return;
        }
        // Alt+S = Sources → Social (not mode)
        if (!e.shiftKey && e.key.toLowerCase() === 's') {
            openSourcesMenuAndClickSubmenuByTestId('source-toggle-social');
            e.preventDefault(); return;
        }
        // Alt+G = Sources → Github
        if (!e.shiftKey && e.key.toLowerCase() === 'g') {
            // Try by testid, fallback to text
            if (!openSourcesMenuAndClickSubmenuByTestId('source-toggle-github')) {
                openSourcesMenuAndClickSubmenuByText('github');
            }
            e.preventDefault(); return;
        }
        // Alt+R = Research mode
        if (!e.shiftKey && e.key.toLowerCase() === 'r') {
            clickSegmentedControlBySVGPath('M8.175 13.976a.876.876');
            e.preventDefault(); return;
        }
        // Alt+E = Edit lowest message (with fallback)
        if (!e.shiftKey && e.key.toLowerCase() === 'e') {
            if (!editLowestMessage()) {
                // Fallback: activate Search mode
                clickSegmentedControlBySVGPath('M11 2.125a8.378');
            }
            e.preventDefault(); return;
        }
        // Scrolling/etc
        switch (e.key.toLowerCase()) {
            case 't': scrollToTop(); e.preventDefault(); break;
            case 'z': scrollToBottom(); e.preventDefault(); break;
            case 'a': scrollUpOneMessage(); e.preventDefault(); break;
            case 'f': scrollDownOneMessage(); e.preventDefault(); break;
            case 'w': focusChatInput(); e.preventDefault(); break;
        }
    }, true);

})();


// alt+n starts new conversation

(function() {
    document.addEventListener('keydown', function(e) {
        if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.repeat && e.key.toLowerCase() === 'n') {
            const btn = document.querySelector('button[data-testid="sidebar-new-thread"]');
            if (btn) btn.click();
        }
    }, true);
})();