Google Forms - Prompt Copier

Adds a button to quickly copy question prompts from Google Forms, differentiating between question types.

// ==UserScript==
// @name         Google Forms - Prompt Copier
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Adds a button to quickly copy question prompts from Google Forms, differentiating between question types.
// @author       Nyxiereal
// @match        https://docs.google.com/forms/d/e/*/viewform*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @license      GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // Add styles for our custom button
    GM_addStyle(`
        .gform-copy-button {
            margin-left: 12px;
            padding: 2px 8px;
            font-size: 12px;
            font-weight: bold;
            color: #fff;
            background-color: #1a73e8; /* Google's primary button blue */
            border: none;
            border-radius: 4px;
            cursor: pointer;
            vertical-align: middle; /* Align with the question text */
            transition: background-color 0.2s;
        }

        .gform-copy-button:hover {
            background-color: #287ae6;
        }

        .gform-copy-button:disabled {
            background-color: #8ab4f8; /* Lighter blue for disabled state */
            cursor: default;
        }
    `);

    /**
     * Handles the click event on a "Copy Prompt" button.
     * It determines the question type, formats the text, and copies it to the clipboard.
     * @param {MouseEvent} event - The click event.
     */
    function handleCopyClick(event) {
        event.preventDefault();
        const button = event.target;
        // Find the root container for the question this button belongs to.
        // 'div[jsmodel]' is a stable identifier for a question block.
        const questionBlock = button.closest('div[jsmodel]');
        if (!questionBlock) return;

        // Find the question title element
        const questionTitleEl = questionBlock.querySelector('div[role="heading"] > span:first-child');
        const questionTitle = questionTitleEl ? questionTitleEl.textContent.trim() : 'Question not found';

        let promptText = '';
        let questionType = 'Unknown Type';
        let options = [];

        // 1. Check for Radio Buttons (Single Choice)
        const radioOptions = questionBlock.querySelectorAll('div[role="radio"]');
        if (radioOptions.length > 0) {
            questionType = 'Single Choice';
            radioOptions.forEach(radio => {
                // 'aria-label' is a reliable, accessibility-focused attribute for the option text.
                const label = radio.getAttribute('aria-label');
                if (label) options.push(`- ${label}`);
            });
        }
        // 2. If not radio, check for Checkboxes (Multi Choice)
        else {
            const checkOptions = questionBlock.querySelectorAll('div[role="checkbox"]');
            if (checkOptions.length > 0) {
                questionType = 'Multi Choice (Select all that apply)';
                checkOptions.forEach(check => {
                    // 'aria-label' or 'data-answer-value' can hold the text.
                    const label = check.getAttribute('aria-label') || check.getAttribute('data-answer-value');
                    if (label) options.push(`- ${label}`);
                });
            }
            // 3. If not radio or checkbox, check for a text input area (Free Text)
            else {
                // Short answers use <input type="text">, long answers use <textarea>.
                // Both have a role of 'textbox'.
                const textInput = questionBlock.querySelector('div[role="textbox"], textarea, input[type="text"]');
                if (textInput) {
                    questionType = 'Free Text';
                    // No options for free text questions.
                }
            }
        }

        // Build the final string to be copied
        promptText = `[${questionType}]\nQuestion: ${questionTitle}`;
        if (options.length > 0) {
            promptText += '\n\nOptions:\n' + options.join('\n');
        }

        // ADDED: The instruction text to be appended to the prompt.
        const instructionText = "Reply with the correct answer and a quick reasoning why it is the correct answer. Don't over-complicate stuff.";

        // MODIFIED: Append the new instruction text to the promptText string.
        promptText += `\n\n${instructionText}`;

        // Copy the generated prompt to the clipboard
        GM_setClipboard(promptText);

        // Provide visual feedback to the user
        const originalText = button.textContent;
        button.textContent = 'Copied!';
        button.disabled = true;
        setTimeout(() => {
            button.textContent = originalText;
            button.disabled = false;
        }, 1500);
    }

    /**
     * Scans the document for question blocks and adds a "Copy Prompt" button if one doesn't already exist.
     * This function is designed to be run multiple times safely.
     */
    function addCopyButtons() {
        // A 'div[jsmodel]' that is a direct child of a 'div[role="listitem"]' is a very reliable
        // selector for a question block.
        const questionBlocks = document.querySelectorAll('div[role="listitem"] > div[jsmodel]');

        questionBlocks.forEach(block => {
            // Use a data attribute to mark blocks we've already processed to avoid adding duplicate buttons.
            if (block.dataset.copyButtonAdded) return;
            block.dataset.copyButtonAdded = 'true';

            // Find the heading area where the question title lives.
            const headingContainer = block.querySelector('div[role="heading"]');
            if (!headingContainer) return; // Not a standard question block (e.g., a description block)

            const button = document.createElement('button');
            button.textContent = 'Copy';
            button.className = 'gform-copy-button';
            button.onclick = handleCopyClick;

            // Add the button right next to the question title.
            headingContainer.appendChild(button);
        });
    }

    // --- Main Execution ---
    // Google Forms can load content dynamically, especially in multi-page forms.
    // A MutationObserver is the most reliable way to detect when new questions are added to the DOM.
    const observer = new MutationObserver((mutations) => {
        // We don't need to inspect the mutations, just re-run our function
        // to find any new question blocks that need a button.
        addCopyButtons();
    });

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

    // Run it once on initial load as well.
    addCopyButtons();
})();