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

08.05.2025 itibariyledir. En son verisyonu görün.

// ==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);
})();