From efed031ea4d9b2a3591fd5a54cda84390ac19c56 Mon Sep 17 00:00:00 2001 From: Jason Etcovitch Date: Fri, 16 Oct 2020 10:55:35 -0400 Subject: [PATCH] Add copy button to code blocks (#16052) * Add deps * Setup copy events * Updoot * Tweak styling * Do a lil' proof-of-concept * Updoot @github-docs/render-content * Use SCSS variables for custom styles * Adjust docs * Ignore clipboard in check-deps * Add copy annotation to quickstart workflow sample Co-authored-by: Cynthia Rich --- .../guides/building-and-testing-nodejs.md | 2 +- content/actions/quickstart.md | 2 +- contributing/content-markup-reference.md | 14 +- javascripts/copy-code.js | 15 ++ javascripts/index.js | 2 + package-lock.json | 152 +++++++++++++++--- package.json | 3 +- script/check-deps.js | 3 +- stylesheets/syntax-highlighting.scss | 13 ++ 9 files changed, 175 insertions(+), 31 deletions(-) create mode 100644 javascripts/copy-code.js diff --git a/content/actions/guides/building-and-testing-nodejs.md b/content/actions/guides/building-and-testing-nodejs.md index a9b60700ff2b..b24bc1e181db 100644 --- a/content/actions/guides/building-and-testing-nodejs.md +++ b/content/actions/guides/building-and-testing-nodejs.md @@ -33,7 +33,7 @@ We recommend that you have a basic understanding of Node.js, YAML, workflow conf To get started quickly, add the template to the `.github/workflows` directory of your repository. {% raw %} -```yaml +```yaml{:copy} name: Node.js CI on: [push] diff --git a/content/actions/quickstart.md b/content/actions/quickstart.md index 27c500c0b7bd..5dbd3b07c203 100644 --- a/content/actions/quickstart.md +++ b/content/actions/quickstart.md @@ -21,7 +21,7 @@ You only need an existing {% data variables.product.prodname_dotcom %} repositor 1. From your repository on {% data variables.product.prodname_dotcom %}, create a new file in the `.github/workflows` directory named `superlinter.yml`. For more information, see "[Creating new files](/github/managing-files-in-a-repository/creating-new-files)." 2. Copy the following YAML contents into the `superlinter.yml` file. **Note:** If your default branch is not `main`, update the value of `DEFAULT_BRANCH` to match your repository's default branch name. {% raw %} - ```yaml + ```yaml{:copy} name: Super-Linter # Run this workflow every time a new commit pushed to your repository diff --git a/contributing/content-markup-reference.md b/contributing/content-markup-reference.md index 61676986e0e0..9a0636a84058 100644 --- a/contributing/content-markup-reference.md +++ b/contributing/content-markup-reference.md @@ -40,14 +40,22 @@ To render syntax highlighting in command line instructions, we use triple backti ### Usage -\`\`\`shell -git init YOUR_REPO -\`\`\` + ```shell + git init YOUR_REPO + ``` This syntax highlighting renders light text on a dark background, and should be reserved for command line instructions. Within the command-line syntax, you can also use the `` helper tag to indicate content that varies for each user, such as a user or repository name. +**Copy-able code blocks** + +You can also add a header that includes the name of the language and a button to copy the contents of the code block: + + ```js{:copy} + const copyMe = true + ``` + ## Octicons Octicons are icons used across GitHub’s interface. We reference Octicons when documenting the user interface. Find the name of the Octicon on the [Octicons site](https://primer.style/octicons). For accessibility purposes, use [the `aria-label` option](https://primer.style/octicons/packages/javascript#aria-label) to describe the Octicon. diff --git a/javascripts/copy-code.js b/javascripts/copy-code.js new file mode 100644 index 000000000000..a92c48fc794a --- /dev/null +++ b/javascripts/copy-code.js @@ -0,0 +1,15 @@ +import Clipboard from 'clipboard' + +export default () => { + const clipboard = new Clipboard('button.js-btn-copy') + + clipboard.on('success', evt => { + const btn = evt.trigger + const beforeTooltip = btn.getAttribute('aria-label') + btn.setAttribute('aria-label', 'Copied!') + + setTimeout(() => { + btn.setAttribute('aria-label', beforeTooltip) + }, 2000) + }) +} diff --git a/javascripts/index.js b/javascripts/index.js index b9bfb3ee8468..12bf1f341ac1 100644 --- a/javascripts/index.js +++ b/javascripts/index.js @@ -13,6 +13,7 @@ import print from './print' import localization from './localization' import helpfulness from './helpfulness' import experiment from './experiment' +import copyCode from './copy-code' import { fillCsrf } from './get-csrf' import initializeEvents from './events' @@ -31,5 +32,6 @@ document.addEventListener('DOMContentLoaded', async () => { await fillCsrf() // this must complete before any POST calls helpfulness() experiment() + copyCode() initializeEvents() }) diff --git a/package-lock.json b/package-lock.json index cfef88742bfd..1c7787a125e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1131,16 +1131,28 @@ } }, "@github-docs/render-content": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@github-docs/render-content/-/render-content-5.0.0.tgz", - "integrity": "sha512-HXSk8B4Yjp7NyaDcq8DPwK33O81Bsd29aSkyWwfrsdJiaLx3D0m5woa3qE5XZRvC5DSiyVZS5xr8eyXL0OAc2w==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@github-docs/render-content/-/render-content-5.1.0.tgz", + "integrity": "sha512-d0Is3zPvumal5MYRYcZv0X2jwzEV3e5/OTwWTi2s9ZVWK53TwoHvO0r+I2Z0XQfHDKZgM6V0h2lZhRIJxcYsMg==", "requires": { + "@primer/octicons": "^11.0.0", "cheerio": "^1.0.0-rc.3", + "hast-util-from-parse5": "^6.0.0", + "hastscript": "^6.0.0", "html-entities": "^1.2.1", "hubdown": "^2.6.0", "liquid": "^5.0.0", + "parse5": "^6.0.1", + "remark-code-extra": "^1.0.1", "semver": "^5.7.1", "strip-html-comments": "^1.0.0" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + } } }, "@github/rest-api-operations": { @@ -2740,6 +2752,14 @@ "@types/node": "*" } }, + "@types/hast": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.1.tgz", + "integrity": "sha512-viwwrB+6xGzw+G1eWpF9geV3fnsDgXqHG+cqgiHrvQfDUW5hzhCyV7Sy3UJxhfRFBsgky2SSW33qi/YrIkjX5Q==", + "requires": { + "@types/unist": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", @@ -2813,6 +2833,11 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", + "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" + }, "@types/prettier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.0.tgz", @@ -4181,7 +4206,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", @@ -4215,7 +4240,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "requires": { "bn.js": "^4.1.0", @@ -4908,6 +4933,16 @@ "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", "dev": true }, + "clipboard": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -5622,7 +5657,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", @@ -5634,7 +5669,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", @@ -6183,6 +6218,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -6356,7 +6396,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", @@ -9484,6 +9524,14 @@ "pinkie-promise": "^2.0.0" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -9693,15 +9741,29 @@ } }, "hast-util-from-parse5": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", - "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-6.0.0.tgz", + "integrity": "sha512-3ZYnfKenbbkhhNdmOQqgH10vnvPivTdsOJCri+APn0Kty+nRkDHArnaX9Hiaf8H+Ig+vkNptL+SRY/6RwWJk1Q==", "requires": { - "ccount": "^1.0.3", + "@types/parse5": "^5.0.0", + "ccount": "^1.0.0", "hastscript": "^5.0.0", "property-information": "^5.0.0", - "web-namespaces": "^1.1.2", - "xtend": "^4.0.1" + "vfile": "^4.0.0", + "web-namespaces": "^1.0.0" + }, + "dependencies": { + "hastscript": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", + "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + } } }, "hast-util-has-property": { @@ -9734,6 +9796,29 @@ "zwitch": "^1.0.0" }, "dependencies": { + "hast-util-from-parse5": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-5.0.3.tgz", + "integrity": "sha512-gOc8UB99F6eWVWFtM9jUikjN7QkWxB3nY0df5Z0Zq1/Nkwl5V4hAAsl0tmwlgWl/1shlTF8DnNYLO8X6wRV9pA==", + "requires": { + "ccount": "^1.0.3", + "hastscript": "^5.0.0", + "property-information": "^5.0.0", + "web-namespaces": "^1.1.2", + "xtend": "^4.0.1" + } + }, + "hastscript": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", + "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "requires": { + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + } + }, "parse5": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", @@ -9791,10 +9876,11 @@ "integrity": "sha512-I5GTdSfhYfAPNztx2xJRQpG8cuDSNt599/7YUn7Gx/WxNMsG+a835k97TDkFgk123cwjfwINaZknkKkphx/f2A==" }, "hastscript": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-5.1.2.tgz", - "integrity": "sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", "requires": { + "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", @@ -9877,9 +9963,9 @@ "integrity": "sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==" }, "highlight.js": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.0.tgz", - "integrity": "sha512-OryzPiqqNCfO/wtFo619W+nPYALM6u7iCQkum4bqRmmlcTikOkmlL06i009QelynBPAlNByTQU6cBB2cOBQtCw==" + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.2.1.tgz", + "integrity": "sha512-A+sckVPIb9zQTUydC9lpRX1qRFO/N0OKEh0NwIr65ckvWA/oMY8v9P3+kGRK3w2ULSh9E8v5MszXafodQ6039g==" }, "highlightjs-graphql": { "version": "1.0.2", @@ -17605,9 +17691,9 @@ "dev": true }, "property-information": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.5.0.tgz", - "integrity": "sha512-RgEbCx2HLa1chNgvChcx+rrCWD0ctBmGSE0M7lVm1yyv4UbvbrWoXp/BkVLZefzjrRBGW8/Js6uh/BnlHXFyjA==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", "requires": { "xtend": "^4.0.0" } @@ -18206,6 +18292,14 @@ "resolved": "https://registry.npmjs.org/relative-date/-/relative-date-1.1.3.tgz", "integrity": "sha1-EgkDBAWI7HpKOZxlR/0B0OPS3GM=" }, + "remark-code-extra": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/remark-code-extra/-/remark-code-extra-1.0.1.tgz", + "integrity": "sha512-MHKTd2zsKc9ayrLBaUzyiIw7rNY4Q/aNSx1L1xss7ZthQ/oSEoMvtA5wpXALVKTWOL5JQXDf/Q9lP8IWbP3cig==", + "requires": { + "unist-util-visit": "^1.4.1" + } + }, "remark-gemoji-to-emoji": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remark-gemoji-to-emoji/-/remark-gemoji-to-emoji-1.1.0.tgz", @@ -18845,6 +18939,11 @@ } } }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -18977,7 +19076,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "requires": { "inherits": "^2.0.1", @@ -20427,6 +20526,11 @@ "setimmediate": "^1.0.4" } }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "title-case": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/title-case/-/title-case-2.1.1.tgz", diff --git a/package.json b/package.json index 57c59b46a5b7..9a5bbe381dd2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@babel/runtime": "^7.11.2", "@github-docs/data-directory": "^1.2.0", "@github-docs/frontmatter": "^1.3.1", - "@github-docs/render-content": "^5.0.0", + "@github-docs/render-content": "^5.1.0", "@github/rest-api-operations": "^3.1.3", "@octokit/rest": "^16.38.1", "@primer/css": "^15.1.0", @@ -26,6 +26,7 @@ "browser-date-formatter": "^3.0.3", "change-case": "^3.1.0", "cheerio": "^1.0.0-rc.3", + "clipboard": "^2.0.6", "compression": "^1.7.4", "connect-slashes": "^1.4.0", "cookie-parser": "^1.4.5", diff --git a/script/check-deps.js b/script/check-deps.js index 71f036cc08e5..36ac0052df67 100644 --- a/script/check-deps.js +++ b/script/check-deps.js @@ -47,7 +47,8 @@ const main = async () => { 'search-with-your-keyboard', 'uuid', 'imurmurhash', - 'js-cookie' + 'js-cookie', + 'clipboard' ] }) diff --git a/stylesheets/syntax-highlighting.scss b/stylesheets/syntax-highlighting.scss index c743657747af..0e144bb8f79f 100644 --- a/stylesheets/syntax-highlighting.scss +++ b/stylesheets/syntax-highlighting.scss @@ -5,6 +5,19 @@ from https://unpkg.com/highlight.js@9.15.8/styles/github.css */ +.markdown-body .code-extra { + margin-top: $spacer-4; + + pre { + margin-top: 0 !important; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + border-left: 1px $gray-200 solid !important; + border-bottom: 1px $gray-200 solid !important; + border-right: 1px $gray-200 solid !important; + } +} + .hljs { display: block; overflow-x: auto;