diff options
| author | Danilo M. <danix@danix.xyz> | 2026-04-18 19:02:18 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-04-18 19:02:18 +0200 |
| commit | 17048ab79312f1752a296ab150984a4ef30aed5c (patch) | |
| tree | 732a7f1e6cf78d0d75e737848ad9328df7a8dd77 /themes/danix-xyz-hacker/assets/js/code-copy.js | |
| parent | 46779476a570346661a2741607265caed42829b2 (diff) | |
| download | danixxyz-17048ab79312f1752a296ab150984a4ef30aed5c.tar.gz danixxyz-17048ab79312f1752a296ab150984a4ef30aed5c.zip | |
refactor: syntax highlighting with Catppuccin Macchiato and copy buttons
- Add [markup.highlight] config: noClasses=false for CSS class output, lineNos=true with lineNumbersInTable=true for proper line number rendering
- Create render-codeblock.html render hook to intercept fenced code blocks and wrap with header bar (language label + copy button)
- Replace chroma-custom.css entirely with Catppuccin Macchiato palette (dark theme) + Catppuccin Latte (light theme), with full token color mapping
- Create code-copy.js: copy-to-clipboard logic with language pretty-name map (bash→Shell, js→JavaScript, etc.), icon swap (copy→check for 2s), and aria-live region for screen reader announcement (WCAG 4.1.3)
- Update baseof.html to load code-copy.js on page kind with Hugo Pipes
- WCAG AA compliance: line number contrast fixed to ~3.5:1 (--ctp-overlay0), light theme copy button color to 4.1:1 (#6c6f85), focus outline 6.21:1 (--ctp-lavender), screen reader announcements via aria-live
All code blocks now render with: syntax highlighting (noClasses=true fixed), line numbers with proper table layout, language label in header, copy button with feather icons, both dark and light theme support.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Diffstat (limited to 'themes/danix-xyz-hacker/assets/js/code-copy.js')
| -rw-r--r-- | themes/danix-xyz-hacker/assets/js/code-copy.js | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/themes/danix-xyz-hacker/assets/js/code-copy.js b/themes/danix-xyz-hacker/assets/js/code-copy.js new file mode 100644 index 0000000..bfcfd4a --- /dev/null +++ b/themes/danix-xyz-hacker/assets/js/code-copy.js @@ -0,0 +1,79 @@ +(function () { + var LANG_NAMES = { + bash: 'Shell', sh: 'Shell', shell: 'Shell', zsh: 'Shell', + js: 'JavaScript', javascript: 'JavaScript', + ts: 'TypeScript', typescript: 'TypeScript', + go: 'Go', + py: 'Python', python: 'Python', + rs: 'Rust', rust: 'Rust', + html: 'HTML', + css: 'CSS', + toml: 'TOML', + yaml: 'YAML', yml: 'YAML', + json: 'JSON', + sql: 'SQL', + md: 'Markdown', markdown: 'Markdown', + c: 'C', + cpp: 'C++', 'c++': 'C++', + java: 'Java', + php: 'PHP', + ruby: 'Ruby', rb: 'Ruby', + swift: 'Swift', + kotlin: 'Kotlin', kt: 'Kotlin', + dockerfile: 'Dockerfile', + makefile: 'Makefile', + text: 'Text', txt: 'Text', + }; + + function prettyName(lang) { + if (!lang) return ''; + var key = lang.toLowerCase(); + return LANG_NAMES[key] || (lang.charAt(0).toUpperCase() + lang.slice(1)); + } + + function getCodeText(wrapper) { + var el = wrapper.querySelector('.lntd:last-child code') + || wrapper.querySelector('.code-body code') + || wrapper.querySelector('.code-body pre'); + return el ? el.innerText : ''; + } + + function initBlock(wrapper) { + var header = wrapper.querySelector('.code-header'); + if (header) { + var label = wrapper.querySelector('.code-lang-label'); + if (label) label.textContent = prettyName(header.getAttribute('data-lang') || ''); + } + + var btn = wrapper.querySelector('[data-copy-target]'); + if (!btn) return; + + btn.addEventListener('click', function () { + var text = getCodeText(wrapper); + if (!text) return; + + navigator.clipboard.writeText(text).then(function () { + var copyIcon = btn.querySelector('[data-feather="copy"]'); + var checkIcon = btn.querySelector('[data-feather="check"]'); + var liveRegion = wrapper.querySelector('.code-copy-status'); + if (copyIcon) copyIcon.style.display = 'none'; + if (checkIcon) checkIcon.classList.remove('hidden'); + btn.classList.add('is-copied'); + if (liveRegion) liveRegion.textContent = 'Code copied to clipboard.'; + + setTimeout(function () { + if (copyIcon) copyIcon.style.display = ''; + if (checkIcon) checkIcon.classList.add('hidden'); + btn.classList.remove('is-copied'); + if (liveRegion) liveRegion.textContent = ''; + }, 2000); + }).catch(function () { + // silent fail for insecure contexts + }); + }); + } + + document.addEventListener('DOMContentLoaded', function () { + document.querySelectorAll('.code-block-wrapper').forEach(initBlock); + }); +})(); |
