Monorepo for Tangled

[feature] - Add copy-to-clipboard button for markdown code blocks #2

open opened by natespilman.com targeting master from add-code-block-copy-button

Summary#

  • Injects a copy button into .prose pre.chroma code blocks via client-side DOM injection in base.html
  • Scoped to .prose so blob view source code (which has its own UX) is unaffected
  • Handles HTMX-swapped content (dynamic comment loading) via htmx:afterSettle listener

Test plan

  • go test ./appview/pages/markup/... passes (no regressions)
  • Verify button appears on hover over code blocks in rendered markdown
  • Verify clicking copies code and shows check icon briefly
  • Verify dark mode styling
  • Verify blob view code blocks do NOT get copy buttons
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:c7frv4rcitff3p2nh7of5bcv/sh.tangled.repo.pull/3mgblysqwmn22
+87
Diff #0
+39
appview/pages/templates/layouts/base.html
··· 80 80 {{ template "layouts/fragments/footer" . }} 81 81 </footer> 82 82 {{ end }} 83 + 84 + <script> 85 + (function() { 86 + var copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>'; 87 + var checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>'; 88 + 89 + function addCopyButtons(root) { 90 + var blocks = (root || document).querySelectorAll('.prose pre.chroma'); 91 + blocks.forEach(function(pre) { 92 + if (pre.querySelector('.code-copy-btn')) return; 93 + pre.style.position = 'relative'; 94 + var btn = document.createElement('button'); 95 + btn.className = 'code-copy-btn'; 96 + btn.type = 'button'; 97 + btn.title = 'Copy code'; 98 + btn.setAttribute('aria-label', 'Copy code to clipboard'); 99 + btn.innerHTML = copyIcon; 100 + btn.addEventListener('click', function() { 101 + var code = pre.querySelector('code'); 102 + var text = code ? code.innerText : pre.innerText; 103 + navigator.clipboard.writeText(text).then(function() { 104 + btn.innerHTML = checkIcon; 105 + btn.classList.add('copied'); 106 + setTimeout(function() { 107 + btn.innerHTML = copyIcon; 108 + btn.classList.remove('copied'); 109 + }, 2000); 110 + }); 111 + }); 112 + pre.appendChild(btn); 113 + }); 114 + } 115 + 116 + addCopyButtons(); 117 + document.body.addEventListener('htmx:afterSettle', function(e) { 118 + addCopyButtons(e.detail.elt); 119 + }); 120 + })(); 121 + </script> 83 122 </body> 84 123 </html> 85 124 {{ end }}
+48
input.css
··· 215 215 @apply disabled:accent-blue-500 checked:accent-blue-500 disabled:checked:accent-blue-500; 216 216 } 217 217 218 + .prose pre.chroma { 219 + position: relative; 220 + } 221 + 222 + .code-copy-btn { 223 + position: absolute; 224 + top: 0.5rem; 225 + right: 0.5rem; 226 + padding: 0.375rem; 227 + border: none; 228 + border-radius: 0.25rem; 229 + cursor: pointer; 230 + color: #6c6f85; 231 + background: rgba(239, 241, 245, 0.8); 232 + opacity: 0; 233 + transition: opacity 0.15s ease, color 0.15s ease, background 0.15s ease; 234 + line-height: 0; 235 + z-index: 1; 236 + } 237 + 238 + .prose pre.chroma:hover .code-copy-btn, 239 + .code-copy-btn:focus { 240 + opacity: 1; 241 + } 242 + 243 + .code-copy-btn:hover { 244 + background: rgba(239, 241, 245, 1); 245 + color: #4c4f69; 246 + } 247 + 248 + .code-copy-btn.copied { 249 + color: #40a02b; 250 + } 251 + 218 252 /* Base callout */ 219 253 details[data-callout] { 220 254 @apply border-l-4 pl-3 py-2 text-gray-800 dark:text-gray-200 my-4; ··· 1003 1037 .chroma .gl { 1004 1038 text-decoration: underline; 1005 1039 } 1040 + 1041 + .code-copy-btn { 1042 + color: #8087a2; 1043 + background: rgba(36, 39, 58, 0.8); 1044 + } 1045 + 1046 + .code-copy-btn:hover { 1047 + background: rgba(36, 39, 58, 1); 1048 + color: #cad3f5; 1049 + } 1050 + 1051 + .code-copy-btn.copied { 1052 + color: #a6da95; 1053 + } 1006 1054 } 1007 1055 1008 1056 actor-typeahead {

History

1 round 0 comments
sign up or login to add to the discussion
natespilman.com submitted #0
1 commit
expand
appview/pages: add copy-to-clipboard button for markdown code blocks
no conflicts, ready to merge
expand 0 comments