// ==UserScript== // @name GitHub Copy Code Snippet // @version 0.3.10 // @description A userscript adds a copy to clipboard button on hover of markdown code snippets // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://gist.github.com/* // @run-at document-idle // @grant GM_addStyle // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=1108163 // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-copy-code-snippet.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/GitHub-userscripts/master/github-copy-code-snippet.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (() => { "use strict"; let copyId = 0; const markdownSelector = ".markdown-body, .markdown-format", codeSelector = "pre:not(.gh-csc-pre)", copyButton = document.createElement("clipboard-copy"); copyButton.className = "btn btn-sm tooltipped tooltipped-w gh-csc-button"; copyButton.setAttribute("aria-label", "Copy to clipboard"); // This hint isn't working yet (GitHub needs to fix it) copyButton.setAttribute("data-copied-hint", "Copied!"); copyButton.innerHTML = ` `; GM_addStyle(` .gh-csc-wrap { position: relative; } .gh-csc-wrap:hover .gh-csc-button { display: block; } .gh-csc-button { display: none; padding: 3px 6px; position: absolute; top: 3px; right: 3px; z-index: 20; } .gh-csc-wrap.ghd-code-wrapper .gh-csc-button { right: 31px; } .gh-csc-button svg { vertical-align: text-bottom; } `); function addButton(wrap, code) { if (!wrap.classList.contains("gh-csc-wrap")) { copyId++; // See comments from sindresorhus/refined-github/issues/1278 code.id = `gh-csc-${copyId}`; copyButton.setAttribute("for", `gh-csc-${copyId}`); wrap.classList.add("gh-csc-wrap"); wrap.insertBefore(copyButton.cloneNode(true), wrap.childNodes[0]); } } function init() { const markdown = document.querySelector(markdownSelector); if (markdown) { [...document.querySelectorAll(markdownSelector)].forEach(md => { [...md.querySelectorAll(codeSelector)].forEach(pre => { let code = pre.querySelector("code"); let wrap = pre.parentNode; if (code) { // pre > code addButton(pre, code); } else if (wrap.classList.contains("highlight")) { // div.highlight > pre addButton(wrap, pre); } }); }); } } document.addEventListener("ghmo:container", init); document.addEventListener("ghmo:comments", init); init(); })();