ChatGPT Copy Without Em Dash

Adds a button to copy ChatGPT responses with em dashes replaced by regular dashes

Size

6.5 KB

Version

1.0.1

Created

Oct 21, 2025

Updated

1 day ago

1// ==UserScript==
2// @name		ChatGPT Copy Without Em Dash
3// @description		Adds a button to copy ChatGPT responses with em dashes replaced by regular dashes
4// @version		1.0.1
5// @match		https://*.chatgpt.com/*
6// @icon		https://cdn.oaistatic.com/assets/favicon-l4nq08hd.svg
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('ChatGPT Copy Without Em Dash extension loaded');
12
13    // Function to create the custom copy button
14    function createCopyButton() {
15        const button = document.createElement('button');
16        button.className = 'text-token-text-secondary hover:bg-token-bg-secondary rounded-lg';
17        button.setAttribute('aria-label', 'Copy without em dash');
18        button.setAttribute('data-state', 'closed');
19        button.title = 'Copy without em dash';
20        
21        button.innerHTML = `
22            <span class="flex items-center justify-center touch:w-10 h-8 w-8">
23                <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-md-heavy">
24                    <path fill-rule="evenodd" clip-rule="evenodd" d="M7 5C7 3.34315 8.34315 2 10 2H19C20.6569 2 22 3.34315 22 5V14C22 15.6569 20.6569 17 19 17H17V19C17 20.6569 15.6569 22 14 22H5C3.34315 22 2 20.6569 2 19V10C2 8.34315 3.34315 7 5 7H7V5ZM9 7H14C15.6569 7 17 8.34315 17 10V15H19C19.5523 15 20 14.5523 20 14V5C20 4.44772 19.5523 4 19 4H10C9.44772 4 9 4.44772 9 5V7ZM5 9C4.44772 9 4 9.44772 4 10V19C4 19.5523 4.44772 20 5 20H14C14.5523 20 15 19.5523 15 19V10C15 9.44772 14.5523 9 14 9H5Z" fill="currentColor"></path>
25                    <line x1="6" y1="12" x2="13" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
26                </svg>
27            </span>
28        `;
29        
30        return button;
31    }
32
33    // Function to copy text without em dashes
34    async function copyWithoutEmDash(messageElement) {
35        try {
36            // Get the text content from the message
37            const textContent = messageElement.textContent || messageElement.innerText;
38            
39            // Replace em dashes (—) with regular hyphens (-)
40            const cleanedText = textContent.replace(//g, '-');
41            
42            // Copy to clipboard
43            await navigator.clipboard.writeText(cleanedText);
44            
45            console.log('Text copied without em dashes:', cleanedText);
46            
47            // Visual feedback
48            return true;
49        } catch (error) {
50            console.error('Failed to copy text:', error);
51            return false;
52        }
53    }
54
55    // Function to add button to a message
56    function addButtonToMessage(article) {
57        // Check if button already exists
58        if (article.querySelector('[aria-label="Copy without em dash"]')) {
59            return;
60        }
61
62        // Find the action buttons container
63        const actionsContainer = article.querySelector('[data-testid="copy-turn-action-button"]')?.parentElement;
64        
65        if (!actionsContainer) {
66            console.log('Actions container not found for message');
67            return;
68        }
69
70        // Find the message content
71        const messageContent = article.querySelector('[data-message-author-role="assistant"] .markdown');
72        
73        if (!messageContent) {
74            console.log('Message content not found');
75            return;
76        }
77
78        // Create and add the button
79        const copyButton = createCopyButton();
80        
81        // Add click handler
82        copyButton.addEventListener('click', async (e) => {
83            e.preventDefault();
84            e.stopPropagation();
85            
86            const success = await copyWithoutEmDash(messageContent);
87            
88            if (success) {
89                // Visual feedback - change button appearance briefly
90                const originalHTML = copyButton.innerHTML;
91                copyButton.innerHTML = `
92                    <span class="flex items-center justify-center touch:w-10 h-8 w-8">
93                        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-md-heavy">
94                            <path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
95                        </svg>
96                    </span>
97                `;
98                
99                setTimeout(() => {
100                    copyButton.innerHTML = originalHTML;
101                }, 1500);
102            }
103        });
104        
105        // Insert the button after the regular copy button
106        const regularCopyButton = actionsContainer.querySelector('[data-testid="copy-turn-action-button"]');
107        if (regularCopyButton) {
108            regularCopyButton.parentNode.insertBefore(copyButton, regularCopyButton.nextSibling);
109            console.log('Copy without em dash button added to message');
110        }
111    }
112
113    // Function to process all messages
114    function processMessages() {
115        const messages = document.querySelectorAll('article[data-testid^="conversation-turn-"]');
116        console.log(`Found ${messages.length} messages to process`);
117        
118        messages.forEach(article => {
119            // Only process assistant messages
120            if (article.querySelector('[data-message-author-role="assistant"]')) {
121                addButtonToMessage(article);
122            }
123        });
124    }
125
126    // Debounce function to avoid excessive processing
127    function debounce(func, wait) {
128        let timeout;
129        return function executedFunction(...args) {
130            const later = () => {
131                clearTimeout(timeout);
132                func(...args);
133            };
134            clearTimeout(timeout);
135            timeout = setTimeout(later, wait);
136        };
137    }
138
139    // Observe DOM changes to add buttons to new messages
140    function observeMessages() {
141        const observer = new MutationObserver(debounce(() => {
142            processMessages();
143        }, 300));
144
145        observer.observe(document.body, {
146            childList: true,
147            subtree: true
148        });
149
150        console.log('MutationObserver started');
151    }
152
153    // Initialize the extension
154    function init() {
155        console.log('Initializing ChatGPT Copy Without Em Dash extension');
156        
157        // Process existing messages
158        processMessages();
159        
160        // Start observing for new messages
161        observeMessages();
162    }
163
164    // Wait for the page to be ready
165    if (document.readyState === 'loading') {
166        document.addEventListener('DOMContentLoaded', init);
167    } else {
168        init();
169    }
170})();
ChatGPT Copy Without Em Dash | Robomonkey