add copy to clipboard functionality

This commit is contained in:
Dan Ditomaso 2023-08-11 15:57:17 -04:00
parent f830794fc4
commit d25c7a8f90
2 changed files with 228 additions and 112 deletions

View file

@ -80,7 +80,7 @@ header nav .button {
text-decoration: none;
}
.breadcrumb-siblings a+a {
.breadcrumb-siblings a + a {
padding-top: 10px;
}
@ -113,7 +113,7 @@ main > nav.sidebar {
main nav li img,
article li img,
img.external-link {
max-height: .9em;
max-height: 0.9em;
}
main nav ul {
@ -133,26 +133,24 @@ main nav li a {
color: #546c75;
}
main nav li a:hover {
color: #01324b;
}
main nav li a:before {
content: '\203A';
content: "\203A";
font-weight: bold;
font-size: 20px;
line-height: 1rem;
position: absolute;
opacity: 0;
left: 0;
transition: left .15s, opacity .15s;
transition: left 0.15s, opacity 0.15s;
}
main nav li a:hover:before {
opacity: 1;
left: .75em;
left: 0.75em;
}
main nav li li a:hover:before {
@ -187,7 +185,7 @@ main nav li li a {
background: white;
border-radius: 4px;
box-shadow: 1px 2px 4px 1px rgba(0, 0, 0, .15);
box-shadow: 1px 2px 4px 1px rgba(0, 0, 0, 0.15);
}
#paper1,
@ -258,8 +256,12 @@ article pre,
article table {
margin-bottom: 1.5rem;
}
article > .fullwidth { margin-bottom: 1.5rem; }
article > .fullwidth > * { margin-bottom: 0; }
article > .fullwidth {
margin-bottom: 1.5rem;
}
article > .fullwidth > * {
margin-bottom: 0;
}
article > pre.chroma > code {
background: none;
@ -292,7 +294,7 @@ article li {
article li p,
article li ul,
article li ol {
margin-bottom: .5em;
margin-bottom: 0.5em;
}
h1,
@ -341,10 +343,10 @@ h5 {
.anchor-link {
opacity: 0;
font-size: .6em;
font-size: 0.6em;
border-radius: 10px;
padding: .3em .5em;
margin-left: .25em;
padding: 0.3em 0.5em;
margin-left: 0.25em;
position: absolute;
top: 5px;
}
@ -356,7 +358,7 @@ h5 {
}
.anchor-link:hover {
background-color: rgba(0, 0, 0, .075);
background-color: rgba(0, 0, 0, 0.075);
}
code {
@ -384,25 +386,67 @@ article > pre {
pre > code.cmd {
border-radius: 5px;
position: relative;
}
pre > code.cmd button {
position: relative;
margin-left: auto;
margin-block: 0;
aspect-ratio: 1;
width: 32px;
display: flex;
align-items: center;
background-color: #1a2c47;
color: #ffffff;
border-radius: 50%;
padding: 8px;
}
.copy-tooltip {
position: absolute;
right: 59px;
top: 13px;
color: #ffffff;
background: #1a2c47;
border: 1px solid transparent;
pointer-events: none;
opacity: 0;
font-size: 14px;
border-radius: 5px;
padding: 8px;
}
.copy-tooltip::before {
content: "";
display: block;
position: absolute;
right: -9px;
top: 13px;
border-left: 10px solid #1a2c47;
border-bottom: 8px solid transparent;
border-top: 8px solid transparent;
}
code.cmd.bash,
code.cmd .bash,
code.cmd.bash-continuation,
code.cmd .bash-continuation {
display: flex;
align-items: center;
font-weight: bold;
}
code.cmd.bash::before,
code.cmd .bash::before {
content: '$';
margin-right: .5rem;
content: "$";
margin-right: 0.5rem;
}
code.cmd.bash-continuation::before,
code.cmd .bash-continuation::before {
content: '>';
margin-right: .5rem;
content: ">";
margin-right: 0.5rem;
}
dt:hover .inline-link {
@ -419,10 +463,10 @@ dd {
.field-name {
display: block;
font-family: 'Source Code Pro', monospace;
font-family: "Source Code Pro", monospace;
margin-top: 2em;
font-weight: bold;
margin-bottom: .5em;
margin-bottom: 0.5em;
}
.inline-link {
@ -430,8 +474,8 @@ dd {
position: absolute;
margin-left: -1.5em;
/* margin-top: -.1em; */
padding-right: .3em;
padding-left: .2em;
padding-right: 0.3em;
padding-left: 0.2em;
visibility: hidden;
}
@ -499,37 +543,49 @@ iframe {
line-height: 1em;
}
.json {
line-height: 1.5em;
}
.json .qu { color: #5c91bf; }
.json .key { color: #1c83dc; font-weight: bold; }
.json .str { color: #2f8598; }
.json .num { color: #038a3f; }
.json .bool { color: #9b5e14; }
.json .key a:not([href]) { color: #222; }
.json .qu {
color: #5c91bf;
}
.json .key {
color: #1c83dc;
font-weight: bold;
}
.json .str {
color: #2f8598;
}
.json .num {
color: #038a3f;
}
.json .bool {
color: #9b5e14;
}
.json .key a:not([href]) {
color: #222;
}
article .json a {
text-decoration: none;
font-weight: bold;
}
.json .has-popup { border-bottom: 1px dashed #222; }
.json a[href].has-popup { border-bottom-color: #1c82dc; }
.json .has-popup.module { border-bottom: none; }
.json .has-popup {
border-bottom: 1px dashed #222;
}
.json a[href].has-popup {
border-bottom-color: #1c82dc;
}
.json .has-popup.module {
border-bottom: none;
}
#hovercard {
max-width: 450px;
border-radius: 10px;
filter: drop-shadow(0 5px 5px rgba(0, 0, 0, .25));
filter: drop-shadow(0 5px 5px rgba(0, 0, 0, 0.25));
position: absolute;
font-size: 16px;
transition: transform .25s ease-in-out, opacity .25s ease-in-out;
transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out;
/* TODO: This would be nice, but it breaks the arrow-box... */
/* max-height: 50%;
overflow-y: auto; */
@ -541,6 +597,14 @@ article .json a {
transform: translateY(0);
}
.hidden {
visibility: hidden;
}
.show {
visibility: visible;
}
.popup {
display: block;
opacity: 1;
@ -572,12 +636,12 @@ article .json a {
line-height: 1.4em;
}
#hovercard p+p {
#hovercard p + p {
padding-top: 0;
}
#hovercard li {
margin: .25em;
margin: 0.25em;
}
#hovercard pre > code {
@ -599,7 +663,7 @@ article .json a {
font-size: 18px;
line-height: 1em;
font-weight: bold;
padding: .5em 1em;
padding: 0.5em 1em;
}
#hovercard .module-link:hover {
@ -614,7 +678,7 @@ article .json a {
#hovercard .module-link-description {
font-size: 14px;
color: #555;
margin-left: .5em;
margin-left: 0.5em;
font-weight: normal;
}
@ -677,14 +741,14 @@ article aside.advice {
}
article aside.tip:nth-child(even)::before {
content: '💁‍♀️';
content: "💁‍♀️";
}
article aside.tip:nth-child(odd)::before {
content: '💁‍♂️';
content: "💁‍♂️";
}
article aside.advice::before {
content: '🤦';
content: "🤦";
}
article aside.complete {
@ -696,14 +760,14 @@ article aside.complete {
}
article aside.complete::before {
content: '✅ complete';
content: "✅ complete";
color: #39c849;
text-transform: uppercase;
font-size: 14px;
font-weight: bold;
letter-spacing: 1px;
margin-right: 2em;
margin-bottom: .5em;
margin-bottom: 0.5em;
}
table {
@ -716,7 +780,8 @@ article > table {
margin: 25px auto;
}
th, td {
th,
td {
border-bottom: 1px solid #ddd;
padding: 10px;
line-height: 1.4em;
@ -734,11 +799,6 @@ td code {
word-wrap: break-word;
}
#module-docs-container,
#module-list-container,
#module-template {
@ -763,7 +823,6 @@ td code {
font-size: 20px;
}
#module-multiple-repos {
display: none;
margin: 25px;
@ -786,9 +845,6 @@ td code {
margin: 10px;
}
.tabs .tab-buttons {
display: flex;
font-size: 0;
@ -833,19 +889,6 @@ td code {
border-radius: 8px;
}
@media (max-width: 1400px) {
table {
font-size: 14px;
@ -953,8 +996,6 @@ td code {
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #060e17;
@ -971,8 +1012,14 @@ td code {
--docsearch-searchbox-focus-background: rgb(28, 52, 79);
--docsearch-text-color: #bdd6f7;
--docsearch-muted-color: #96a9c4;
--docsearch-key-gradient: linear-gradient(-26.5deg, #060e17 0%, rgb(47, 62, 72) 100%);
--docsearch-key-shadow: inset 0 -2px 0 0 #4c4c56, inset 0 0 1px 1px rgb(70, 70, 70), 0 1px 2px 1px rgba(77, 79, 91, 0.4);
--docsearch-key-gradient: linear-gradient(
-26.5deg,
#060e17 0%,
rgb(47, 62, 72) 100%
);
--docsearch-key-shadow: inset 0 -2px 0 0 #4c4c56,
inset 0 0 1px 1px rgb(70, 70, 70),
0 1px 2px 1px rgba(77, 79, 91, 0.4);
}
/* End Algolia DocSearch */
@ -1032,7 +1079,7 @@ td code {
}
.json .key a:not([href]) {
color: #bdd6f7
color: #bdd6f7;
}
#hovercard,

View file

@ -1,12 +1,14 @@
$(function() {
$(function () {
function hasPrefix(str, prefix) {
if (!prefix) return true;
if (!str) return false;
if (!str) return false;
return str.indexOf(prefix) === 0;
}
// highlight current page in left nav
var $currentPageLink = $('main nav a[href="'+window.location.pathname+'"]');
var $currentPageLink = $(
'main nav a[href="' + window.location.pathname + '"]'
);
if (hasPrefix(window.location.pathname, "/docs/json/")) {
// as a special case, highlight the JSON structure link anywhere within it
$currentPageLink = $('main nav a[href="/docs/json/"]');
@ -15,11 +17,17 @@ $(function() {
// as another special case, highlight the modules link anywhere within it
$currentPageLink = $('main nav a[href="/docs/modules/"]');
}
$currentPageLink.addClass('current');
$currentPageLink.addClass("current");
// add anchor links, inspired by https://github.com/bryanbraun/anchorjs
$('article > h1[id], article > h2[id], article > h3[id], article > h4[id], article > h5[id], article > h6[id]').each(function() {
var $anchor = $('<a href="#'+this.id+'" class="anchor-link" title="Direct link">🔗</a>');
$(
"article > h1[id], article > h2[id], article > h3[id], article > h4[id], article > h5[id], article > h6[id]"
).each(function () {
var $anchor = $(
'<a href="#' +
this.id +
'" class="anchor-link" title="Direct link">🔗</a>'
);
$(this).append($anchor);
});
@ -29,32 +37,68 @@ $(function() {
// the <pre>; this line finds those and adds a .chroma class
// to the outer pre element, and our CSS file has a style to
// ensure the inner code block does not produce extra padding
$('article > pre:not(.chroma) > code:not(.cmd)').parent().addClass('chroma');
$("article > pre:not(.chroma) > code:not(.cmd)")
.parent()
.addClass("chroma");
// Add links to Caddyfile directive tokens in code blocks.
// See include/docs-head.html for the whitelist bootstrapping logic
$('pre.chroma .k')
.filter((k, item) =>
window.CaddyfileDirectives.includes(item.innerText)
|| item.innerText === '<directives...>'
$("pre.chroma .k")
.filter(
(k, item) =>
window.CaddyfileDirectives.includes(item.innerText) ||
item.innerText === "<directives...>"
)
.map(function(k, item) {
.map(function (k, item) {
let text = item.innerText;
let url = text === '<directives...>'
? '/docs/caddyfile/directives'
: '/docs/caddyfile/directives/' + text;
text = text.replace(/</g,'&lt;').replace(/>/g,'&gt;');
$(item).html('<a href="' + url + '" style="color: inherit;" title="Directive">' + text + '</a>');
let url =
text === "<directives...>"
? "/docs/caddyfile/directives"
: "/docs/caddyfile/directives/" + text;
text = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
$(item).html(
'<a href="' +
url +
'" style="color: inherit;" title="Directive">' +
text +
"</a>"
);
});
// Add links to [<matcher>] or named matcher tokens in code blocks.
// The matcher text includes <> characters which are parsed as HTML,
// so we must use text() to change the link text.
$('pre.chroma .nd')
.map(function(k, item) {
let text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');
$(item).html('<a href="/docs/caddyfile/matchers#syntax" style="color: inherit;" title="Matcher token">' + text + '</a>');
});
$("pre.chroma .nd").map(function (k, item) {
let text = item.innerText.replace(/</g, "&lt;").replace(/>/g, "&gt;");
$(item).html(
'<a href="/docs/caddyfile/matchers#syntax" style="color: inherit;" title="Matcher token">' +
text +
"</a>"
);
});
// select all bash code elements and dynamically add clipboard button/svg
$("pre > code.bash").map(function (_, block) {
// only add button if browser supports Clipboard API
if (navigator.clipboard) {
let tooltipText = "Copied";
let icon = $(
`<svg fill="currentColor" class="copy_icon" width="32" height="32" viewBox="0 0 32 32"><path d="M25.629 27.591v-18.051h-14.127v18.051h14.127zM25.629 7.005q1.026 0 1.811 0.755t0.785 1.781v18.051q0 1.026-0.785 1.811t-1.811 0.785h-14.127q-1.026 0-1.811-0.785t-0.785-1.811v-18.051q0-1.026 0.785-1.781t1.811-0.755h14.127zM21.766 1.813v2.596h-15.455v18.051h-2.536v-18.051q0-1.026 0.755-1.811t1.781-0.785h15.455z"></path></svg>`
);
let $button = $(`<button>`).append(icon);
let $tooltip = $(`<div class="copy-tooltip">${tooltipText}</div>`);
let $codeBlockElem = $(block);
$codeBlockElem.append($button);
$codeBlockElem.append($tooltip);
$button.on("click", async function () {
await copyToClipboard(block, $button);
});
}
});
});
// addLinkaddLinksToSubdirectivessToAnchors finds all the ID anchors
@ -62,26 +106,51 @@ $(function() {
// links that have an ID on the page. This is opt-in for each page,
// because it's not necessary to run everywhere.
function addLinksToSubdirectives() {
let anchors = $('article *[id]').map((i, el) => el.id).toArray();
$('pre.chroma .k')
let anchors = $("article *[id]")
.map((i, el) => el.id)
.toArray();
$("pre.chroma .k")
.filter((k, item) => anchors.includes(item.innerText))
.map(function(k, item) {
let text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;');
let url = '#' + item.innerText;
$(item).html('<a href="' + url + '" style="color: inherit;" title="' + text + '">' + text + '</a>');
.map(function (k, item) {
let text = item.innerText
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
let url = "#" + item.innerText;
$(item).html(
'<a href="' +
url +
'" style="color: inherit;" title="' +
text +
'">' +
text +
"</a>"
);
});
}
function stripScheme(url) {
return url.substring(url.indexOf("://")+3);
return url.substring(url.indexOf("://") + 3);
}
// splitTypeName splits a fully qualified type name into
// its package path and type name components, for example:
// "github.com/foo/bar.Type" => "github.com/foo/bar" and "Type".
function splitTypeName(fqtn) {
let lastDotPos = fqtn.lastIndexOf('.');
let lastDotPos = fqtn.lastIndexOf(".");
let pkg = fqtn.substr(0, lastDotPos);
let typeName = fqtn.substr(lastDotPos+1);
return {pkg: pkg, typeName: typeName};
let typeName = fqtn.substr(lastDotPos + 1);
return { pkg: pkg, typeName: typeName };
}
async function copyToClipboard(elem, button) {
// cache the selector to avoid unnessessary overhead
let $codeElem = $(elem);
let $btnElem = $(button);
// grab the first elements text content
let text = $codeElem.contents().get(0).nodeValue;
await navigator.clipboard.writeText(text);
// visual feedback that task is completed
$btnElem.next().fadeIn(500).fadeTo(1000, 1).delay(500).fadeOut(500);
}