Created
May 4, 2023 12:48
-
-
Save pwillia7/db5607f120a68046068f0d8d63af15eb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Copy Code Blocks from Bing Chats | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description A script that adds a copy button to each code block in bing chats | |
// @match https://www.bing.com/* | |
// @grant GM_setClipboard | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
// A function that returns a copyCode function with a reference to the code block element | |
function createCopyCode(codeBlock) { | |
// Return a copyCode function that does the following: | |
return function(e) { | |
// Get the visible text of the code block element passed to the createCopyCode function | |
let text = codeBlock.innerText; | |
// Copy the text to the clipboard | |
GM_setClipboard(text); | |
// Show a confirmation message using a temporary span element | |
let span = document.createElement("span"); | |
span.className = "copy-message"; | |
span.textContent = "Copied!"; | |
span.style.color = "green"; | |
span.style.marginLeft = "5px"; | |
span.style.opacity = "1"; | |
e.target.parentElement.appendChild(span); | |
// Fade away and remove the span element after 3 seconds | |
setTimeout(function() { | |
let fade = setInterval(function() { | |
if (span.style.opacity > 0) { | |
span.style.opacity -= 0.1; | |
} else { | |
clearInterval(fade); | |
span.remove(); | |
} | |
}, 100); | |
}, 3000); | |
}; | |
} | |
// A function that traverses the entire DOM tree and checks for shadow roots and code blocks at each node | |
function traverseDOM(node) { | |
// If the node has a shadowRoot property, call the function recursively on the shadow root node | |
if (node.shadowRoot) { | |
traverseDOM(node.shadowRoot); | |
} | |
// If the node has a querySelectorAll method, use it to get all the code blocks under the node and add copy buttons to them if they don't have one already | |
if (node.querySelectorAll) { | |
let codeBlocks = node.querySelectorAll("pre > code"); | |
for (let codeBlock of codeBlocks) { | |
let copyButton = codeBlock.querySelector(".copy-button"); | |
if (!copyButton) { | |
copyButton = document.createElement("button"); | |
copyButton.className = "copy-button"; | |
copyButton.textContent = "Copy"; | |
copyButton.addEventListener("click", createCopyCode(codeBlock)); | |
try { | |
codeBlock.insertBefore(copyButton, codeBlock.firstChild); | |
} catch (error) { | |
console.error(error); | |
} | |
} | |
} | |
} | |
// If the node has child nodes, loop through them and call the function recursively on each child node | |
if (node.childNodes) { | |
for (let child of node.childNodes) { | |
traverseDOM(child); | |
} | |
} | |
} | |
// A function that runs when the DOM changes and calls the traverseDOM function | |
function onDOMChange(mutations) { | |
// Loop through each mutation record and check if it added or removed any nodes or attributes to the DOM | |
for (let mutation of mutations) { | |
if (mutation.type === "childList" || mutation.type === "attributes") { | |
// Call the traverseDOM function on the target node of the mutation record and any added or removed nodes | |
traverseDOM(mutation.target); | |
for (let addedNode of mutation.addedNodes) { | |
traverseDOM(addedNode); | |
} | |
for (let removedNode of mutation.removedNodes) { | |
traverseDOM(removedNode); | |
} | |
} | |
} | |
} | |
// Create a new mutation observer and pass it the onDOMChange function | |
let observer = new MutationObserver(onDOMChange); | |
// Start observing the entire document for anytype of changes | |
observer.observe(document, {childList: true, attributes: true, subtree: true}); | |
// A function that checks if the cib-feedback element is present in the DOM | |
function isFeedbackReady() { | |
// Use the traverseDOM function to get all the cib-feedback elements from any shadow roots | |
let feedbackElements = []; | |
traverseDOM(document.body, feedbackElements); | |
// Return true if there is at least one cib-feedback element in the DOM, false otherwise | |
return feedbackElements.length > 0; | |
} | |
// A function that adds a copy button to each code block in bing chats | |
function addCopyButtons() { | |
// Get all the code blocks in bing chats using the new selector | |
let codeBlocks = document.querySelectorAll("pre > code"); | |
// Loop through each code block | |
for (let codeBlock of codeBlocks) { | |
// Check if the code block already has a copy button | |
let copyButton = codeBlock.querySelector(".copy-button"); | |
// If not, create a new copy button | |
if (!copyButton) { | |
// Create a new button element | |
copyButton = document.createElement("button"); | |
// Set the class name of the button | |
copyButton.className = "copy-button"; | |
// Set the text content of the button | |
copyButton.textContent = "Copy"; | |
// Add a click event listener to the button | |
copyButton.addEventListener("click", createCopyCode(codeBlock)); | |
// Try to insert the button before the code element | |
try { | |
codeBlock.insertBefore(copyButton, codeBlock.firstChild); | |
} catch (error) { | |
// Catch and log any errors that occur while inserting the button | |
console.error(error); | |
} | |
} | |
} | |
} | |
// A function that runs when the document is ready and calls the addCopyButtons function only if the cib-feedback element is present in the DOM | |
function onDocumentReady() { | |
// Check if the cib-feedback element is present in the DOM using the isFeedbackReady function | |
if (isFeedbackReady()) { | |
// Call the addCopyButtons function to add copy buttons to all code blocks in bing chats | |
addCopyButtons(); | |
} else { | |
// Wait for 100 milliseconds and try again | |
setTimeout(onDocumentReady, 100); | |
} | |
} | |
// Run the onDocumentReady function when the document is ready | |
if (document.readyState === "complete" || document.readyState === "interactive") { | |
onDocumentReady(); | |
} else { | |
document.addEventListener("DOMContentLoaded", onDocumentReady); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment