|
68 | 68 | if (modal) modal.classList.add('hidden'); |
69 | 69 | } |
70 | 70 |
|
71 | | - // Sanitize Pagefind excerpt: strip all HTML except <mark> tags |
| 71 | + // Recursively sanitize Pagefind excerpt: preserve only <mark> tags, strip everything else |
72 | 72 | function sanitizeExcerpt(html) { |
73 | | - var tmp = document.createElement('div'); |
74 | | - tmp.textContent = ''; |
75 | | - // Parse into a temporary element to strip tags |
76 | 73 | var parser = new DOMParser(); |
77 | 74 | var doc = parser.parseFromString(html, 'text/html'); |
78 | | - var walker = doc.body.childNodes; |
79 | | - for (var i = 0; i < walker.length; i++) { |
80 | | - var node = walker[i]; |
81 | | - if (node.nodeType === 3) { |
82 | | - tmp.appendChild(document.createTextNode(node.textContent)); |
83 | | - } else if (node.nodeType === 1 && node.tagName === 'MARK') { |
84 | | - var mark = document.createElement('mark'); |
85 | | - mark.setAttribute('data-pagefind-highlight', ''); |
86 | | - mark.textContent = node.textContent; |
87 | | - tmp.appendChild(mark); |
88 | | - } else { |
89 | | - tmp.appendChild(document.createTextNode(node.textContent || '')); |
| 75 | + var container = document.createElement('div'); |
| 76 | + function walk(parent, target) { |
| 77 | + for (var i = 0; i < parent.childNodes.length; i++) { |
| 78 | + var node = parent.childNodes[i]; |
| 79 | + if (node.nodeType === 3) { |
| 80 | + target.appendChild(document.createTextNode(node.textContent)); |
| 81 | + } else if (node.nodeType === 1 && node.tagName === 'MARK') { |
| 82 | + var mark = document.createElement('mark'); |
| 83 | + mark.setAttribute('data-pagefind-highlight', ''); |
| 84 | + walk(node, mark); |
| 85 | + target.appendChild(mark); |
| 86 | + } else if (node.nodeType === 1) { |
| 87 | + walk(node, target); |
| 88 | + } |
90 | 89 | } |
91 | 90 | } |
92 | | - return tmp; |
| 91 | + walk(doc.body, container); |
| 92 | + return container; |
93 | 93 | } |
94 | 94 |
|
95 | 95 | document.addEventListener('keydown', function(e) { |
|
109 | 109 | }); |
110 | 110 |
|
111 | 111 | var debounceTimer; |
| 112 | + var searchRequestId = 0; |
112 | 113 | document.addEventListener('input', function(e) { |
113 | 114 | if (!e.target || !e.target.hasAttribute('data-search-input')) return; |
114 | 115 | clearTimeout(debounceTimer); |
|
123 | 124 | return; |
124 | 125 | } |
125 | 126 |
|
| 127 | + var thisRequestId = ++searchRequestId; |
| 128 | + |
126 | 129 | loadPagefind().then(function(pf) { |
127 | 130 | if (!pf) { |
128 | 131 | setResultsText('Search unavailable (build required)'); |
129 | 132 | return; |
130 | 133 | } |
131 | 134 |
|
132 | 135 | pf.search(query).then(function(search) { |
| 136 | + if (thisRequestId !== searchRequestId) return; // stale response |
133 | 137 | if (search.results.length === 0) { |
134 | 138 | setResultsText('No results for "' + query + '"'); |
135 | 139 | return; |
136 | 140 | } |
137 | 141 |
|
138 | 142 | Promise.all(search.results.slice(0, 8).map(function(r) { return r.data(); })) |
139 | 143 | .then(function(items) { |
| 144 | + if (thisRequestId !== searchRequestId) return; // stale response |
140 | 145 | while (results.firstChild) results.removeChild(results.firstChild); |
141 | 146 | items.forEach(function(item) { |
142 | 147 | var link = document.createElement('a'); |
|
0 commit comments