ChatGPT Template Text Buttons

Adds buttons to ChatGPT text inputs to paste predefined templates.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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