PageSourceSearch

https://www.home-assistant.io/javascripts/prism-template-links.js?7bedbf54ec266241edb843530c111903

js home-assistant.io collected 2026-10-01 06:27:36 UTC 7,691 bytes, 234 lines download raw bytes

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.