Bouton d'Exportation de Code ChatGPT

Ajoute un bouton d'exportation aux blocs de code dans les réponses de ChatGPT, invite l'utilisateur à enregistrer le code sous forme de fichier avec un nom prédéfini basé sur le langage de programmation détecté à partir du nom de classe du bloc de code.

// ==UserScript==
// @name              ChatGPT Code Export Button
// @name:en           ChatGPT Code Export Button
// @name:zh-CN        ChatGPT 代码导出按钮
// @name:es           Botón de Exportación de Código de ChatGPT
// @name:hi           चैटजीपीटी कोड निर्यात बटन
// @name:fr           Bouton d'Exportation de Code ChatGPT
// @name:ar           زر تصدير الكود في ChatGPT
// @name:bn           চ্যাটজিপিটি কোড রপ্তানি বাটন
// @name:ru           Кнопка экспорта кода ChatGPT
// @name:pt           Botão de Exportação de Código do ChatGPT
// @name:ur           چیٹ جی پی ٹی کوڈ برآمد بٹن
// @namespace         http://tampermonkey.net/
// @version           2024/07/11
// @license           MIT
// @description       Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.
// @description:en    Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.
// @description:zh-CN 为 ChatGPT 响应中的代码块添加导出按钮,提示用户根据代码块的类名检测到的编程语言将代码保存为文件。
// @description:es    Agrega un botón de exportación a los bloques de código en las respuestas de ChatGPT, solicitando al usuario que guarde el código como archivo con un nombre predefinido según el lenguaje de programación detectado del nombre de la clase del bloque de código.
// @description:hi    चैटजीपीटी प्रतिक्रियाओं में कोड ब्लॉक्स में निर्यात बटन जोड़ता है, उपयोगकर्ता को कोड को फ़ाइल के रूप में सहेजने के लिए प्रीडिफ़ाइन्ड फ़ाइलनाम पर प्रोम्प्ट करता है जोड़ता है। कोड ब्लॉक के क्लास नाम से डिटेक्ट किया गया।
// @description:fr    Ajoute un bouton d'exportation aux blocs de code dans les réponses de ChatGPT, invite l'utilisateur à enregistrer le code sous forme de fichier avec un nom prédéfini basé sur le langage de programmation détecté à partir du nom de classe du bloc de code.
// @description:ar    يضيف زر التصدير إلى كتل الكود في ردود ChatGPT ، ويطلب من المستخدم حفظ الكود كملف باسم محدد مسبقًا بناءً على لغة البرمجة المكتشفة من اسم الفئة لكتلة الكود.
// @description:bn    ChatGPT রেসপন্সে কোড ব্লকের জন্য রপ্তানি বাটন যোগ করে, ব্যবহারকারীকে কোডটি ক্লাস নাম থেকে ডিটেক্ট করে পূর্বনির্ধারিত ফাইলনেমের মধ্যে সংরক্ষণ করতে বলে।
// @description:ru    Добавляет кнопку экспорта в блоки кода в ответах ChatGPT, предлагает пользователю сохранить код в файл с предопределенным именем на основе обнаруженного языка программирования из имени класса блока кода.
// @description:pt    Adiciona um botão de exportação aos blocos de código nas respostas do ChatGPT, solicitando ao usuário que salve o código como arquivo com um nome predefinido com base na linguagem de programação detectada a partir do nome da classe do bloco de código.
// @description:ur    چیٹ جی پی ٹی ردعملات میں کوڈ بلاکس میں برآمد بٹن شامل کرتا ہے، صارف کو کوڈ کو فائل کے طور پر محفوظ کرنے کے لیے پہلے سے تعین شدہ فائل نام پر پرومپٹ کرتا ہے جو کوڈ بلاک کے کلاس کے نام سے دریافت کیا گیا ہے
// @author       Muffin & Arcadie
// @match        https://chatgpt.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Function to add "Export" button to existing code headers
    function addExportButtonToHeaders() {
        const codeHeaders = document.querySelectorAll('.flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md');

        codeHeaders.forEach(header => {
            // Check if "Export" button is already added
            if (header.querySelector('.export-button')) {
                return; // Skip if already added
            }

            // Find the language label
            const languageLabel = header.querySelector('span');

            // Create the "Export" button
            const exportButton = document.createElement('button');
            exportButton.classList.add('export-button', 'flex', 'items-center', 'gap-1', 'text-xs', 'p-1', 'rounded');
            exportButton.setAttribute('title', 'Export Code');

            // Create the SVG icon
            const exportIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            exportIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            exportIcon.setAttribute('viewBox', '0 0 24 24');
            exportIcon.setAttribute('fill', 'none');
            exportIcon.setAttribute('stroke', 'currentColor');
            exportIcon.setAttribute('stroke-width', '2');
            exportIcon.setAttribute('stroke-linecap', 'round');
            exportIcon.setAttribute('stroke-linejoin', 'round');
            exportIcon.classList.add('h-4', 'w-4');

            const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
            polyline.setAttribute('points', '4 17 10 11 4 5');
            exportIcon.appendChild(polyline);

            const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
            line.setAttribute('x1', '12');
            line.setAttribute('y1', '19');
            line.setAttribute('x2', '19');
            line.setAttribute('y2', '12');
            exportIcon.appendChild(line);

            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
            path.setAttribute('d', 'M4 17h4v4H4z');
            exportIcon.appendChild(path);

            // Append the SVG icon to the button
            exportButton.appendChild(exportIcon);

            // Add the button text
            const buttonText = document.createElement('span');
            buttonText.textContent = 'Export';
            exportButton.appendChild(buttonText);

            // Create the tooltip
            const tooltip = document.createElement('div');
            tooltip.classList.add('tooltip', 'flex', 'items-center', 'gap-1', 'text-xs', 'p-1', 'rounded', 'absolute', 'bg-black', 'text-white', 'hidden');
            tooltip.style.top = '110%';
            tooltip.style.left = '50%';
            tooltip.style.transform = 'translateX(-50%)';
            tooltip.style.whiteSpace = 'nowrap';

            // Create the tooltip text
            const tooltipText = document.createElement('span');
            tooltipText.textContent = 'Export Code';
            tooltip.appendChild(tooltipText);

            // Append the tooltip to the button
            exportButton.appendChild(tooltip);

            // Find the element containing "Copy code"
            let copyCodeDiv = null;
            const elements = header.querySelectorAll('.flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md div');
            elements.forEach(element => {
                if (element.innerHTML.includes('Copy code')) {
                    copyCodeDiv = element;
                }
            });

            // Insert "Export" button before the found element or append to header if not found
            if (copyCodeDiv) {
                copyCodeDiv.parentNode.insertBefore(exportButton, copyCodeDiv);
            } else {
                header.appendChild(exportButton);
            }



            // Add hover event listener for the "Export" button to show tooltip
            exportButton.addEventListener('mouseenter', () => {
                tooltip.classList.remove('hidden');
            });

            // Add hover event listener for the "Export" button to hide tooltip
            exportButton.addEventListener('mouseleave', () => {
                tooltip.classList.add('hidden');
            });

            // Add click event listener for the "Export" button
            exportButton.addEventListener('click', () => {
                const codeBlock = header.parentElement.querySelector('pre code'); // Assuming structure, adjust as needed
                const language = languageLabel.textContent.trim().toLowerCase(); // Extract language
                exportCode(codeBlock, language); // Call export function
            });
        });
    }

    // Function to open File Explorer for saving the code as file
    async function exportCode(codeBlock, language) {
        let fileName;
        let fileExtension;
        let mimeType;

        // Determine filename, extension, and MIME type based on language
        switch (language) {
            case 'javascript':
            case 'js':
                fileName = 'script';
                fileExtension = '.js';
                mimeType = 'application/javascript';
                break;
            case 'html':
                fileName = 'index';
                fileExtension = '.html';
                mimeType = 'text/html';
                break;
            case 'css':
                fileName = 'styles';
                fileExtension = '.css';
                mimeType = 'text/css';
                break;
            case 'python':
            case 'py':
                fileName = 'main';
                fileExtension = '.py';
                mimeType = 'text/x-python';
                break;
            case 'json':
            case 'json':
                fileName = 'manifest';
                fileExtension = '.json';
                mimeType = 'text/json';
                break;
            default:
                fileName = 'code';
                fileExtension = '.txt';
                mimeType = 'text/plain';
                break;
        }

        // Create a Blob object with the code content
        const blob = new Blob([codeBlock.innerText], { type: mimeType });

        try {
            if (window.showSaveFilePicker) {
                // Use File System Access API if available
                const fileHandle = await window.showSaveFilePicker({
                    suggestedName: fileName + fileExtension,
                    types: [
                        {
                            description: language,
                            accept: {
                                [mimeType]: [fileExtension],
                            },
                        },
                    ],
                });

                const writable = await fileHandle.createWritable();
                await writable.write(blob);
                await writable.close();
            } else {
                // Fallback for browsers that do not support showSaveFilePicker
                const downloadLink = document.createElement('a');
                downloadLink.href = URL.createObjectURL(blob);
                downloadLink.download = fileName + fileExtension;
                downloadLink.style.display = 'none';
                document.body.appendChild(downloadLink);
                downloadLink.click();
                URL.revokeObjectURL(downloadLink.href);
                document.body.removeChild(downloadLink);
            }
        } catch (error) {
            console.error('Save file dialog was canceled or failed', error);
        }
    }

    // Observe the document for changes and add "Export" button to new code headers
    const observer = new MutationObserver(addExportButtonToHeaders);
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial processing of existing code headers
    addExportButtonToHeaders();

    // Add custom CSS styles if needed
    const style = document.createElement('style');
    style.textContent = `
        .export-button {
            position: relative;
            background: none;
            border: none;
            cursor: pointer;
        }

        .export-button .tooltip {
            z-index: 10;
        }

        .export-button:hover .tooltip {
            display: flex;
        }
    `;
    document.head.appendChild(style);
})();