-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add copy-to-clipboard button to code blocks #313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2e06bac
93c76d0
7dce870
1e5d997
da60caf
5173cfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,8 @@ | |
| {{ content }} | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="visually-hidden" aria-live="polite" role="status"></div> | ||
|
|
||
| <script> | ||
| // open external links in new tab | ||
| var links = document.links; | ||
|
|
@@ -25,6 +26,46 @@ | |
| } | ||
| } | ||
| </script> | ||
| <script> | ||
| // copy to clipboard button on code blocks | ||
| if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { | ||
| var statusRegion = document.querySelector('[aria-live="polite"][role="status"]'); | ||
| var preBlocks = document.querySelectorAll('pre.highlight'); | ||
| var preBlocksLength = preBlocks.length; | ||
| var j; | ||
| for (j = 0; j < preBlocksLength; j++) { | ||
| (function (pre) { | ||
| var wrapper = document.createElement('div'); | ||
| wrapper.className = 'pre-wrapper'; | ||
| pre.parentNode.insertBefore(wrapper, pre); | ||
| wrapper.appendChild(pre); | ||
|
|
||
| var button = document.createElement('button'); | ||
| button.className = 'copy-btn'; | ||
| button.type = 'button'; | ||
| button.textContent = 'Copy'; | ||
|
|
||
| button.addEventListener('click', function () { | ||
| var code = pre.querySelector('code'); | ||
| var text = code ? (code.textContent || '') : (pre.textContent || ''); | ||
| navigator.clipboard.writeText(text).then(function () { | ||
| button.textContent = 'Copied'; | ||
| statusRegion.textContent = ''; | ||
| statusRegion.textContent = 'Copied to clipboard'; | ||
| setTimeout(function () { button.textContent = 'Copy'; }, 1500); | ||
| }).catch(function () { | ||
| button.textContent = 'Failed'; | ||
| statusRegion.textContent = ''; | ||
| statusRegion.textContent = 'Copy to clipboard failed'; | ||
| setTimeout(function () { button.textContent = 'Copy'; }, 1500); | ||
| }); | ||
|
Comment on lines
+51
to
+61
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Clearing the status region before setting the new message in both the success and failure branches. Quick heads up though, in my earlier testing VoiceOver on macOS wasn't reliably announcing the aria-live region at all, so this fix is more for NVDA/JAWS than VoiceOver. It's a recognized pattern so I added it, but the VoiceOver gap from before is still a thing. |
||
| }); | ||
|
|
||
| wrapper.appendChild(button); | ||
| })(preBlocks[j]); | ||
| } | ||
| } | ||
| </script> | ||
| {% include analytics.html %} | ||
| </body> | ||
| </html> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -319,7 +319,7 @@ pre > code:focus { | |
| } */ | ||
|
|
||
| /* Code */ | ||
| /* .highlight .hll { background-color: #ffc; | ||
| /* .highlight .hll { background-color: #ffc; | ||
| .highlight .c { color: #999; } | ||
| .highlight .err { color: #a00; background-color: #faa } | ||
| .highlight .k { color: #069; } | ||
|
|
@@ -699,3 +699,53 @@ pre.highlight { | |
| .highlight .gi { | ||
| color: #a6e22e; | ||
| } | ||
|
|
||
| /* Copy to clipboard button */ | ||
| .pre-wrapper { | ||
| position: relative; | ||
| } | ||
|
|
||
| .copy-btn { | ||
| position: absolute; | ||
| top: 8px; | ||
| right: 8px; | ||
|
Comment on lines
+709
to
+711
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I’d prefer to avoid
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense to go with the wrapper approach instead of |
||
| min-width: 60px; | ||
| padding: 4px 10px; | ||
| font-family: inherit; | ||
| font-size: 12px; | ||
| color: #abb2bf; | ||
| background: transparent; | ||
| border: 1px solid #abb2bf; | ||
| border-radius: 4px; | ||
| cursor: pointer; | ||
| opacity: 0; | ||
| text-align: center; | ||
| transition: opacity 0.15s ease-in-out; | ||
|
joshbuchea marked this conversation as resolved.
|
||
| pointer-events: none; | ||
| } | ||
|
|
||
| .pre-wrapper:hover .copy-btn, | ||
| .copy-btn:focus { | ||
| opacity: 1; | ||
| pointer-events: auto; | ||
| } | ||
|
joshbuchea marked this conversation as resolved.
|
||
|
|
||
| .copy-btn:hover { | ||
| background: rgba(171, 178, 191, 0.1); | ||
| } | ||
|
|
||
| @media (hover: none) { | ||
| .copy-btn { | ||
| opacity: 1; | ||
| pointer-events: auto; | ||
| } | ||
| } | ||
|
|
||
| .visually-hidden { | ||
| clip-path: inset(50%); | ||
| height: 1px; | ||
| width: 1px; | ||
| overflow: hidden; | ||
| position: absolute; | ||
| white-space: nowrap; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested the wrapping on the live site, just the wrapper code with no other changes, and didn't see any visible spacing shift between code blocks and surrounding content. The margin-collapsing thing is real in principle but doesn't apply here since the surrounding markdown elements all have their own margins.
Gonna skip this one. If you spot a regression after merge let me know and I'll handle it in CSS instead of the computed-style JS approach Copilot suggested.