agda-web-docs-lib
Version:
Library for enhancing Agda-generated HTML documentation
289 lines (244 loc) • 10.3 kB
JavaScript
// Function to handle code block functionality with line numbers
(function() {
// Track the last clicked line for range selection
let lastClickedLine = null;
let lastClickedBlockId = null;
// Function to copy code without line numbers
function copyCode(event) {
const button = event.currentTarget;
const pre = button.closest('pre.Agda');
if (!pre) return;
const codeContent = pre.querySelector('.code-content');
if (!codeContent) return;
// Extract text content from code lines, preserving structure
const codeLines = Array.from(codeContent.querySelectorAll('.code-line'));
const codeText = codeLines.map(line => line.textContent).join('\n');
// Create temporary textarea to copy from
const textarea = document.createElement('textarea');
textarea.value = codeText;
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
// Show success state
button.classList.add('copy-success');
setTimeout(() => {
button.classList.remove('copy-success');
}, 2000);
} catch (err) {
console.error('Failed to copy code: ', err);
}
document.body.removeChild(textarea);
}
// Add click event listeners to all copy buttons
const copyButtons = document.querySelectorAll('.copy-code-button');
copyButtons.forEach(button => {
button.addEventListener('click', copyCode);
});
// Function to highlight a range of lines
function highlightLineRange(blockId, startLine, endLine) {
// Ensure startLine <= endLine
if (startLine > endLine) {
[startLine, endLine] = [endLine, startLine];
}
// Clear all existing highlights first
clearAllHighlights();
// Highlight the range of lines
for (let lineNum = startLine; lineNum <= endLine; lineNum++) {
const lineNumberId = `${blockId}-L${lineNum}`;
const lineContentId = `${blockId}-LC${lineNum}`;
const lineNumEl = document.getElementById(lineNumberId);
const lineContentEl = document.getElementById(lineContentId);
if (lineNumEl) {
lineNumEl.classList.add('highlighted');
// Add range-specific classes for styling
if (startLine === endLine) {
// Single line
lineNumEl.classList.add('range-single');
} else if (lineNum === startLine) {
// First line in range
lineNumEl.classList.add('range-start');
} else if (lineNum === endLine) {
// Last line in range
lineNumEl.classList.add('range-end');
} else {
// Middle line in range
lineNumEl.classList.add('range-middle');
}
}
if (lineContentEl) {
lineContentEl.classList.add('highlighted');
// Add range-specific classes for styling
if (startLine === endLine) {
// Single line
lineContentEl.classList.add('range-single');
} else if (lineNum === startLine) {
// First line in range
lineContentEl.classList.add('range-start');
} else if (lineNum === endLine) {
// Last line in range
lineContentEl.classList.add('range-end');
} else {
// Middle line in range
lineContentEl.classList.add('range-middle');
}
}
}
}
// Function to clear all highlights
function clearAllHighlights() {
document.querySelectorAll('.code-line.highlighted').forEach(line => {
line.classList.remove('highlighted', 'range-start', 'range-middle', 'range-end', 'range-single');
});
document.querySelectorAll('.line-number.highlighted').forEach(num => {
num.classList.remove('highlighted', 'range-start', 'range-middle', 'range-end', 'range-single');
});
}
// Handle highlighting of lines when line numbers are clicked
function setupLineHighlighting() {
const lineNumbers = document.querySelectorAll('.line-number');
lineNumbers.forEach(lineNum => {
lineNum.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default hash navigation
// Get the line number and block ID
const num = parseInt(this.getAttribute('data-line-number'));
const blockId = this.getAttribute('data-block-id') || 'block-1';
if (!num) return;
// Check if shift is held for range selection
if (e.shiftKey && lastClickedLine && lastClickedBlockId === blockId) {
// Range selection: highlight from last clicked line to current line
const startLine = Math.min(lastClickedLine, num);
const endLine = Math.max(lastClickedLine, num);
highlightLineRange(blockId, startLine, endLine);
// Update URL hash for range
const newUrl = window.location.pathname + window.location.search + `#${blockId}-L${startLine}-${endLine}`;
history.pushState(null, '', newUrl);
// Scroll the range into view (scroll to the start of the range)
const startLineContentId = `${blockId}-LC${startLine}`;
const startLineEl = document.getElementById(startLineContentId);
if (startLineEl) {
startLineEl.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
} else {
// Single line selection
clearAllHighlights();
// Add highlight class to clicked line number
this.classList.add('highlighted', 'range-single');
// Find the corresponding code line and highlight it
const lineContentId = `${blockId}-LC${num}`;
const codeLine = document.getElementById(lineContentId);
if (codeLine) {
codeLine.classList.add('highlighted', 'range-single');
// Update URL hash without causing a jump
const lineId = `${blockId}-L${num}`;
const newUrl = window.location.pathname + window.location.search + '#' + lineId;
history.pushState(null, '', newUrl);
// Scroll the element into view
codeLine.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}
// Update last clicked line for future range selections
lastClickedLine = num;
lastClickedBlockId = blockId;
}
});
});
}
// Handle highlighting of lines when URL has fragment
function highlightTargetLine() {
// Clear any existing highlights first
clearAllHighlights();
const hash = window.location.hash;
if (hash) {
// Check for range format: #block-X-LY-Z (lines Y through Z)
const rangeMatch = hash.match(/#(B\d+)-L(\d+)-(\d+)$/);
if (rangeMatch) {
const blockId = rangeMatch[1];
const startLine = parseInt(rangeMatch[2]);
const endLine = parseInt(rangeMatch[3]);
highlightLineRange(blockId, startLine, endLine);
// Scroll to the start of the range
const startLineContentId = `${blockId}-LC${startLine}`;
const startLineEl = document.getElementById(startLineContentId);
if (startLineEl) {
setTimeout(() => {
startLineEl.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}, 100);
}
// Update tracking variables
lastClickedLine = endLine; // Set to the end of the range for future selections
lastClickedBlockId = blockId;
return;
}
// Check for block-specific line format: #block-X-LY
const blockLineMatch = hash.match(/#(B\d+)-L(\d+)$/);
if (blockLineMatch) {
const blockId = blockLineMatch[1];
const lineNum = parseInt(blockLineMatch[2]);
const lineId = `${blockId}-L${lineNum}`;
const lineContentId = `${blockId}-LC${lineNum}`;
const codeLine = document.getElementById(lineContentId);
const lineNumEl = document.getElementById(lineId);
if (codeLine) {
// Highlight the target line
codeLine.classList.add('highlighted', 'range-single');
// Highlight the corresponding line number
if (lineNumEl) {
lineNumEl.classList.add('highlighted', 'range-single');
}
// Scroll the line into view
setTimeout(() => {
codeLine.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}, 100); // Small delay to ensure DOM is ready
// Update tracking variables
lastClickedLine = lineNum;
lastClickedBlockId = blockId;
}
} else if (hash.startsWith('#L')) {
// Legacy format support: #L123 (for backward compatibility)
// Try to find it in the first block
const lineNum = parseInt(hash.substring(2));
const blockId = 'B1';
const lineContentId = `${blockId}-LC${lineNum}`;
const lineId = `${blockId}-L${lineNum}`;
const codeLine = document.getElementById(lineContentId);
const lineNumEl = document.getElementById(lineId);
if (codeLine) {
// Highlight the target line
codeLine.classList.add('highlighted', 'range-single');
// Highlight the corresponding line number
if (lineNumEl) {
lineNumEl.classList.add('highlighted', 'range-single');
}
// Scroll the line into view
setTimeout(() => {
codeLine.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
}, 100); // Small delay to ensure DOM is ready
// Update tracking variables
lastClickedLine = lineNum;
lastClickedBlockId = blockId;
}
}
}
}
// Run on page load and when hash changes
setupLineHighlighting();
highlightTargetLine();
window.addEventListener('hashchange', highlightTargetLine);
})();