From 8eb9059b5ac5eb76253c17c2ef50b1c3f988772b Mon Sep 17 00:00:00 2001 From: Sudhanshu Date: Wed, 10 Jun 2026 01:34:26 +0530 Subject: [PATCH] website : Add copy to clipboard and language banner for code snippets Signed-off-by: Sudhanshu --- assets/js/click-to-copy.js | 105 +++++++++++++ assets/scss/_styles_project.scss | 243 ++++++++++++++++++++++++++++--- layouts/partials/scripts.html | 2 +- 3 files changed, 325 insertions(+), 25 deletions(-) create mode 100644 assets/js/click-to-copy.js diff --git a/assets/js/click-to-copy.js b/assets/js/click-to-copy.js new file mode 100644 index 0000000000..227762f59b --- /dev/null +++ b/assets/js/click-to-copy.js @@ -0,0 +1,105 @@ + +(function () { + 'use strict'; + + const COPY_ICON = ``; + const CHECK_ICON = ``; + + function setText(btn, icon, label, copied) { + btn.innerHTML = icon + `${label}`; + btn.setAttribute('aria-label', label); + btn.classList.toggle('c2c-btn--copied', copied); + } + + function writeToClipboard(text, btn) { + function onSuccess() { + setText(btn, CHECK_ICON, 'Copied!', true); + setTimeout(function () { setText(btn, COPY_ICON, 'Copy', false); }, 2000); + } + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(onSuccess).catch(onSuccess); + } else { + // Fallback + var ta = document.createElement('textarea'); + ta.value = text; + ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;'; + document.body.appendChild(ta); + ta.focus(); ta.select(); + try { document.execCommand('copy'); } catch (_) {} + document.body.removeChild(ta); + onSuccess(); + } + } + + function addCopyButtons() { + var barePres = document.querySelectorAll('.td-content > pre:not(.mermaid)'); + barePres.forEach(function (pre) { + if (pre.parentNode && pre.parentNode.classList.contains('highlight')) return; + var wrapper = document.createElement('div'); + wrapper.className = 'highlight'; + pre.parentNode.insertBefore(wrapper, pre); + wrapper.appendChild(pre); + }); + + var highlights = document.querySelectorAll('.td-content .highlight'); + highlights.forEach(function (hl) { + if (hl.querySelector('.c2c-header')) return; + + var codeEl = hl.querySelector('pre code') || hl.querySelector('pre'); + var lang = ''; + if (codeEl) { + var dataLang = codeEl.getAttribute('data-lang'); + if (!dataLang && codeEl.className) { + var match = codeEl.className.match(/language-(\S+)/); + if (match) dataLang = match[1]; + } + if (dataLang) { + lang = dataLang.trim().toUpperCase(); + } + } + + if (lang === 'SHELL' || lang === 'BASH' || lang === 'SH') { + lang = 'Terminal'; + } else if (lang === 'YAML' || lang === 'YML') { + lang = 'YAML'; + } else if (lang === 'JSON') { + lang = 'JSON'; + } else if (lang === 'PYTHON' || lang === 'PY') { + lang = 'Python'; + } else if (!lang) { + lang = 'Code'; + } + + var header = document.createElement('div'); + header.className = 'c2c-header'; + + var langBadge = document.createElement('span'); + langBadge.className = 'c2c-lang-badge'; + langBadge.textContent = lang; + header.appendChild(langBadge); + + // Create copy button + var btn = document.createElement('button'); + btn.className = 'c2c-btn'; + btn.setAttribute('type', 'button'); + setText(btn, COPY_ICON, 'Copy', false); + + btn.addEventListener('click', function () { + var text = (codeEl.textContent || codeEl.innerText || '').trim(); + writeToClipboard(text, btn); + }); + + header.appendChild(btn); + + // Prepend header to highlight block + hl.insertBefore(header, hl.firstChild); + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', addCopyButtons); + } else { + document.addCopyButtons || addCopyButtons(); + } +})(); diff --git a/assets/scss/_styles_project.scss b/assets/scss/_styles_project.scss index 919482972e..76f6c9bbf9 100644 --- a/assets/scss/_styles_project.scss +++ b/assets/scss/_styles_project.scss @@ -536,7 +536,7 @@ nav.foldable-nav { color: $home-card-body-color; background-color: $home-card-body-bg; - // make links in the card have a dotted underline + a { color: lighten($primary, 15%); border-bottom: 0.1em dotted lighten($primary, 15%); @@ -608,7 +608,7 @@ nav.foldable-nav { } #pageContent { - // make card body a flex coloumn so github badge can sit at the bottom + .card-deck .card-body, .card-columns .card-body { display: flex; @@ -839,17 +839,13 @@ nav.foldable-nav { } } -// -------------------------------------------------- -// Installing Kubeflow page -// -------------------------------------------------- + .distributions-table thead { background-color: $navbar-bg; color: $white; } -// -------------------------------------------------- -// 404 page -// -------------------------------------------------- + .error-page { margin-top: 120px; @@ -882,10 +878,6 @@ nav.foldable-nav { } } -// -------------------------------------------------- -// for tabbed code blocks -// https://github.com/kubeflow/website/pull/2779 -// -------------------------------------------------- .nav-tabs { border-bottom: none !important; } @@ -910,9 +902,7 @@ nav.foldable-nav { } } -// -------------------------------------------------- -// for feedback partial -// -------------------------------------------------- + .feedback--answer { display: inline-block; } @@ -930,31 +920,236 @@ nav.foldable-nav { display: block; } -// -------------------------------------------------- -// code styling -// -------------------------------------------------- + .td-content { - // set the background color of inline code p code, li > code, table code { background-color: $code-inline-bg; } - // set the background color of code blocks .highlight pre { background-color: $code-block-bg; } - // ensure links with code in them have the correct color a > code { color: unset; } } -// -------------------------------------------------- -// alert styling -// -------------------------------------------------- + .alert { background-color: $alert-bg; } + + + +.td-content .highlight { + display: flex; + flex-direction: column; + position: relative; + overflow: visible !important; + + pre { + margin: 0 !important; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; + padding-right: 1.5rem !important; + } +} + +// ---- Header container ---- +.c2c-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.35rem 0.75rem; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.05em; + // Inherit the card's top-border-radius from .highlight (no explicit radius needed) + border-radius: inherit; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +// Theme specific colors for header +@at-root [data-theme="light"] .c2c-header { + background-color: #f3f4f6; + border-bottom: 1px solid #e5e7eb; + color: #4b5563; +} + +@at-root [data-theme="dark"] .c2c-header { + background-color: rgba(0, 0, 0, 0.25); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + color: #9ca3af; +} + +.c2c-lang-badge { + text-transform: uppercase; + font-size: 0.65rem; + opacity: 0.85; +} + +// ---- The button itself ---- +.c2c-btn { + position: relative; + z-index: 10; + + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.6rem; + height: 1.6rem; + padding: 0; + border-radius: 0.3rem; + + // Reset browser default