1/* Make template function/filter/parameter names in code blocks hoverable/clickable */ 2(function() { 3 var funcs = window.__templateFunctions; 4 if (!funcs) return; 5 6 var TEMPLATE_LANGS = ['template', 'django', 'jinja2', 'yaml']; 7 8 // Link function and filter tokens to their documentation pages 9 function linkFunctions(codeEl) { 10 codeEl.querySelectorAll('.token.function, .token.filter').forEach(function(token) { 11 if (token.classList.contains('tf-linked')) return; 12 var name = token.textContent.trim(); 13 if (funcs[name]) { 14 token.setAttribute('data-tf-url', funcs[name].u); 15 token.setAttribute('data-tf-desc', funcs[name].d); 16 token.classList.add('tf-linked'); 17 } 18 }); 19 } 20 21 // Find the parent function for a keyword argument by walking backwards 22 // through sibling tokens to find the matching opening parenthesis 23 function findParentFunction(varToken) { 24 var depth = 0; 25 var el = varToken.previousElementSibling; 26 27 while (el) { 28 if (el.classList.contains('token')) { 29 var text = el.textContent.trim(); 30 if (text === ')') depth++; 31 if (text === '(') { 32 if (depth === 0) { 33 var prev = el.previousElementSibling; 34 if (prev && (prev.classList.contains('function') || prev.classList.contains('filter'))) { 35 return prev.textContent.trim(); 36 } 37 return null; 38 } 39 depth--; 40 } 41 } 42 el = el.previousElementSibling; 43 } 44 return null; 45 } 46 47 // Link keyword argument names (variable tokens before =) to their 48 // parent function's parameter documentation 49 function linkParameters(codeEl) { 50 var variables = codeEl.querySelectorAll('.token.variable'); 51
52 variables.forEach(function(varToken) { 53 if (varToken.classList.contains('tf-linked')) return; 54 55 // Check if next sibling token is the = operator (not == or !=) 56 var next = varToken.nextElementSibling; 57 if (!next || !next.classList.contains('operator') || next.textContent.trim() !== '=') return; 58 59 var paramName = varToken.textContent.trim(); 60 var funcName = findParentFunction(varToken); 61 if (!funcName || !funcs[funcName]) return; 62 63 var funcData = funcs[funcName]; 64 var paramDesc = funcData.p && funcData.p[paramName]; 65 var url = funcData.u + '#' + paramName; 66 var desc = paramDesc || ('Parameter of ' + funcName + '()'); 67 68 varToken.setAttribute('data-tf-url', url); 69 varToken.setAttribute('data-tf-desc', desc); 70 varToken.classList.add('tf-linked', 'tf-param'); 71 }); 72 } 73 74 // Process a code element for both function and parameter linking 75 function processCodeBlock(codeEl) { 76 linkFunctions(codeEl); 77 linkParameters(codeEl); 78 } 79 80 // Hook into Prism for elements highlighted after this script loads 81 Prism.hooks.add('after-highlight', function(env) { 82 if (TEMPLATE_LANGS.indexOf(env.language) === -1) return; 83 processCodeBlock(env.element); 84 }); 85 86 // Also process elements already highlighted before hooks were registered 87 document.addEventListener('DOMContentLoaded', function() { 88 var selectors = TEMPLATE_LANGS.map(function(l) { 89 return 'code.language-' + l; 90 }).join(', '); 91 document.querySelectorAll(selectors).forEach(processCodeBlock); 92 }); 93 94 // Track the last input type so touch and mouse behave differently. 95 // Also dismiss any active tooltip when the user taps outside a linked token. 96 var lastPointerType = 'mouse'; 97 document.addEventListener('pointerdown', function(e) { 98 lastPointerType = e.pointerType; 99 if (primedElement && !e.target.closest('.tf-linked')) { 100 primedElement = null; 101 currentHovered = null; 102 hideTooltip(); 103 } 104 }, { passive: true }); 105 106 // Dismiss tooltip when scrolling â the anchored position becomes stale 107 // and the user is no longer reading it. 108 window.addEventListener('scroll', function() { 109 if (primedElement || currentHovered) { 110 primedElement = null; 111 currentHovered = null; 112 hideTooltip(); 113 } 114 }, { passive: true }); 115 116 function isTouchInput() { 117 return lastPointerType === 'touch' || lastPointerType === 'pen'; 118 } 119 120 // Navigate to the linked URL, honoring modifier keys for new-tab behavior 121 function navigate(url, event) { 122 if (event.ctrlKey || event.metaKey || event.shiftKey || event.button === 1) { 123 window.open(url, '_blank'); 124 } else { 125 window.location.href = url; 126 } 127 } 128 129 // On touch input, first tap primes the element and shows the tooltip; 130 // a second tap on the same element navigates. 131 var primedElement = null; 132 133 // Click handler: left-click with or without modifier keys 134 document.addEventListener('click', function(e) { 135 var el = e.target.closest('.tf-linked'); 136 137 // Tap outside any linked token: dismiss tooltip and clear primed state 138 if (!el) { 139 if (primedElement) { 140 primedElement = null; 141 hideTooltip(); 142 currentHovered = null; 143 } 144 return; 145 } 146 147 var url = el.getAttribute('data-tf-url'); 148 if (!url) return; 149 e.preventDefault(); 150 151 // On touch, require two taps: first shows tooltip, second navigates 152 if (isTouchInput() && primedElement !== el) { 153 if (primedElement) hideTooltip(); 154 primedElement = el; 155 currentHovered = el; 156 showTooltip(el); 157 return; 158 } 159 160 // Desktop, or second tap on touch â navigate 161 primedElement = null; 162 navigate(url, e); 163 }); 164 165 // Middle-click opens in a new tab 166 document.addEventListener('auxclick', function(e) { 167 if (e.button !== 1) return; 168 var el = e.target.closest('.tf-linked'); 169 if (!el) return; 170 var url = el.getAttribute('data-tf-url'); 171 if (!url) return; 172 e.preventDefault(); 173 window.open(url, '_blank'); 174 }); 175 176 // Tooltip on hover â delegated mouseover/mouseout with enter/leave semantics 177 // to avoid repositioning while moving within the same token. Touch input 178 // uses the click-based flow above, so mouse handlers bail out on touch. 179 var tooltip = document.createElement('div'); 180 tooltip.className = 'tf-code-tooltip'; 181 tooltip.style.display = 'none'; 182 document.body.appendChild(tooltip); 183 184 var currentHovered = null; 185 186 function showTooltip(el) { 187 var desc = el.getAttribute('data-tf-desc'); 188 if (!desc) return; 189 190 tooltip.textContent = desc; 191 tooltip.style.display = 'block'; 192 193 // Batch reads, then writes: measure target and tooltip once, 194 // compute final position, then apply both style changes together 195 var rect = el.getBoundingClientRect(); 196 var tooltipRect = tooltip.getBoundingClientRect(); 197 var viewportWidth = window.innerWidth; 198 199 var top = rect.top - tooltipRect.height - 8; 200 if (top < 60) top = rect.bottom + 8; 201 202 var left = rect.left + (rect.width / 2) - (tooltipRect.width / 2); 203 if (left < 10) left = 10; 204 if (left + tooltipRect.width > viewportWidth - 10) { 205 left = viewportWidth - tooltipRect.width - 10; 206 } 207 208 tooltip.style.top = top + 'px'; 209 tooltip.style.left = left + 'px'; 210 } 211 212 function hideTooltip() { 213 tooltip.style.display = 'none'; 214 } 215 216 document.addEventListener('mouseover', function(e) { 217 if (isTouchInput()) return; 218 var el = e.target.closest('.tf-linked'); 219 if (el === currentHovered) return; 220 if (currentHovered) hideTooltip(); 221 currentHovered = el || null; 222 if (el) showTooltip(el); 223 }); 224 225 document.addEventListener('mouseout', function(e) { 226 if (isTouchInput()) return; 227 if (!currentHovered) return; 228 var next = e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest('.tf-linked'); 229 if (!next) { 230 hideTooltip(); 231 currentHovered = null; 232 } 233 }); 234})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.