Skip to content

Commit f691412

Browse files
André DietrichAndré Dietrich
authored andcommitted
feat: Project search ugraded from fuse to minisearch
1 parent f251374 commit f691412

1 file changed

Lines changed: 38 additions & 48 deletions

File tree

src/export/project.ts

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -467,53 +467,48 @@ export async function exporter(argument: ProjectExportArguments, json: any) {
467467
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
468468
${
469469
searchIndex.length
470-
? `<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0/dist/fuse.min.js"></script>
470+
? `<script src="https://cdn.jsdelivr.net/npm/minisearch@7/dist/umd/index.min.js"></script>
471471
<script>
472-
// Compact index: [{t, g, i, u, s: [[sectionTitle, sectionContent, indentation], ...]}]
473-
// Denormalize into flat records for Fuse
472+
// Compact index: [{t, g, i, u, s: [[sectionTitle, sectionContent], ...]}]
473+
// Denormalize into flat records for MiniSearch
474474
var SEARCH_INDEX = JSON.parse(${JSON.stringify(JSON.stringify(searchIndex))});
475475
var FLAT_INDEX = [];
476+
var _id = 0;
476477
SEARCH_INDEX.forEach(function(course) {
477478
for (var n = 0; n < course.s.length; n++) {
478479
var sec = course.s[n];
479480
FLAT_INDEX.push({
481+
id: _id++,
480482
courseTitle: course.t,
481483
sectionTitle: sec[0],
482484
sectionContent: sec[1],
483-
tags: course.g,
485+
tagsText: Array.isArray(course.g) ? course.g.join(' ') : '',
486+
tagsArr: course.g || [],
484487
image: course.i,
485488
url: course.u + '#' + (n + 1),
486489
});
487490
}
488491
});
489492
490-
var fuse = new Fuse(FLAT_INDEX, {
491-
keys: [
492-
{ name: 'courseTitle', weight: 0.35 },
493-
{ name: 'sectionTitle', weight: 0.30 },
494-
{ name: 'sectionContent', weight: 0.20 },
495-
{ name: 'tags', weight: 0.15 },
496-
],
497-
includeScore: true,
498-
includeMatches: true,
499-
threshold: 0.4,
500-
minMatchCharLength: 2,
493+
var miniSearch = new MiniSearch({
494+
fields: ['courseTitle', 'sectionTitle', 'sectionContent', 'tagsText'],
495+
storeFields: ['courseTitle', 'sectionTitle', 'sectionContent', 'tagsArr', 'image', 'url'],
496+
searchOptions: {
497+
boost: { sectionTitle: 3, courseTitle: 2, tagsText: 1.5 },
498+
fuzzy: 0.2,
499+
prefix: true,
500+
},
501501
});
502-
503-
function highlight(text, matches, key) {
504-
if (!text || !matches) return escapeHtml(text || '');
505-
var match = matches.find(function(m) { return m.key === key; });
506-
if (!match || !match.indices || !match.indices.length) return escapeHtml(text);
507-
var result = '';
508-
var last = 0;
509-
var indices = match.indices.slice().sort(function(a,b){ return a[0]-b[0]; });
510-
indices.forEach(function(pair) {
511-
result += escapeHtml(text.slice(last, pair[0]));
512-
result += '<mark class="search-highlight">' + escapeHtml(text.slice(pair[0], pair[1]+1)) + '</mark>';
513-
last = pair[1] + 1;
514-
});
515-
result += escapeHtml(text.slice(last));
516-
return result;
502+
miniSearch.addAll(FLAT_INDEX);
503+
504+
function highlight(text, terms) {
505+
if (!text || !terms || !terms.length) return escapeHtml(text || '');
506+
var pattern = new RegExp('(' + terms.join('|') + ')', 'gi');
507+
return text.split(pattern).map(function(part, i) {
508+
return (i % 2 === 1)
509+
? '<mark class="search-highlight">' + escapeHtml(part) + '</mark>'
510+
: escapeHtml(part);
511+
}).join('');
517512
}
518513
519514
function escapeHtml(str) {
@@ -536,44 +531,39 @@ export async function exporter(argument: ProjectExportArguments, json: any) {
536531
container.innerHTML = '<p class="text-muted px-3">Start typing to search…</p>';
537532
return;
538533
}
539-
var results = fuse.search(query, { limit: 20 });
534+
var results = miniSearch.search(query).slice(0, 20);
540535
if (!results.length) {
541536
container.innerHTML = '<p class="text-muted px-3">No results found.</p>';
542537
return;
543538
}
544539
545540
var html = '';
546-
var seenUrls = {};
547541
results.forEach(function(r) {
548-
var item = r.item;
549-
var matches = r.matches;
550-
var baseUrl = item.url.split('#')[0];
551-
var seenUrl = seenUrls[baseUrl];
552-
seenUrls[baseUrl] = true;
553-
554-
var imgHtml = item.image
555-
? '<div class="flex-shrink-0 me-3" style="width:64px;height:48px;background-image:url(&quot;' + escapeHtml(item.image) + '&quot;);background-size:cover;background-position:center;border-radius:4px;"></div>'
542+
var terms = r.terms;
543+
544+
var imgHtml = r.image
545+
? '<div class="flex-shrink-0 me-3" style="width:64px;height:48px;background-image:url(&quot;' + escapeHtml(r.image) + '&quot;);background-size:cover;background-position:center;border-radius:4px;"></div>'
556546
: '';
557547
558548
var tagHtml = '';
559-
if (item.tags && item.tags.length) {
560-
tagHtml = '<div class="mt-1">' + item.tags.map(function(t) {
549+
if (r.tagsArr && r.tagsArr.length) {
550+
tagHtml = '<div class="mt-1">' + r.tagsArr.map(function(t) {
561551
return '<span class="badge rounded-pill bg-light text-dark me-1" style="font-size:0.7rem;">' + escapeHtml(t) + '</span>';
562552
}).join('') + '</div>';
563553
}
564554
565-
html += '<a href="' + escapeHtml(item.url) + '" target="_blank" class="list-group-item list-group-item-action py-2 px-3">' +
555+
html += '<a href="' + escapeHtml(r.url) + '" target="_blank" class="list-group-item list-group-item-action py-2 px-3">' +
566556
'<div class="d-flex align-items-start">' +
567557
imgHtml +
568558
'<div class="flex-grow-1 overflow-hidden">' +
569559
'<div class="d-flex align-items-baseline gap-2">' +
570-
'<div class="fw-semibold flex-shrink-0">' + highlight(item.sectionTitle, matches, 'sectionTitle') + '</div>' +
571-
(item.sectionTitle !== item.courseTitle
572-
? '<div class="text-muted small text-truncate" style="min-width:0;">(' + highlight(item.courseTitle, matches, 'courseTitle') + ')</div>'
560+
'<div class="fw-semibold flex-shrink-0">' + highlight(r.sectionTitle, terms) + '</div>' +
561+
(r.sectionTitle !== r.courseTitle
562+
? '<div class="text-muted small text-truncate" style="min-width:0;">(' + highlight(r.courseTitle, terms) + ')</div>'
573563
: '') +
574564
'</div>' +
575-
(item.sectionContent
576-
? '<div class="text-muted small text-truncate-multiline">' + highlight(excerpt(item.sectionContent, 160), matches, 'sectionContent') + '</div>'
565+
(r.sectionContent
566+
? '<div class="text-muted small text-truncate-multiline">' + highlight(excerpt(r.sectionContent, 160), terms) + '</div>'
577567
: '') +
578568
tagHtml +
579569
'</div>' +

0 commit comments

Comments
 (0)