mirror of https://github.com/jpanther/congo.git
✨ Add code copy buttons
parent
47632533e0
commit
add3f764f7
|
@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
- Automatic Markdown image resizing and srcset generation
|
- Automatic Markdown image resizing and srcset generation
|
||||||
- Performance and Accessibility improvements to achieve perfect Lighthouse scores
|
- Performance and Accessibility improvements to achieve perfect Lighthouse scores
|
||||||
- Tables of Contents on article pages
|
- Tables of Contents on article pages
|
||||||
|
- Code copy buttons in article content
|
||||||
- Taxonomy and term listings now support Markdown content
|
- Taxonomy and term listings now support Markdown content
|
||||||
- Taxonomies on article and list pages
|
- Taxonomies on article and list pages
|
||||||
- Article pagination direction can be inverted
|
- Article pagination direction can be inverted
|
||||||
|
|
|
@ -25,7 +25,7 @@ Congo is designed to be a powerful, lightweight theme for [Hugo](https://gohugo.
|
||||||
- Mathematical notation using KaTeX
|
- Mathematical notation using KaTeX
|
||||||
- SVG icons from FontAwesome 5
|
- SVG icons from FontAwesome 5
|
||||||
- Automatic image resizing using Hugo Pipes
|
- Automatic image resizing using Hugo Pipes
|
||||||
- Heading anchors, Tables of Contents, Buttons, Badges and more
|
- Heading anchors, Tables of Contents, Code copy, Buttons, Badges and more
|
||||||
- HTML and Emoji support in articles 🎉
|
- HTML and Emoji support in articles 🎉
|
||||||
- SEO friendly with links for sharing to social media
|
- SEO friendly with links for sharing to social media
|
||||||
- Fathom Analytics and Google Analytics support
|
- Fathom Analytics and Google Analytics support
|
||||||
|
|
|
@ -1022,11 +1022,73 @@ body a, body button {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Code Copy */
|
||||||
|
|
||||||
|
.highlight-wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight:hover > .copy-button {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 10;
|
||||||
|
width: 5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
border-bottom-left-radius: 0.375rem;
|
||||||
|
border-top-right-radius: 0.375rem;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(var(--color-neutral-200), var(--tw-bg-opacity));
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgba(var(--color-neutral-700), var(--tw-text-opacity));
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .copy-button {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(var(--color-neutral-600), var(--tw-bg-opacity));
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgba(var(--color-neutral-200), var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover, .copy-button:focus, .copy-button:active, .copy-button:active:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(var(--color-primary-100), var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .copy-button:hover, .dark .copy-button:focus, .dark .copy-button:active, .dark .copy-button:active:hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgba(var(--color-primary-600), var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-textarea {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -10;
|
||||||
|
opacity: 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
/* -- Chroma Highlight -- */
|
/* -- Chroma Highlight -- */
|
||||||
|
|
||||||
/* Background */
|
/* Background */
|
||||||
|
|
||||||
.prose .chroma {
|
.prose .chroma {
|
||||||
|
position: static;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgba(var(--color-neutral-50), var(--tw-bg-opacity));
|
background-color: rgba(var(--color-neutral-50), var(--tw-bg-opacity));
|
||||||
|
|
|
@ -77,10 +77,33 @@ body button {
|
||||||
@apply rtl:mr-0;
|
@apply rtl:mr-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Code Copy */
|
||||||
|
.highlight-wrapper {
|
||||||
|
@apply block;
|
||||||
|
}
|
||||||
|
.highlight {
|
||||||
|
@apply relative z-0;
|
||||||
|
}
|
||||||
|
.highlight:hover > .copy-button {
|
||||||
|
@apply visible;
|
||||||
|
}
|
||||||
|
.copy-button {
|
||||||
|
@apply absolute top-0 right-0 z-10 invisible w-20 py-1 font-mono text-sm cursor-pointer opacity-90 bg-neutral-200 whitespace-nowrap rounded-bl-md rounded-tr-md text-neutral-700 dark:bg-neutral-600 dark:text-neutral-200;
|
||||||
|
}
|
||||||
|
.copy-button:hover,
|
||||||
|
.copy-button:focus,
|
||||||
|
.copy-button:active,
|
||||||
|
.copy-button:active:hover {
|
||||||
|
@apply bg-primary-100 dark:bg-primary-600;
|
||||||
|
}
|
||||||
|
.copy-textarea {
|
||||||
|
@apply absolute opacity-5 -z-10;
|
||||||
|
}
|
||||||
|
|
||||||
/* -- Chroma Highlight -- */
|
/* -- Chroma Highlight -- */
|
||||||
/* Background */
|
/* Background */
|
||||||
.prose .chroma {
|
.prose .chroma {
|
||||||
@apply rounded-md text-neutral-700 bg-neutral-50 dark:bg-neutral-700 dark:text-neutral-200;
|
@apply static rounded-md text-neutral-700 bg-neutral-50 dark:bg-neutral-700 dark:text-neutral-200;
|
||||||
}
|
}
|
||||||
/* LineTableTD */
|
/* LineTableTD */
|
||||||
.chroma .lntd,
|
.chroma .lntd,
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
var codeLang = document.getElementById("code-lang");
|
||||||
|
var copyText = codeLang ? codeLang.getAttribute("data-copy") : "Copy";
|
||||||
|
var copiedText = codeLang ? codeLang.getAttribute("data-copied") : "Copied";
|
||||||
|
|
||||||
|
function createCopyButton(highlightDiv) {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.className = "copy-button";
|
||||||
|
button.type = "button";
|
||||||
|
button.innerText = copyText;
|
||||||
|
button.addEventListener("click", () => copyCodeToClipboard(button, highlightDiv));
|
||||||
|
addCopyButtonToDom(button, highlightDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyCodeToClipboard(button, highlightDiv) {
|
||||||
|
const codeToCopy = highlightDiv.querySelector(":last-child > .chroma > code").innerText;
|
||||||
|
try {
|
||||||
|
result = await navigator.permissions.query({ name: "clipboard-write" });
|
||||||
|
if (result.state == "granted" || result.state == "prompt") {
|
||||||
|
await navigator.clipboard.writeText(codeToCopy);
|
||||||
|
} else {
|
||||||
|
copyCodeBlockExecCommand(codeToCopy, highlightDiv);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
copyCodeBlockExecCommand(codeToCopy, highlightDiv);
|
||||||
|
} finally {
|
||||||
|
codeWasCopied(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyCodeBlockExecCommand(codeToCopy, highlightDiv) {
|
||||||
|
const textArea = document.createElement("textArea");
|
||||||
|
textArea.contentEditable = "true";
|
||||||
|
textArea.readOnly = "false";
|
||||||
|
textArea.className = "copy-textarea";
|
||||||
|
textArea.value = codeToCopy;
|
||||||
|
highlightDiv.insertBefore(textArea, highlightDiv.firstChild);
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(textArea);
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
textArea.setSelectionRange(0, 999999);
|
||||||
|
document.execCommand("copy");
|
||||||
|
highlightDiv.removeChild(textArea);
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeWasCopied(button) {
|
||||||
|
button.blur();
|
||||||
|
button.innerText = copiedText;
|
||||||
|
setTimeout(function () {
|
||||||
|
button.innerText = copyText;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCopyButtonToDom(button, highlightDiv) {
|
||||||
|
highlightDiv.insertBefore(button, highlightDiv.firstChild);
|
||||||
|
const wrapper = document.createElement("div");
|
||||||
|
wrapper.className = "highlight-wrapper";
|
||||||
|
highlightDiv.parentNode.insertBefore(wrapper, highlightDiv);
|
||||||
|
wrapper.appendChild(highlightDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", (event) => {
|
||||||
|
document.querySelectorAll(".highlight").forEach((highlightDiv) => createCopyButton(highlightDiv));
|
||||||
|
});
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
colorScheme = "congo"
|
colorScheme = "congo"
|
||||||
enableSearch = false
|
enableSearch = false
|
||||||
|
enableCodeCopy = false
|
||||||
darkMode = "auto"
|
darkMode = "auto"
|
||||||
# logo = "img/logo.jpg"
|
# logo = "img/logo.jpg"
|
||||||
# mainSections = ["section1", "section2"]
|
# mainSections = ["section1", "section2"]
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
colorScheme = "congo"
|
colorScheme = "congo"
|
||||||
enableSearch = true
|
enableSearch = true
|
||||||
|
enableCodeCopy = true
|
||||||
darkMode = "auto"
|
darkMode = "auto"
|
||||||
# logo = "img/logo.jpg"
|
# logo = "img/logo.jpg"
|
||||||
mainSections = ["samples"]
|
mainSections = ["samples"]
|
||||||
|
|
|
@ -100,6 +100,7 @@ Many of the article defaults here can be overridden on a per article basis by sp
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|`colorScheme`|`"congo"`|The theme colour scheme to use. Valid values are `congo` (default), `avocado`, `ocean`, `fire` and `slate`. Refer to the [Colour Schemes]({{< ref "getting-started#colour-schemes" >}}) section for more details.|
|
|`colorScheme`|`"congo"`|The theme colour scheme to use. Valid values are `congo` (default), `avocado`, `ocean`, `fire` and `slate`. Refer to the [Colour Schemes]({{< ref "getting-started#colour-schemes" >}}) section for more details.|
|
||||||
|`enableSearch`|`false`|Whether site search is enabled. Set to `true` to enable search functionality. Note that the search feature depends on the `outputs.home` setting in the [site configuration](#site-configuration) being set correctly.|
|
|`enableSearch`|`false`|Whether site search is enabled. Set to `true` to enable search functionality. Note that the search feature depends on the `outputs.home` setting in the [site configuration](#site-configuration) being set correctly.|
|
||||||
|
|`enableCodeCopy`|`false`|Whether copy buttons are enabled for `<code>` blocks.|
|
||||||
|`darkMode`|`"auto"`|The preferred theme appearance for dark mode. Set to `true` to force dark appearance or `false` to force light appearance. Using `"auto"` will defer to the user's operating system preference.|
|
|`darkMode`|`"auto"`|The preferred theme appearance for dark mode. Set to `true` to force dark appearance or `false` to force light appearance. Using `"auto"` will defer to the user's operating system preference.|
|
||||||
|`logo`|_Not set_|The relative path to the site logo file within the `assets/` folder. The logo file should be provided at 2x resolution and supports any image dimensions.|
|
|`logo`|_Not set_|The relative path to the site logo file within the `assets/` folder. The logo file should be provided at 2x resolution and supports any image dimensions.|
|
||||||
|`mainSections`|_Not set_|The sections that should be displayed in the recent articles list. If not provided the section with the greatest number of articles is used.|
|
|`mainSections`|_Not set_|The sections that should be displayed in the recent articles list. If not provided the section with the greatest number of articles is used.|
|
||||||
|
|
|
@ -16,6 +16,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Autor"
|
byline_title: "Autor"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Seite nicht gefunden :confused:"
|
404_title: "Seite nicht gefunden :confused:"
|
||||||
404_error: "Fehler 404"
|
404_error: "Fehler 404"
|
||||||
|
|
|
@ -16,6 +16,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Author"
|
byline_title: "Author"
|
||||||
|
|
||||||
|
code:
|
||||||
|
copy: "Copy"
|
||||||
|
copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Page Not Found :confused:"
|
404_title: "Page Not Found :confused:"
|
||||||
404_error: "Error 404"
|
404_error: "Error 404"
|
||||||
|
|
|
@ -16,6 +16,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Autor"
|
byline_title: "Autor"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Página no encontrada :confused:"
|
404_title: "Página no encontrada :confused:"
|
||||||
404_error: "Error 404"
|
404_error: "Error 404"
|
||||||
|
|
|
@ -16,6 +16,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Auteur"
|
byline_title: "Auteur"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Cette page n'existe pas :confused:"
|
404_title: "Cette page n'existe pas :confused:"
|
||||||
404_error: "Erreur 404"
|
404_error: "Erreur 404"
|
||||||
|
|
|
@ -16,6 +16,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Autor"
|
byline_title: "Autor"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Página não econtrada :confused:"
|
404_title: "Página não econtrada :confused:"
|
||||||
404_error: "Erro 404"
|
404_error: "Erro 404"
|
||||||
|
|
|
@ -15,6 +15,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "Yazar"
|
byline_title: "Yazar"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "Sayfa Bulunamadı :confused:"
|
404_title: "Sayfa Bulunamadı :confused:"
|
||||||
404_error: "Hata 404"
|
404_error: "Hata 404"
|
||||||
|
|
|
@ -15,6 +15,10 @@ article:
|
||||||
author:
|
author:
|
||||||
byline_title: "作者"
|
byline_title: "作者"
|
||||||
|
|
||||||
|
# code:
|
||||||
|
# copy: "Copy"
|
||||||
|
# copied: "Copied"
|
||||||
|
|
||||||
error:
|
error:
|
||||||
404_title: "找不到网页 :confused:"
|
404_title: "找不到网页 :confused:"
|
||||||
404_error: "404 错误"
|
404_error: "404 错误"
|
||||||
|
|
|
@ -59,8 +59,14 @@
|
||||||
{{ $jsSearch := resources.Get "js/search.js" }}
|
{{ $jsSearch := resources.Get "js/search.js" }}
|
||||||
{{ $assets.Add "js" (slice $jsFuse $jsSearch) }}
|
{{ $assets.Add "js" (slice $jsFuse $jsSearch) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Site.Params.enableCodeCopy | default false }}
|
||||||
|
{{ $jsCode := resources.Get "js/code.js" }}
|
||||||
|
{{ $assets.Add "js" (slice $jsCode) }}
|
||||||
|
<script type="application/json" id="code-lang" data-copy="{{ i18n "code.copy" }}" data-copied="{{ i18n "code.copied" }}"></script>
|
||||||
|
{{ end }}
|
||||||
{{ if $assets.Get "js" }}
|
{{ if $assets.Get "js" }}
|
||||||
{{ $bundleJS := $assets.Get "js" | resources.Concat "js/main.bundle.js" | resources.Minify | resources.Fingerprint "sha512" }} <script defer type="text/javascript" src="{{ $bundleJS.RelPermalink }}" integrity="{{ $bundleJS.Data.Integrity }}"></script>
|
{{ $bundleJS := $assets.Get "js" | resources.Concat "js/main.bundle.js" | resources.Minify | resources.Fingerprint "sha512" }}
|
||||||
|
<script defer type="text/javascript" src="{{ $bundleJS.RelPermalink }}" integrity="{{ $bundleJS.Data.Integrity }}"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{/* Icons */}}
|
{{/* Icons */}}
|
||||||
{{ if templates.Exists "partials/favicons.html" }}
|
{{ if templates.Exists "partials/favicons.html" }}
|
||||||
|
|
Loading…
Reference in New Issue