@@ -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("' + escapeHtml(item.image) + '");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("' + escapeHtml(r.image) + '");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