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

View file

@ -1,12 +1,14 @@
$(function() { $(function () {
function hasPrefix(str, prefix) { function hasPrefix(str, prefix) {
if (!prefix) return true; if (!prefix) return true;
if (!str) return false; if (!str) return false;
return str.indexOf(prefix) === 0; return str.indexOf(prefix) === 0;
} }
// highlight current page in left nav // 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/")) { if (hasPrefix(window.location.pathname, "/docs/json/")) {
// as a special case, highlight the JSON structure link anywhere within it // as a special case, highlight the JSON structure link anywhere within it
$currentPageLink = $('main nav a[href="/docs/json/"]'); $currentPageLink = $('main nav a[href="/docs/json/"]');
@ -15,11 +17,17 @@ $(function() {
// as another special case, highlight the modules link anywhere within it // as another special case, highlight the modules link anywhere within it
$currentPageLink = $('main nav a[href="/docs/modules/"]'); $currentPageLink = $('main nav a[href="/docs/modules/"]');
} }
$currentPageLink.addClass('current'); $currentPageLink.addClass("current");
// add anchor links, inspired by https://github.com/bryanbraun/anchorjs // 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); $(this).append($anchor);
}); });
@ -29,32 +37,68 @@ $(function() {
// the <pre>; this line finds those and adds a .chroma class // the <pre>; this line finds those and adds a .chroma class
// to the outer pre element, and our CSS file has a style to // to the outer pre element, and our CSS file has a style to
// ensure the inner code block does not produce extra padding // 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. // Add links to Caddyfile directive tokens in code blocks.
// See include/docs-head.html for the whitelist bootstrapping logic // See include/docs-head.html for the whitelist bootstrapping logic
$('pre.chroma .k') $("pre.chroma .k")
.filter((k, item) => .filter(
window.CaddyfileDirectives.includes(item.innerText) (k, item) =>
|| item.innerText === '<directives...>' window.CaddyfileDirectives.includes(item.innerText) ||
item.innerText === "<directives...>"
) )
.map(function(k, item) { .map(function (k, item) {
let text = item.innerText; let text = item.innerText;
let url = text === '<directives...>' let url =
? '/docs/caddyfile/directives' text === "<directives...>"
: '/docs/caddyfile/directives/' + text; ? "/docs/caddyfile/directives"
text = text.replace(/</g,'&lt;').replace(/>/g,'&gt;'); : "/docs/caddyfile/directives/" + text;
$(item).html('<a href="' + url + '" style="color: inherit;" title="Directive">' + text + '</a>'); 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. // Add links to [<matcher>] or named matcher tokens in code blocks.
// The matcher text includes <> characters which are parsed as HTML, // The matcher text includes <> characters which are parsed as HTML,
// so we must use text() to change the link text. // so we must use text() to change the link text.
$('pre.chroma .nd') $("pre.chroma .nd").map(function (k, item) {
.map(function(k, item) { let text = item.innerText.replace(/</g, "&lt;").replace(/>/g, "&gt;");
let text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;'); $(item).html(
$(item).html('<a href="/docs/caddyfile/matchers#syntax" style="color: inherit;" title="Matcher token">' + text + '</a>'); '<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 // 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, // links that have an ID on the page. This is opt-in for each page,
// because it's not necessary to run everywhere. // because it's not necessary to run everywhere.
function addLinksToSubdirectives() { function addLinksToSubdirectives() {
let anchors = $('article *[id]').map((i, el) => el.id).toArray(); let anchors = $("article *[id]")
$('pre.chroma .k') .map((i, el) => el.id)
.toArray();
$("pre.chroma .k")
.filter((k, item) => anchors.includes(item.innerText)) .filter((k, item) => anchors.includes(item.innerText))
.map(function(k, item) { .map(function (k, item) {
let text = item.innerText.replace(/</g,'&lt;').replace(/>/g,'&gt;'); let text = item.innerText
let url = '#' + item.innerText; .replace(/</g, "&lt;")
$(item).html('<a href="' + url + '" style="color: inherit;" title="' + text + '">' + text + '</a>'); .replace(/>/g, "&gt;");
let url = "#" + item.innerText;
$(item).html(
'<a href="' +
url +
'" style="color: inherit;" title="' +
text +
'">' +
text +
"</a>"
);
}); });
} }
function stripScheme(url) { function stripScheme(url) {
return url.substring(url.indexOf("://")+3); return url.substring(url.indexOf("://") + 3);
} }
// splitTypeName splits a fully qualified type name into // splitTypeName splits a fully qualified type name into
// its package path and type name components, for example: // its package path and type name components, for example:
// "github.com/foo/bar.Type" => "github.com/foo/bar" and "Type". // "github.com/foo/bar.Type" => "github.com/foo/bar" and "Type".
function splitTypeName(fqtn) { function splitTypeName(fqtn) {
let lastDotPos = fqtn.lastIndexOf('.'); let lastDotPos = fqtn.lastIndexOf(".");
let pkg = fqtn.substr(0, lastDotPos); let pkg = fqtn.substr(0, lastDotPos);
let typeName = fqtn.substr(lastDotPos+1); let typeName = fqtn.substr(lastDotPos + 1);
return {pkg: pkg, typeName: typeName}; 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);
} }