feat: codeblock improvements (#1328)

* fix: do not show codeblock copy button if clipboard api is not available

related: https://github.com/CaiJimmy/hugo-theme-stack/issues/1309

* fix: remove duplicated codeblock script in main.ts

* feat: add i18n support to codeblock copy button

* feat: add translation strings for codeblock copy button
This commit is contained in:
Jimmy
2026-04-26 13:40:46 +02:00
committed by GitHub
parent 9ddd11797b
commit 08a8b5d767
35 changed files with 181 additions and 34 deletions
+41
View File
@@ -0,0 +1,41 @@
import * as params from '@params';
export function setupCodeCopy() {
/**
* Add copy button to code block
*/
const highlights = document.querySelectorAll('.article-content div.highlight');
const copyText = params.codeblock.copy,
copiedText = params.codeblock.copied;
if (!navigator.clipboard) {
/// Clipboard API is only supported in secure contexts (HTTPS)
console.warn('Clipboard API not supported, copy button will not work.');
return;
}
highlights.forEach(highlight => {
const copyButton = document.createElement('button');
copyButton.innerHTML = copyText;
copyButton.classList.add('copyCodeButton');
highlight.appendChild(copyButton);
const codeBlock = highlight.querySelector('code[data-lang]');
if (!codeBlock) return;
copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(codeBlock.textContent)
.then(() => {
copyButton.textContent = copiedText;
setTimeout(() => {
copyButton.textContent = copyText;
}, 1000);
})
.catch(err => {
alert(err)
console.log('Something went wrong', err);
});
});
});
};
+2 -32
View File
@@ -11,6 +11,7 @@ import StackColorScheme from './colorScheme';
import { setupScrollspy } from './scrollspy';
import { setupSmoothAnchors } from './smoothAnchors';
import { setupPaginationJump } from './pagination';
import { setupCodeCopy } from './code-copy';
let Stack = {
init: () => {
@@ -23,42 +24,11 @@ let Stack = {
if (articleContent) {
setupSmoothAnchors();
setupScrollspy();
setupCodeCopy();
}
setupPaginationJump();
/**
* Add copy button to code block
*/
const highlights = document.querySelectorAll('.article-content div.highlight');
const copyText = `Copy`,
copiedText = `Copied!`;
highlights.forEach(highlight => {
const copyButton = document.createElement('button');
copyButton.innerHTML = copyText;
copyButton.classList.add('copyCodeButton');
highlight.appendChild(copyButton);
const codeBlock = highlight.querySelector('code[data-lang]');
if (!codeBlock) return;
copyButton.addEventListener('click', () => {
navigator.clipboard.writeText(codeBlock.textContent)
.then(() => {
copyButton.textContent = copiedText;
setTimeout(() => {
copyButton.textContent = copyText;
}, 1000);
})
.catch(err => {
alert(err)
console.log('Something went wrong', err);
});
});
});
new StackColorScheme(document.getElementById('dark-mode-toggle')!);
}
}
+4
View File
@@ -29,6 +29,10 @@ darkMode = "الوضع الداكن"
warning = "تحذير"
caution = "تنبيه"
[article.codeblock]
copy = "نسخ"
copied = "تم النسخ!"
[notFound]
title = "غير موجود"
subtitle = "تعذر العثور على الصفحة المطلوبة."
+4
View File
@@ -30,6 +30,10 @@ darkMode = "Цёмны рэжым"
warning = "Папярэджанне"
caution = "Асцярога"
[article.codeblock]
copy = "Капіяваць"
copied = "Скапіявана!"
[notFound]
title = "Не знойдзена"
subtitle = "Запытваемая старонка не існуе"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Тъмен Режим"
warning = "Предупреждение"
caution = "Внимание"
[article.codeblock]
copy = "Копирай"
copied = "Копирано!"
[notFound]
title = "Не е намерено"
subtitle = "Страницата която търсите не е открита"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "ডার্ক মোড"
warning = "সতর্কতা"
caution = "সাবধানতা"
[article.codeblock]
copy = "কপি"
copied = "কপি হয়েছে!"
[notFound]
title = "পাওয়া যায়নি"
subtitle = "এই পাতাটি বিদ্যমান নেই"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Mode fosc"
warning = "Avís"
caution = "Precaució"
[article.codeblock]
copy = "Copia"
copied = "Copiat!"
[notFound]
title = "No Trobat"
subtitle = "Aquesta pàgina no existeix"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Tmavý režim"
warning = "Varování"
caution = "Upozornění"
[article.codeblock]
copy = "Kopírovat"
copied = "Zkopírováno!"
[notFound]
title = "Nenalezeno"
subtitle = "Tato stránka neexistuje"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Dunkler Modus"
warning = "Warnung"
caution = "Vorsicht"
[article.codeblock]
copy = "Kopieren"
copied = "Kopiert!"
[notFound]
title = "Seite nicht gefunden"
subtitle = "Diese Seite existiert nicht"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Σκοτεινό θέμα"
warning = "Προειδοποίηση"
caution = "Προσοχή"
[article.codeblock]
copy = "Αντιγραφή"
copied = "Αντιγράφηκε!"
[notFound]
title = "Δε βρέθηκε"
subtitle = "Η σελίδα δε βρέθηκε."
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Dark Mode"
warning = "Warning"
caution = "Caution"
[article.codeblock]
copy = "Copy"
copied = "Copied!"
[notFound]
title = "Not Found"
subtitle = "This page does not exist"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Modo oscuro"
warning = "Advertencia"
caution = "Precaución"
[article.codeblock]
copy = "Copiar"
copied = "¡Copiado!"
[notFound]
title = "No Encontrado"
subtitle = "Esta página no existe"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "حالت شب"
warning = "هشدار"
caution = "احتیاط"
[article.codeblock]
copy = "کپی"
copied = "کپی شد!"
[notFound]
title = "یافت نشد"
subtitle = "این صحه وجود ندارد"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Mode sombre"
warning = "Avertissement"
caution = "Attention"
[article.codeblock]
copy = "Copier"
copied = "Copié !"
[notFound]
title = "Page non trouvée"
subtitle = "Cette page n'existe pas."
+4
View File
@@ -29,6 +29,10 @@ darkMode = "डार्क मोड"
warning = "चेतावनी"
caution = "सावधानी"
[article.codeblock]
copy = "कॉपी"
copied = "कॉपी हो गया!"
[notFound]
title = "404 नहीं मिला।"
subtitle = "यह पृष्ठ मौजूद नहीं है।"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Sötét Mód"
warning = "Figyelmeztetés"
caution = "Óvatosság"
[article.codeblock]
copy = "Másolás"
copied = "Másolva!"
[notFound]
title = "Nem található"
subtitle = "Ez az oldal nem létezik"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Mode Gelap"
warning = "Peringatan"
caution = "Perhatian"
[article.codeblock]
copy = "Salin"
copied = "Disalin!"
[notFound]
title = "Tidak ditemukan"
subtitle = "Halaman yang Anda akses tidak ditemukan."
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Modalità scura"
warning = "Avvertenza"
caution = "Attenzione"
[article.codeblock]
copy = "Copia"
copied = "Copiato!"
[notFound]
title = "Non trovato"
subtitle = "Questa pagina non esiste."
+4
View File
@@ -20,6 +20,10 @@ darkMode = "ダークモード"
warning = "警告"
caution = "注意"
[article.codeblock]
copy = "コピー"
copied = "コピーしました!"
[notFound]
title = "404 Not Found"
subtitle = "指定されたページは存在しません。"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "다크 모드"
warning = "경고"
caution = "주의"
[article.codeblock]
copy = "복사"
copied = "복사됨!"
[notFound]
title = "찾을 수 없음"
subtitle = "페이지를 찾을 수 없습니다."
+4
View File
@@ -24,6 +24,10 @@ darkMode = "Donkere modus"
warning = "Waarschuwing"
caution = "Let op"
[article.codeblock]
copy = "Kopiëren"
copied = "Gekopieerd!"
[notFound]
title = "Niet gevonden"
subtitle = "Deze pagina bestaat niet."
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Mòde fosc"
warning = "Avertiment"
caution = "Atencion"
[article.codeblock]
copy = "Copiar"
copied = "Copiat!"
[notFound]
title = "Non trobat"
subtitle = "Aquesta pagina existís pas"
+4
View File
@@ -35,6 +35,10 @@ darkMode = "Tryb ciemny"
warning = "Ostrzeżenie"
caution = "Uwaga"
[article.codeblock]
copy = "Kopiuj"
copied = "Skopiowano!"
[notFound]
title = "Nie znaleziono"
subtitle = "Ta strona nie istnieje"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Modo Escuro"
warning = "Aviso"
caution = "Cuidado"
[article.codeblock]
copy = "Copiar"
copied = "Copiado!"
[notFound]
title = "Não Encontrado"
subtitle = "Esta página não existe."
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Modo Escuro"
warning = "Aviso"
caution = "Cuidado"
[article.codeblock]
copy = "Copiar"
copied = "Copiado!"
[notFound]
title = "Não Encontrado"
subtitle = "Esta página não existe."
+4
View File
@@ -30,6 +30,10 @@ darkMode = "Тёмный режим"
warning = "Предупреждение"
caution = "Осторожно"
[article.codeblock]
copy = "Копировать"
copied = "Скопировано!"
[notFound]
title = "Не найдено"
subtitle = "Запрашиваемая страница не существует"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Tmavý režim"
warning = "Varovanie"
caution = "Upozornenie"
[article.codeblock]
copy = "Kopírovať"
copied = "Skopírované!"
[notFound]
title = "Nenájdené"
subtitle = "Tato stránka neexistuje"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "ธีมมืด"
warning = "คำเตือน"
caution = "ระวัง"
[article.codeblock]
copy = "คัดลอก"
copied = "คัดลอกแล้ว!"
[notFound]
title = "ไม่พบหัวข้อ"
subtitle = "ไม่พบหน้านี้ในระบบ"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Koyu Mod"
warning = "Uyarı"
caution = "Dikkat"
[article.codeblock]
copy = "Kopyala"
copied = "Kopyalandı!"
[notFound]
title = "Bulunamadı"
subtitle = "Aradığınız sayfa mevcut değil."
+4
View File
@@ -30,6 +30,10 @@ darkMode = "Темна тема"
warning = "Попередження"
caution = "Обережно"
[article.codeblock]
copy = "Копіювати"
copied = "Скопійовано!"
[notFound]
title = "Не знайдено"
subtitle = "Ця сторінка не існує"
+4
View File
@@ -29,6 +29,10 @@ darkMode = "Chế độ nền tối"
warning = "Cảnh báo"
caution = "Thận trọng"
[article.codeblock]
copy = "Sao chép"
copied = "Đã sao chép!"
[notFound]
title = "Không tìm thấy"
subtitle = "Trang này không tồn tại"
+4
View File
@@ -20,6 +20,10 @@ darkMode = "深色模式"
warning = "警告"
caution = "注意"
[article.codeblock]
copy = "複製"
copied = "已複製!"
[notFound]
title = "找不到頁面"
subtitle = "本頁面並不存在"
+4
View File
@@ -20,6 +20,10 @@ darkMode = "夜晚模式"
warning = "警告"
caution = "注意"
[article.codeblock]
copy = "複製"
copied = "已複製!"
[notFound]
title = "404 錯誤"
subtitle = "頁面不存在"
+4
View File
@@ -20,6 +20,10 @@ darkMode = "暗色模式"
warning = "警告"
caution = "注意"
[article.codeblock]
copy = "复制"
copied = "已复制!"
[notFound]
title = "404 错误"
subtitle = "页面不存在"
@@ -1,4 +1,12 @@
{{- $opts := dict "minify" hugo.IsProduction -}}
{{- $opts := dict
"minify" hugo.IsProduction
"params" (dict
"codeblock" (dict
"copy" (i18n "article.codeblock.copy")
"copied" (i18n "article.codeblock.copied")
)
)
-}}
{{- $script := resources.Get "ts/main.ts" | js.Build $opts | fingerprint -}}
<script type="text/javascript" src="{{ $script.RelPermalink }}" defer></script>
@@ -7,4 +15,4 @@
{{/* Place your custom script in HUGO_SITE_FOLDER/assets/ts/custom.ts */}}
{{- $customScript := . | js.Build $opts | fingerprint -}}
<script type="text/javascript" src="{{ $customScript.RelPermalink }}" defer></script>
{{- end -}}
{{- end -}}