1(function () { 2 'use strict'; 3 4 var nextId = 0; 5 6 function trimPreviewEnd(element) { 7 while (element.lastChild) { 8 var child = element.lastChild; 9 if (child.nodeType === 1) { 10 if (child.tagName === 'IMG') return; 11 trimPreviewEnd(child); 12 if (child.textContent.trim() || child.querySelector('img')) return; 13 } else if (child.nodeType === 3 && child.textContent.trim()) { 14 return; 15 } 16 element.removeChild(child); 17 } 18 } 19 20 // Find the end of the eighth rendered text line, including lines in later 21 // paragraphs. Paragraph margins are spacing, not part of a text line. 22 function previewRange(content) { 23 var walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT); 24 var range = document.createRange(); 25 var line = null; 26 var count = 0; 27 var node; 28 while ((node = walker.nextNode())) { 29 range.selectNodeContents(node); 30 var rects = Array.prototype.filter.call(range.getClientRects(), function (rect) { 31 return rect.width > 0 && rect.height > 0; 32 }); 33 for (var i = 0; i < rects.length; i++) { 34 var rect = rects[i]; 35 if (line && rect.top < line.bottom - 1 && rect.bottom > line.top + 1) { 36 line.top = Math.min(line.top, rect.top); 37 line.bottom = Math.max(line.bottom, rect.bottom); 38 continue; 39 } 40 if (count === 8) { 41 // Binary search only the text node that crosses the boundary. 42 var low = 0; 43 var high = node.length; 44 while (low < high) { 45 var middle = Math.ceil((low + high) / 2); 46 range.setEnd(node, middle); 47 var beyond = Array.prototype.some.call(range.getClientRects(), function (part) { 48 return part.width > 0 && part.bottom > line.bottom + 0.5; 49 }); 50 if (beyond) high = middle - 1; 51 else low = middle; 52 } 53 range.setStart(content, 0); 54 range.setEnd(node, low); 55 return range; 56 } 57 line = { top: rect.top, bottom: rect.bottom }; 58 count++; 59 } 60 } 61 return null; 62 } 63 64 function init(content) { 65 if (content.dataset.descriptionReady) return; 66 // The server supplies labels from the active site's translation config. 67 var copy = [content.getAttribute('data-description-more'), content.getAttribute('data-description-less')]; 68 if (!copy[0] || !copy[1]) return; 69 content.dataset.descriptionReady = 'true'; 70 content.classList.add('listing-description-content'); 71 if (!content.id) { 72 var id; 73 do { id = 'listing-description-' + (++nextId); } 74 while (document.getElementById(id)); 75 content.id = id; 76 } 77 var button = document.createElement('button'); 78 button.type = 'button'; 79 button.className = 'listing-description-toggle'; 80 button.hidden = true; 81 button.setAttribute('aria-controls', content.id); 82 content.insertAdjacentElement('afterend', button); 83 var preview = content.cloneNode(false); 84 ['id', 'data-listing-description', 'data-description-ready', 'data-description-more', 'data-description-less', 'itemprop', 'itemscope', 'itemtype'].forEach(function (attribute) { 85 preview.removeAttribute(attribute); 86 }); 87 preview.classList.add('listing-description-preview'); 88 preview.hidden = true; 89 content.insertAdjacentElement('beforebegin', preview); 90 var expanded = false; 91 var long = false; 92 93 function render() { 94 var collapsed = long && !expanded; 95 content.hidden = collapsed; 96 preview.hidden = !collapsed; 97 button.hidden = !long; 98 button.textContent = expanded ? copy[1] : copy[0]; 99 button.setAttribute('aria-expanded', collapsed ? 'false' : 'true'); 100 } 101
102 function update() { 103 // Measure the original at its real width. This synchronous swap is 104 // completed before paint; the full content remains untouched. 105 preview.hidden = true; 106 content.hidden = false; 107 var range = previewRange(content); 108 long = range !== null; 109 preview.textContent = ''; 110 if (range) { 111 preview.appendChild(range.cloneContents()); 112 preview.querySelectorAll('[id], [itemprop], [itemscope], [itemtype]').forEach(function (element) { 113 ['id', 'itemprop', 'itemscope', 'itemtype'].forEach(function (attribute) { 114 element.removeAttribute(attribute); 115 }); 116 }); 117 // A boundary at the start of a paragraph can leave an empty 118 // paragraph or trailing BR. Neither belongs in the preview. 119 trimPreviewEnd(preview); 120 } 121 render(); 122 } 123 124 button.addEventListener('click', function () { 125 expanded = !expanded; 126 render(); 127 if (!expanded && button.getBoundingClientRect().top < 0) { 128 button.scrollIntoView({ block: 'nearest' }); 129 } 130 }); 131 update(); 132 if (window.ResizeObserver) { 133 var parent = content.parentElement; 134 var width = parent.getBoundingClientRect().width; 135 new ResizeObserver(function () { 136 var newWidth = parent.getBoundingClientRect().width; 137 if (newWidth !== width) { width = newWidth; update(); } 138 }).observe(parent); 139 } 140 window.addEventListener('resize', update); 141 content.addEventListener('load', update, true); 142 if (document.fonts && document.fonts.ready) document.fonts.ready.then(update); 143 } 144 145 function start() { 146 document.querySelectorAll('[data-listing-description]').forEach(init); 147 } 148 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); 149 else start(); 150}());
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.