2022-12-05 (月)
[Hugo] コードブロックをコピーするボタンを配置する
Hugo の Markdown で書いた記事内のコードブロックに対して、クリップボードにコピーするボタンを動的に配置する方法です。
環境
- Hugo 0.106.0
- Windows 10 Pro 64bit 22H2 19045.2251
結果
public void Copy()
{
// ここにカーソルを当てると (もしくはタッチすると)、右上にクリップボードにコピーするボタンが表示されます。
}
注意点
linenos=inline は未対応
linenos=inline
形式のコピーは未対応です。行番号とコードが混在してコピーされます。linenos=table
形式なら問題ありません。
例えば、以下のように行番号を表示した場合です。
1```go {linenos=inline}
2// ... code
3```
ハイライト言語の指定が必須
ハイライト言語を指定しないと、コピーボタンが表示されません。
```
// ... code
```
特に指定がない場合は、text
を指定すれば、コピーボタンが表示されます。
```text
// ... code
```
実装
主にこちらを参考にしました。
https://aaronluna.dev/blog/add-copy-button-to-code-blocks-hugo-chroma/
以下の点を修正しています。
- マウスオーバー (スマホならタップ) した場合のみ、コピーボタンが表示されるように変更。
highlight-wrapper
クラスを廃止。
copy-code-button.js
コードブロックに動的にボタンを追加する js です。
function createCopyButton(highlightDiv) {
const button = document.createElement("button");
button.className = "copy-code-button";
button.type = "button";
button.innerText = "Copy";
button.addEventListener("click", () => copyCodeToClipboard(button, highlightDiv));
addCopyButtonToDom(button, highlightDiv);
}
async function copyCodeToClipboard(button, highlightDiv) {
const codeToCopy = highlightDiv.querySelector("pre.chroma > code[data-lang]").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 = "copyable-text-area";
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 = "Copied!";
setTimeout(function() {
button.innerText = "Copy";
}, 2000);
}
function addCopyButtonToDom(button, highlightDiv) {
highlightDiv.appendChild(button);
}
document.querySelectorAll(".highlight")
.forEach(highlightDiv => createCopyButton(highlightDiv));
copy-code-button.css
コピーボタンを配置するための .css です。
.highlight {
position: relative;
}
.copy-code-button {
position: absolute;
z-index: 2;
right: 0;
top: 0;
font-size: 13px;
font-weight: 700;
line-height: 14px;
letter-spacing: 0.5px;
width: 65px;
color: #232326;
background-color: #7f7f7f;
border: 1.25px solid #232326;
border-top-left-radius: 0;
border-top-right-radius: 4px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 4px;
white-space: nowrap;
padding: 4px 4px 5px 4px;
margin: 0 0 0 1px;
cursor: pointer;
opacity: 0.6;
}
.copy-code-button:hover,
.copy-code-button:focus,
.copy-code-button:active,
.copy-code-button:active:hover {
color: #222225;
background-color: #b3b3b3;
opacity: 0.8;
}
.copyable-text-area {
position: absolute;
height: 0;
z-index: -1;
opacity: .01;
}
.highlight .copy-code-button {
visibility: hidden;
}
.highlight:hover .copy-code-button {
visibility: visible;
}
baseof.html
上記を適用するテンプレートの .html 例です。
この例では、minify, fingerprint などは考慮しません。
<html>
<head>
{{ if (findRE "<pre" .Content 1) }}
<link rel="stylesheet" href="xxx/copy-button.min.css" integrity="xxx=" media="screen">
{{{ end }}}
</head>
<body>
{{ if (findRE "<pre" .Content 1) }}
<script async src="xxx/copy-code-button.min.js"></script>
{{ end }}
</body>
</html>
感謝
- 実装本体
- pre タグがある場合のみ実行
- Support
- Issue 参照
関連記事
新着記事