website-zola/static/js/codeblock.js
2024-09-11 21:22:13 +02:00

104 lines
4.7 KiB
JavaScript

const successIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-check-lg" viewBox="0 0 16 16">
<path d="M13.485 1.85a.5.5 0 0 1 1.065.02.75.75 0 0 1-.02 1.065L5.82 12.78a.75.75 0 0 1-1.106.02L1.476 9.346a.75.75 0 1 1 1.05-1.07l2.74 2.742L12.44 2.92a.75.75 0 0 1 1.045-.07z"/>
</svg>`;
const errorIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-x-lg" viewBox="0 0 16 16">
<path d="M2.293 2.293a1 1 0 0 1 1.414 0L8 6.586l4.293-4.293a1 1 0 0 1 1.414 1.414L9.414 8l4.293 4.293a1 1 0 0 1-1.414 1.414L8 9.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L6.586 8 2.293 3.707a1 1 0 0 1 0-1.414z"/>
</svg>`;
const copyIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" class="bi bi-clipboard" viewBox="0 0 16 16">
<path d="M10 1.5a.5.5 0 0 1 .5-.5h2a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2V3a2 2 0 0 1 2-2h2a.5.5 0 0 1 .5.5V3h3V1.5zM6.5 3V2h3v1h-3zm4 0v1h2a1 1 0 0 0-1-1h-2V3zm-5 0H3a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H5.5V3z"/>
</svg>`;
// Function to change icons after copying
const changeIcon = (button, isSuccess) => {
button.innerHTML = isSuccess ? successIcon : errorIcon;
setTimeout(() => {
button.innerHTML = copyIcon; // Reset to copy icon
}, 2000);
};
// Function to get code text from tables, skipping line numbers
const getCodeFromTable = (codeBlock) => {
return [...codeBlock.querySelectorAll('tr')]
.map(row => row.querySelector('td:last-child')?.innerText ?? '')
.join('');
};
// Function to get code text from non-table blocks
const getNonTableCode = (codeBlock) => {
return codeBlock.textContent.trim();
};
document.addEventListener('DOMContentLoaded', function () {
// Select all `pre` elements containing `code`
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
const pre = entry.target.parentNode;
const clipboardBtn = pre.querySelector('.clipboard-button');
const label = pre.querySelector('.code-label');
if (clipboardBtn) {
// Adjust the position of the clipboard button when the `code` is not fully visible
clipboardBtn.style.right = entry.isIntersecting ? '5px' : `-${entry.boundingClientRect.right - pre.clientWidth + 5}px`;
}
if (label) {
// Adjust the position of the label similarly
label.style.right = entry.isIntersecting ? '0px' : `-${entry.boundingClientRect.right - pre.clientWidth}px`;
}
});
}, {
root: null, // observing relative to viewport
rootMargin: '0px',
threshold: 1.0 // Adjust this to control when the callback fires
});
document.querySelectorAll('pre code').forEach(codeBlock => {
const pre = codeBlock.parentNode;
pre.style.position = 'relative'; // Ensure parent `pre` can contain absolute elements
// Create and append the copy button
const copyBtn = document.createElement('button');
copyBtn.className = 'clipboard-button';
copyBtn.innerHTML = copyIcon;
copyBtn.setAttribute('aria-label', 'Copy code to clipboard');
pre.appendChild(copyBtn);
// Attach event listener to copy button
copyBtn.addEventListener('click', async () => {
// Determine if the code is in a table or not
const isTable = codeBlock.querySelector('table');
const codeToCopy = isTable ? getCodeFromTable(codeBlock) : getNonTableCode(codeBlock);
try {
await navigator.clipboard.writeText(codeToCopy);
changeIcon(copyBtn, true); // Show success icon
} catch (error) {
console.error('Failed to copy text: ', error);
changeIcon(copyBtn, false); // Show error icon
}
});
const langClass = codeBlock.className.match(/language-(\w+)/);
const lang = langClass ? langClass[1] : 'default';
// Create and append the label
const label = document.createElement('span');
label.className = 'code-label label-' + lang; // Use the specific language class
label.textContent = lang.toUpperCase(); // Display the language as label
pre.appendChild(label);
let ticking = false;
pre.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
copyBtn.style.right = `-${pre.scrollLeft}px`;
label.style.right = `-${pre.scrollLeft}px`;
ticking = false;
});
ticking = true;
}
});
});
});