From efba0cf6195b55ebffb32b02d83b374f49636c01 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 4 Jul 2024 00:19:12 +0800 Subject: [PATCH] Make copying code blocks easier When hovering over code blocks, we now show a copy button. When clicked, the textual representation of a code block is copied to the clipboard. --- docs/source/css/icons.css | 5 ++++- docs/source/css/main.css | 33 +++++++++++++++++++++++++++++++++ docs/source/js/main.js | 23 ++++++++++++++++++++--- docs/src/docs/filters.inko | 28 ++++++++++++++++++++++++++++ docs/src/docs/layouts.inko | 3 ++- 5 files changed, 87 insertions(+), 5 deletions(-) diff --git a/docs/source/css/icons.css b/docs/source/css/icons.css index aa1dec045..78514c579 100644 --- a/docs/source/css/icons.css +++ b/docs/source/css/icons.css @@ -1,6 +1,6 @@ @font-face { font-family: 'icons'; - src: url(data:font/woff2;base64,d09GMgABAAAAAAUEAAsAAAAACLQAAAS5AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAdBEIColYiDQLFAABNgIkAyQEIAWCbgcgGzsHID4NcHK76gfI8srYRpikabpkmJAS9kTyyUHXefpFSA4e+v3Rn4sFNZ0drqCb74vrvEnsauu0s8nJDfw/vz/yPl/iSRYSb8SJUpnf+tRjZ4fG5d1TaNBApS2qEQxp1aFt2uWloSc0Fi/gLtFJgfRrkYzSXk/4p4fHrjCJLEiy4oRSDHGehgTy+3/ga2tqqRkmQA9bAo/3D38/00UURtuABzijt/cXVVhpUbhLwAS5AV1EWpXI8R3YqYDnyCqdL14BpYLEFDgAch59Bua6ScQbyQJPp094s24a8L1DG2mKQBgBLkS7VJgKB7JcJAgbRdT/9oUGEhQEpe80ZA1Avb2w8zr12hEcFqwVlJ/YyCEqQMABXjHFBacvqYgFQK4YIQtp2DiYCAVTJzoHONCjheTkhST19UVV3JDhc0oLI4KAhe20Fght3SzdPQdMIrb3cV3fIowoTmPgsFlikYjIvkNi+wyb9iyDXgH3M4rGJ7tBH2YRVuGf6oMxwgp0udqagi9XY7HrZAR3IxZWJO9W7DYV/vXaOcxNDWpc9bS8RbGqACeXXlYsceVFbqixLf573GqevrjmpllwpJcDFvrefib9vtEy3Wf5fXoNxeazYP1dn3pYYK6bk2c5FxfYuyZMclnz+8R2y5sXfLX9f9AaFbxf0GnmCG29sJZjrH2d9lfgZhlMr56HVzI7nk1nYFZ9WmCPX8XETZ51cYnbtyBrvujOvojmB4CprBDVE30I/QWr7VBQA2v7uHkwxA/lGmc54cqfG8689IfblPfDtZzDcNbLdDY2OpG3t72OMNnkgEz3MTcXFkNbqSMiPcZsT0n3/4+ZXZNPCxy2ViWU96jKgVOK4M7AQP/wCxW8VsvpZdoygT6m9uJonNNK5duzLEixzjYgMS97ZleBct0Cp5P8f5WKQOXAigoaVYmNjUEPRKgEoaEaZYLxQpAdLChmHDuFNbzuu7GrdL7zKUVIJ/QDrcAFNShVjHAy3qdrY5TFMFyD4xOd7XxEgEkB2P/7914S2V3CZMTKrANVlza7LqTjFgmSQ+6o9iWiDal0h6Fe0d7+0DAwEIFbbaZjIRVumKfXIqJDkoA9SLoYvDnJVrMqFZMJaNXi9WmSwfl9nnRwChVL9mvJlMgN89SMqPaE6QWzUmVWPktaQkBTd9Us0zWM1TVSfu/u0NDMy31PtOnKuYtVC7d+Nhc61jvSR/88Qfdi7nLeenJ+fw+jyP72j10bWjNSGMrdrhtRogqp/Reh8PcRX8lR4dTdbwByS2b0/twdvOcPjmvq2XSN5Tq7GFtaGLuwfzk2AcYNAGygDgO6atDRQ9OQZX5cAORWL+f+F/rvTNQA8PT6P7S4teAMCmTIGaEThzG6RTPPRYtkpE81EFwPNJNlvk8ATlBNCbvRQ8KT1QrCUwQgKiQUAQoFgRFa41MVDrAIKhwhcDzeFRk6wUC0CwgMRKBAESBcgcFgjsbxWQoCCQQKEga3AoMZY5du25VCNqkUUkDpLd91jA2MoIh0SrAj2WRK2LFgcxmlMb2p3i9BCOlkUk4eyZT08L7+odtHVm8M/AsVAxADtZehRDPpFCyCZjJlpJPWXEc1SiRnl/CnsF65AUgG); + src: url(data:font/woff2;base64,d09GMgABAAAAAAVIAAsAAAAACWgAAAT6AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAdBEICosEiT8LFgABNgIkAygEIAWCbgcgG8oHIB6HsXvknFHsX5N8+HfTPzcJ0qSF8l4KNTWqxrQmTA02h82RqcJMDHhi+k09wTew2W3/oWulMxcRFi16gGc0O5dz2R/dVuAJ8gf0URhNgVGAz//Yy9gcYOVafeYZQNOZZCMEALWNeg5gw1AO+RUpxAWTM+KOLQuBv36lbzuhkI5aAIQJGKgE6ApfkCinQaQMOv73A7iDBgWCwL8Kdx2+8q/OO50AIKb/8gWI81vpfalVulOqlfqDBkEixLDohDTSWcIHIOeQT4RUdaYGDngmZUAHKJKaLiT2EMgC3X393BVxcaw77f9hcCmB+2lvbmsAB25Ea2J1Br7XYIDGyOnNigGzmbS19rJkgWYr39LWbj7C6Q95tBq3YQoxT7A0Rk4BVBkOzAZuejPpIloka56saXn/fq5jdW12u45o2x0OHadvtdlCC6iFYTXf+GXF2lSipNxUx3hDzehsUyzQJJ72P+Rh+OX3qYaDPKszyYlpnGy9FWC3E23KuVU+ZNIZtGret+y6EWU08sRgkJsOyy/asw/eZ3X2OuMhTp9xyZFzLcfhINrd3jYb3KQ0q7MQwjILtobpCtxGd3JtkynNUVZiQVzx6UVaKyfVHwOMRo9W4xmMaAyszkS09vxZzJzeBk4bf/Jl+kVWl3r+eexxaM5EGY95nLD5cXscCWarwnqOcJr3kcff+xq3QbOVB2DMXWa3VhLdVe9uRGtWmLYH5o7m3fZ4i71+f8IpR/UzTq98Yos7gemoLytTVqxIyVMq86Jlwn4BXSRLS5PxVG8QUkVBqYf6j/3++yef/PFHetBBydU1EybU15gwfptVIrVuW7lizVUpKg+qxNvEGWHiY6JLKTdOtL2JX8qoOCoxAcpXOlXqsFq4pVpkGfteFVwfVD97Ns4Fq1QJjYjFQhSoZjYVTsvhM0NdLVm1hE0qH4+JdojHxFYR4ljEt7Qdz1266dAg2WRmMTM5KIinxd3Cy+Ih8VEBkpuQ999/oyrlxVVavPmZWfUh1rMz7lDCY0Io5pdREd+WRjTNKTMD30FlornFGLHIrXveJ5vKm2aX2ryBCKpsvgKiK5PuzDhrqQ/NnMF7162JaD69yTX+4YUmr8ZuUXlKzepa+UhLRUTbvApcgvoPhVPx+6ebXUIyQsUW0WYkEYeKwOYRm+ZaLZbQfzUNCQ1xVQYmCBy1fLtrzeaSn352JsfBnUb5VPuVeskZKLpqREdMgiomMejPBaPhEaMjfyYlxviW8fNka97FtRYsK8B3/m6NbB5f5quM9a358nZ+W97DjyVs90B/Nyv56GJansi3y/PyCx+fz496dfmK8tIufmTA7R/IffwwzyF2+0vHTWOz1+9pLCpOykhPKiY/c896LLkNAE4AfRuEdLcDNZ8Bc1KU8iYEAAF2Fq77fKI0938xxwAAvrEGPwNf1w9iUEDhOk8OwWEepocgrJgJLqg43Y0YagEDDqVMgKQGzANTzTF1SHASZNUVAoA1UxChzkxDgETIMHvMAnCQmoUQoKA7C29MB11qGReQsCCYKUjQaqbBIg0yzEGzAHJ4m4VgUdrKIhVrL2z3moEh9GAK1JhxQ3ybpPhEtKEP06HHRXmBSEI8El1mk3je4++D0IQ+DGAWRjEZ00f80MOMm2sFZc+Uf3QxiXKjsTMRiAH0Qb0J9pMxE33o7duNeQiEcnWkFmHz9ceDxgQAAAA=); font-weight: normal; font-style: normal; font-display: block; @@ -31,6 +31,9 @@ i { .icon-comments:before { content: "\f086"; } +.icon-clipboard:before { + content: "\f0ea"; +} .icon-lightbulb-o:before { content: "\f0eb"; } diff --git a/docs/source/css/main.css b/docs/source/css/main.css index 2788b331c..170ac737a 100644 --- a/docs/source/css/main.css +++ b/docs/source/css/main.css @@ -168,6 +168,39 @@ table tr:last-child td { border-bottom: 0; } +div.highlight { + position: relative; +} + +div.highlight .copy { + background: var(--white); + display: none; + border-radius: var(--border-radius); + border: 1px solid var(--border); + cursor: pointer; + font-size: 12px; + padding: 5px; + position: absolute; + right: 5px; + top: 5px; +} + +div.highlight .copy .copied-text { + display: none; +} + +div.highlight .copy.copied .copied-text { + display: inline; +} + +div.highlight .copy.copied .copy-text { + display: none; +} + +div.highlight:hover .copy { + display: block; +} + .highlight .k { font-weight: bold; } diff --git a/docs/source/js/main.js b/docs/source/js/main.js index c27a32617..e1bebc5c0 100644 --- a/docs/source/js/main.js +++ b/docs/source/js/main.js @@ -1,9 +1,9 @@ (function() { "use strict"; - document.addEventListener('DOMContentLoaded', function() { - document.querySelectorAll('.expand-menus a').forEach(function(button) { - button.addEventListener('click', function(event) { + document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('.expand-menus a').forEach((button) => { + button.addEventListener('click', (event) => { event.preventDefault(); let query = button.dataset.toggle; @@ -15,5 +15,22 @@ document.querySelector(query).classList.toggle('visible'); }); }); + + document.querySelectorAll('.highlight .copy').forEach((el) => { + let timer = null; + + el.addEventListener('click', (e) => { + e.preventDefault(); + + if (timer) { + clearTimeout(timer); + timer = null; + } + + el.classList.add('copied'); + navigator.clipboard.writeText(el.previousSibling.textContent); + timer = setTimeout(function() { el.classList.remove('copied'); }, 2000); + }); + }); }); })(); diff --git a/docs/src/docs/filters.inko b/docs/src/docs/filters.inko index 6c71d4654..d3b1daa88 100644 --- a/docs/src/docs/filters.inko +++ b/docs/src/docs/filters.inko @@ -23,6 +23,34 @@ impl Filter for AutoTableOfContents { } } +class AddCopyButton { + fn static new -> AddCopyButton { + AddCopyButton() + } +} + +impl Filter for AddCopyButton { + fn pub mut run(document: mut Document) { + document.nodes.iter_mut.each(fn (n) { + let el = match n { + case Element(e) if e.name == 'div' -> { + match e.attributes.opt('class') { + case Some('highlight') -> e + case _ -> return + } + } + case _ -> return + } + + el.button.attr('class', 'copy').with(fn (btn) { + btn.i.attr('class', 'icon-clipboard') + btn.span.attr('class', 'copy-text').text(' Copy') + btn.span.attr('class', 'copied-text').text(' Copied!') + }) + }) + } +} + # A filter that turns relative document links (e.g. `[](ivm)`) into the correct # URLs, optionally setting the link text if left out. This makes it easier to # link to different documents. diff --git a/docs/src/docs/layouts.inko b/docs/src/docs/layouts.inko index 9ec9d3cc6..6b88b2366 100644 --- a/docs/src/docs/layouts.inko +++ b/docs/src/docs/layouts.inko @@ -1,7 +1,7 @@ import builder.html import builder.xml import docs.config (Config) -import docs.filters (AutoTableOfContents, RelativeLinks) +import docs.filters (AddCopyButton, AutoTableOfContents, RelativeLinks) import docs.menu (Item, Menu) import docs.url (link_from) import markdown.html (Filter, TableOfContents) @@ -15,6 +15,7 @@ fn filters(menu: ref Menu, page: ref Page) -> Array[Filter] { SyntaxHighlight.new as Filter, Admonitions.new as Filter, RelativeLinks(menu: menu, page: page) as Filter, + AddCopyButton.new as Filter, AutoTableOfContents() as Filter, # This filter must come last ] }