ChatGPT Template Text Buttons

Adds buttons to ChatGPT text inputs to paste predefined templates.

// ==UserScript==
// @name         ChatGPT Template Text Buttons
// @namespace    https://chatgpt.com/
// @version      1.0.1
// @description  Adds buttons to ChatGPT text inputs to paste predefined templates.
// @author       63OR63
// @license      MIT
// @match        https://chatgpt.com/*
// @icon         https://chat.openai.com/favicon.ico
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Button definitions: { button_name: { text: "pasted_text", color: "color_code" } }
    const buttonDefinitions = {
        Refactor: {
            text: `Refactor the following code to improve efficiency or readability without altering its functionality:
\`\`\`
{clipboard}
\`\`\`
Do not explain the code in your response.
`,
            color: "#FFC1CC", // Pastel pink
        },
        Fix: {
            text: `Fix any errors in the following code without changing its core functionality:
\`\`\`
{clipboard}
\`\`\`
Explain the fixes you made by comments in the code's body and give a laconic explanation after.
`,
            color: "#FFDDC1", // Pastel peach
        },
        Explain: {
            text: `Explain the following code concisely:
\`\`\`
{clipboard}
\`\`\`
Focus on key functionality and purpose.
`,
            color: "#FFEBCC", // Pastel yellow
        },
        Review: {
            text: `You are a highly skilled software engineer specializing in code reviews.
Review the following code:
\`\`\`
{clipboard}
\`\`\`
Ensure your feedback is constructive and professional.
Propose improvements with concise explanation.
Reference to guidelines and known best practices where applicable.
`,
            color: "#E6E0FF", // Pastel lavender
        },
        Docs: {
            text: `Generate comprehensive documentation for the following code:
\`\`\`
{clipboard}
\`\`\`
Use the standard documentation format for the provided language. If unsure, use a widely accepted format.
Do not explain the changes in your response.
Do not refactor the code.
`,
            color: "#D8F3DC", // Pastel mint green
        },
        Template: {
            text: `
\`\`\`
{clipboard}
\`\`\`
    `,
            color: "#D1E7FF", // Pastel blue
        },
    };

    // Create buttons for a textarea's parent container
    const createButtonsForTextarea = (textarea) => {
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.flexDirection = 'row';
        buttonContainer.style.gap = '10px'; // gap between buttons
        buttonContainer.style.marginTop = '12px'; // margin below the textarea
        buttonContainer.style.alignItems = 'center';

        Object.entries(buttonDefinitions).forEach(([name, config]) => {
            const button = document.createElement('button');
            button.innerText = name;
            button.type = 'button';
            button.style.backgroundColor = config.color;
            button.style.color = '#333';
            button.style.border = 'none';
            button.style.borderRadius = '5px';
            button.style.padding = '3px 8px';
            button.style.cursor = 'pointer';
            button.style.fontSize = '14px';
            button.style.transition = 'none';

            // Add effect when button is pressed
            button.addEventListener('mousedown', () => {
                button.style.transform = 'scale(0.95)';
            });
            button.addEventListener('mouseup', () => {
                button.style.transform = 'scale(1)';
            });
            button.addEventListener('mouseleave', () => {
                button.style.transform = 'scale(1)';
            });

            // Add click event to paste text
            button.addEventListener('mousedown', async (e) => {
                e.preventDefault(); // Prevent losing focus on the currently focused element
                e.stopPropagation(); // Stop the event from propagating further

                const focusedElement = document.activeElement; // Save the currently focused element
                const promptTextarea = document.getElementById('prompt-textarea');

                // Read clipboard content
                let clipboardText = '';
                try {
                    clipboardText = await navigator.clipboard.readText();
                } catch (err) {
                    console.error("Clipboard access failed:", err);
                    return;
                }

                // Replace the placeholder with clipboard content
                const finalText = config.text.replace("{clipboard}", clipboardText);

                if (promptTextarea === focusedElement) {
                    // Paste into the focused "prompt-textarea"
                    const lines = finalText.split(/\r?\n/); // Split finalText into lines by newlines
                    lines.forEach(line => {
                        const paragraph = document.createElement('p'); // Create a new <p> element
                        paragraph.textContent = line; // Set the text content of the <p> element to the line
                        promptTextarea.appendChild(paragraph); // Append the <p> element to promptTextarea
                    });

                } else if (focusedElement && focusedElement.tagName === 'TEXTAREA') {
                    // Use setRangeText to ensure persistence and proper event firing
                    const cursorPosition = focusedElement.selectionStart || focusedElement.value.length;
                    focusedElement.setRangeText(finalText, cursorPosition, cursorPosition, "end");

                    // Trigger events to simulate manual entry
                    focusedElement.dispatchEvent(new Event('input', { bubbles: true }));
                }
            });

            buttonContainer.appendChild(button);
        });

        return buttonContainer;
    };

    // Attach buttons to all relevant textareas
    const attachButtons = () => {
        const main = document.querySelector('main');
        if (!main) return;

        const textareas = main.querySelectorAll('textarea');
        textareas.forEach((textarea) => {
            const parent = textarea.parentElement;
            if (!parent.querySelector('.button-container')) {
                const buttonContainer = createButtonsForTextarea(textarea);
                buttonContainer.classList.add('button-container');
                parent.appendChild(buttonContainer);
            }
        });
    };

    // Observe the DOM for dynamically added textareas
    const observer = new MutationObserver(() => {
        attachButtons();
    });

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

    // Initial attachment
    attachButtons();
})();