search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: highlight exact search terms, not FTS matched tokens

- remove FTS5 snippet highlighting (was highlighting entire matched words)
- add frontend highlighting that matches exactly what user typed
- "whats" now highlights "whats" in "WhatsApp", not the whole word

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz 8b38e42e a1dc005e

+17 -4
+3 -3
backend/src/db/mod.zig
··· 128 128 129 129 const DocsByFtsAndTag = zql.Query( 130 130 \\SELECT f.uri, d.did, d.title, 131 - \\ snippet(documents_fts, 2, '<mark>', '</mark>', '...', 32) as snippet, 131 + \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 132 132 \\ d.created_at, d.rkey, p.base_path, 133 133 \\ CASE WHEN d.publication_uri != '' THEN 1 ELSE 0 END as has_publication 134 134 \\FROM documents_fts f ··· 141 141 142 142 const DocsByFts = zql.Query( 143 143 \\SELECT f.uri, d.did, d.title, 144 - \\ snippet(documents_fts, 2, '<mark>', '</mark>', '...', 32) as snippet, 144 + \\ snippet(documents_fts, 2, '', '', '...', 32) as snippet, 145 145 \\ d.created_at, d.rkey, p.base_path, 146 146 \\ CASE WHEN d.publication_uri != '' THEN 1 ELSE 0 END as has_publication 147 147 \\FROM documents_fts f ··· 174 174 175 175 const PubSearch = zql.Query( 176 176 \\SELECT f.uri, p.did, p.name, 177 - \\ snippet(publications_fts, 2, '<mark>', '</mark>', '...', 32) as snippet, 177 + \\ snippet(publications_fts, 2, '', '', '...', 32) as snippet, 178 178 \\ p.rkey, p.base_path 179 179 \\FROM publications_fts f 180 180 \\JOIN publications p ON f.uri = p.uri
+14 -1
site/index.html
··· 339 339 ? `<a href="${leafletUrl}" target="_blank">${escapeHtml(doc.title || 'Untitled')}</a>` 340 340 : escapeHtml(doc.title || 'Untitled')} 341 341 </div> 342 - <div class="result-snippet">${doc.snippet || ''}</div> 342 + <div class="result-snippet">${highlightTerms(doc.snippet, query)}</div> 343 343 <div class="result-meta"> 344 344 ${date ? `${date} | ` : ''}${doc.basePath 345 345 ? `<a href="https://${doc.basePath}" target="_blank">${doc.basePath}</a>` ··· 364 364 return str.replace(/[&<>"']/g, c => ({ 365 365 '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' 366 366 })[c]); 367 + } 368 + 369 + function highlightTerms(text, query) { 370 + if (!text || !query) return escapeHtml(text); 371 + const terms = query.toLowerCase().split(/\s+/).filter(t => t.length > 0); 372 + if (terms.length === 0) return escapeHtml(text); 373 + 374 + // build regex that matches any term (case insensitive) 375 + const escaped = terms.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); 376 + const regex = new RegExp(`(${escaped.join('|')})`, 'gi'); 377 + 378 + // escape HTML first, then apply highlights 379 + return escapeHtml(text).replace(regex, '<mark>$1</mark>'); 367 380 } 368 381 369 382 function updateUrl() {