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.
Από την
// ==UserScript==
// @name Chat GPT Code Export Button
// @namespace http://tampermonkey.net/
// @version 2024/07/03
// @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.
// @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.innerText = 'Export';
exportButton.classList.add('export-button'); // Add a class for styling
exportButton.style.padding = '8px 16px';
exportButton.style.marginRight = '8px'; // Adjust spacing if necessary
exportButton.style.border = 'none';
exportButton.style.borderRadius = '4px';
exportButton.style.backgroundColor = '#e3e3e3';
exportButton.style.color = '#333';
exportButton.style.cursor = 'pointer';
exportButton.style.transition = 'background-color 0.3s';
// Insert "Export" button after the language label
languageLabel.parentNode.insertBefore(exportButton, languageLabel.nextSibling);
// 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;
default:
// If language cannot be determined from <span>, fallback to provided language
switch (language.toLowerCase()) {
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 'java':
fileName = 'Main';
fileExtension = '.java';
mimeType = 'text/x-java-source';
break;
case 'kotlin':
fileName = 'Main';
fileExtension = '.kt';
mimeType = 'text/x-kotlin';
break;
case 'c++':
case 'cpp':
fileName = 'main';
fileExtension = '.cpp';
mimeType = 'text/x-c++src';
break;
case 'c#':
case 'csharp':
fileName = 'Program';
fileExtension = '.cs';
mimeType = 'text/x-csharp';
break;
case 'c':
fileName = 'main';
fileExtension = '.c';
mimeType = 'text/x-csrc';
break;
case 'ruby':
fileName = 'script';
fileExtension = '.rb';
mimeType = 'text/x-ruby';
break;
case 'rust':
fileName = 'main';
fileExtension = '.rs';
mimeType = 'text/x-rustsrc';
break;
case 'php':
fileName = 'script';
fileExtension = '.php';
mimeType = 'text/x-php';
break;
case 'swift':
fileName = 'main';
fileExtension = '.swift';
mimeType = 'text/x-swift';
break;
case 'typescript':
case 'ts':
fileName = 'script';
fileExtension = '.ts';
mimeType = 'application/typescript';
break;
case 'go':
fileName = 'main';
fileExtension = '.go';
mimeType = 'text/x-go';
break;
case 'perl':
fileName = 'script';
fileExtension = '.pl';
mimeType = 'text/x-perl';
break;
case 'lua':
fileName = 'script';
fileExtension = '.lua';
mimeType = 'text/x-lua';
break;
default:
fileName = 'code';
fileExtension = '.txt';
mimeType = 'text/plain';
break;
}
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
})();