Greasy Fork

Greasy Fork is available in English.

Perplexity Code Block Copy (AFU IT)

Enhanced code blocks in Perplexity with better selection and copy features for inline code

// ==UserScript==
// @name         Perplexity Code Block Copy (AFU IT)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Enhanced code blocks in Perplexity with better selection and copy features for inline code
// @author       AFU IT
// @match        https://www.perplexity.ai/*
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Custom CSS to maintain grey text color during selection with smaller padding
    const customCSS = `
        code.enhanced-code::selection {
            background-color: rgba(255, 255, 255, 0.3) !important;
            color: grey !important;
            padding-top: 0.5px;
            padding-bottom: 0.5px;
        }
        code.enhanced-code::-moz-selection {
            background-color: rgba(255, 255, 255, 0.3) !important;
            color: grey !important;
            padding-top: 0.5px;
            padding-bottom: 0.5px;
        }
    `;

    // Add custom CSS to the document
    function addCustomCSS() {
        if (!document.getElementById('perplexity-code-enhancer-css')) {
            const style = document.createElement('style');
            style.id = 'perplexity-code-enhancer-css';
            style.textContent = customCSS;
            document.head.appendChild(style);
        }
    }

    // Function to check if answer is still generating
    function isAnswerGenerating() {
        // Look for elements that indicate answer generation is in progress
        return document.querySelector('.answer-loading, .generating, [data-generating="true"], .typing') !== null;
    }

    // Variables to manage tooltip timer and last copied selection
    let tooltipTimer = null;
    let lastCopiedSelection = '';
    let generatingBlocks = new Set();

    // Function to apply styles and add copy functionality
    function enhanceCodeBlocks() {
        const generating = isAnswerGenerating();

        // Find all code blocks that haven't been enhanced yet
        const codeBlocks = document.querySelectorAll('code:not(.enhanced-code):not(.temp-styled):not(.enhanced-code-default)');

        codeBlocks.forEach(codeBlock => {
            // If answer is still generating, mark this block but don't style it yet
            if (generating) {
                codeBlock.classList.add('temp-styled');
                generatingBlocks.add(codeBlock);
                addCopyFunctionality(codeBlock);
                return;
            }

            // Answer is complete, apply full styling
            applyFinalStyling(codeBlock);
        });

        // If generation has completed, process any blocks that were marked during generation
        if (!generating && generatingBlocks.size > 0) {
            generatingBlocks.forEach(block => {
                block.classList.remove('temp-styled');
                applyFinalStyling(block);
            });
            generatingBlocks.clear();
        }
    }

    // Function to apply final styling to a code block after generation is complete
    function applyFinalStyling(codeBlock) {
        // Check if this is an inline code block (within a paragraph)
        const isInlineCode = !codeBlock.parentElement.tagName.toLowerCase().includes('pre') &&
                             codeBlock.textContent.split('\n').length === 1;

        // Apply styling for inline code blocks
        if (isInlineCode) {
            codeBlock.style.backgroundColor = '#20b8cb';
            codeBlock.style.color = 'black';
            codeBlock.classList.add('enhanced-code'); // Add class for selection styling
        } else {
            // Count the number of lines in the code block for multi-line blocks
            const lineCount = (codeBlock.textContent.match(/\n/g) || []).length + 1;

            // Keep default styling for all code blocks
            codeBlock.classList.add('enhanced-code-default');
        }

        // Add copy functionality if not already added
        if (!codeBlock.dataset.copyEnabled) {
            addCopyFunctionality(codeBlock);
        }
    }

    // Function to add copy functionality to a code block
    function addCopyFunctionality(codeBlock) {
        // Skip if already processed
        if (codeBlock.dataset.copyEnabled === 'true') return;

        // Common styling regardless of line count
        codeBlock.style.position = 'relative';

        // Add a subtle hover effect
        codeBlock.addEventListener('mouseover', function() {
            this.style.opacity = '0.9';
        });

        codeBlock.addEventListener('mouseout', function() {
            this.style.opacity = '1';
        });

        // Add click event to copy code for inline code
        codeBlock.addEventListener('click', function(e) {
            // Check if this is an inline code and no text is selected
            const selection = window.getSelection();
            const selectedText = selection.toString();

            // If this is an inline code and no specific selection, copy the whole inline code
            if (this.classList.contains('enhanced-code') && (!selectedText || selectedText.length === 0)) {
                const codeText = this.textContent;
                navigator.clipboard.writeText(codeText).then(() => {
                    showCopiedTooltip(this, "Copied!", e.clientX, e.clientY);
                });
                e.preventDefault(); // Prevent default to avoid text selection
                return;
            }
        });

        // Add mouseup event to copy selected text
        codeBlock.addEventListener('mouseup', function(e) {
            // Get selected text
            const selection = window.getSelection();
            const selectedText = selection.toString();

            // If text is selected and different from last copied, copy it
            if (selectedText && selectedText.length > 0 && selectedText !== lastCopiedSelection) {
                lastCopiedSelection = selectedText;
                navigator.clipboard.writeText(selectedText).then(() => {
                    showCopiedTooltip(this, "Selection copied!", e.clientX, e.clientY);
                    // Clear any existing timer
                    if (tooltipTimer) {
                        clearTimeout(tooltipTimer);
                    }
                    // Set timer to remove tooltip
                    tooltipTimer = setTimeout(() => {
                        const existingTooltip = document.querySelector('.code-copied-tooltip');
                        if (existingTooltip) {
                            existingTooltip.style.opacity = '0';
                            setTimeout(() => {
                                existingTooltip.remove();
                            }, 500);
                        }
                        tooltipTimer = null;
                        lastCopiedSelection = '';
                    }, 1500);
                });
            }
        });

        // Add double click event to copy entire code
        codeBlock.addEventListener('dblclick', function(e) {
            e.preventDefault();
            const codeText = this.textContent;
            navigator.clipboard.writeText(codeText).then(() => {
                showCopiedTooltip(this, "All code copied!", e.clientX, e.clientY);
                // Clear any existing timer
                if (tooltipTimer) {
                    clearTimeout(tooltipTimer);
                }
                // Set timer to remove tooltip
                tooltipTimer = setTimeout(() => {
                    const existingTooltip = document.querySelector('.code-copied-tooltip');
                    if (existingTooltip) {
                        existingTooltip.style.opacity = '0';
                        setTimeout(() => {
                            existingTooltip.remove();
                        }, 500);
                    }
                    tooltipTimer = null;
                    lastCopiedSelection = '';
                }, 1500);
            });
        });

        // Mark as processed
        codeBlock.dataset.copyEnabled = 'true';
    }

    // Function to show a temporary tooltip close to the mouse cursor
    function showCopiedTooltip(element, message, x, y) {
        // Remove any existing tooltips
        const existingTooltip = document.querySelector('.code-copied-tooltip');
        if (existingTooltip) {
            existingTooltip.remove();
        }

        // Create tooltip
        const tooltip = document.createElement('div');
        tooltip.textContent = message || 'Copied!';
        tooltip.className = 'code-copied-tooltip';

        // Style the tooltip - using Perplexity's font family
        tooltip.style.position = 'fixed';
        tooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        tooltip.style.color = 'white';
        tooltip.style.padding = '4px 8px';
        tooltip.style.borderRadius = '4px';
        tooltip.style.fontSize = '12px'; // Smaller font size
        tooltip.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif'; // Perplexity default font
        tooltip.style.zIndex = '10000';
        tooltip.style.pointerEvents = 'none';

        // Add to document and get dimensions
        document.body.appendChild(tooltip);

        // Position tooltip very close to the right of the mouse cursor
        const offsetX = 20; // pixels to the right (closer now)

        // Use clientX/Y instead of pageX/Y for better positioning
        tooltip.style.left = (x + offsetX) + 'px';

        // Calculate vertical position to center the tooltip to the mouse
        // We need to wait for the tooltip to be in the DOM to get its height
        setTimeout(() => {
            const tooltipHeight = tooltip.offsetHeight;
            tooltip.style.top = (y - (tooltipHeight / 4.5)) + 'px';
        }, 0);
    }

    // Add the custom CSS
    addCustomCSS();

    // Run initially
    enhanceCodeBlocks();

    // Set up a MutationObserver to handle dynamically loaded content
    const observer = new MutationObserver(function(mutations) {
        enhanceCodeBlocks();
    });

    // Start observing the document body for changes
    observer.observe(document.body, { childList: true, subtree: true });

    // Also run periodically to catch when answer generation completes
    setInterval(enhanceCodeBlocks, 500);
})();