Skip to content

Instantly share code, notes, and snippets.

@pwillia7
Created May 4, 2023 12:48
Show Gist options
  • Save pwillia7/db5607f120a68046068f0d8d63af15eb to your computer and use it in GitHub Desktop.
Save pwillia7/db5607f120a68046068f0d8d63af15eb to your computer and use it in GitHub Desktop.
// ==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