File size: 6,200 Bytes
8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 72be969 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 72be969 98f8ae8 72be969 98f8ae8 72be969 98f8ae8 72be969 98f8ae8 72be969 98f8ae8 8ed13f7 98f8ae8 8ed13f7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
// search.js - Complete MiniSearch implementation with artifacts
import { renderSearchTags } from './tags.js';
// Use global areasData (loaded in index.html <head>)
const areasData = window.areasData;
let miniSearch;
let allArtifacts;
// Helper to create and populate a MiniSearch instance
function createMiniSearchIndex(data, storeFields) {
const search = new MiniSearch({
fields: ['title', 'description', 'areas', 'topics'],
storeFields: storeFields
});
search.addAll(data);
return search;
}
// Initialize search
export async function initializeSearch(artifactsData) {
allArtifacts = artifactsData;
// Prepare data for MiniSearch
const searchData = allArtifacts.map((artifact, index) => ({
id: index,
title: artifact.title,
description: artifact.description,
type: artifact.type,
areas: (artifact.areas || []).join(' '),
topics: (artifact.topics || []).join(' '),
url: artifact.url,
date: artifact.date
}));
// Initialize MiniSearch for artifacts
miniSearch = createMiniSearchIndex(searchData, ['title', 'description', 'type', 'areas', 'topics', 'url', 'date']);
}
export function searchContent(query) {
if (!query || query.trim().length < 2) {
return { artifacts: [] };
}
const artifactResults = miniSearch.search(query, {
prefix: true,
fuzzy: 0.2,
boost: { title: 2, description: 1 }
});
return {
artifacts: artifactResults
};
}
// Helper functions
function getAreaDisplayName(area) {
return areasData[area]?.title || area;
}
function getSubAreaDisplayName(areaId, subArea) {
const area = areasData[areaId];
if (!area || !area.subAreas) return subArea;
const subAreaData = area.subAreas[subArea];
return typeof subAreaData === 'string' ? subAreaData : subAreaData?.name || subArea;
}
// Search UI
export function initializeSearchUI(artifactsData) {
initializeSearch(artifactsData);
const searchInput = document.getElementById('search-input');
const searchResults = document.getElementById('search-results');
if (!searchInput || !searchResults) return;
let searchTimeout;
// Function to perform search
function performSearch() {
const query = searchInput.value.trim();
if (query.length < 2) {
searchResults.innerHTML = `<div class="text-gray-500 text-center py-8"><p>Enter a search term...</p></div>`;
return;
}
const results = searchContent(query);
displaySearchResults(results, query);
}
// Handle input events (typing)
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
const query = e.target.value.trim();
if (query.length < 2) {
searchResults.innerHTML = `<div class="text-gray-500 text-center py-8"><p>Enter a search term...</p></div>`;
return;
}
// Debounce search
searchTimeout = setTimeout(() => {
performSearch();
}, 300);
});
// Handle Enter key (immediate search)
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
clearTimeout(searchTimeout);
performSearch();
}
});
}
// Update the displaySearchResults function in search.js
export function displaySearchResults(results, query) {
const { artifacts } = results;
const totalResults = artifacts.length;
if (totalResults === 0) {
document.getElementById('search-results').innerHTML = `
<div class="text-gray-500 text-center py-8"><p>No results found for "${query}"</p></div>
`;
return;
}
let html = `<div class="text-sm text-gray-600 mb-4">
Found ${totalResults} result${totalResults === 1 ? '' : 's'}
</div>`;
// Display artifacts
if (artifacts.length > 0) {
html += `<div class="space-y-2">`;
artifacts.forEach(result => {
const score = Math.round(result.score * 100);
// Use renderSearchTags utility
const areas = result.areas ? result.areas.split(' ') : [];
const topics = result.topics ? result.topics.split(' ') : [];
const { areaLinksHtml: areaLinks, topicLinksHtml: subAreaLinks } = renderSearchTags(areas, topics);
html += `
<div class="p-3 bg-gray-50 rounded-lg border border-gray-200">
<div class="flex items-center justify-between mb-2">
<h5 class="font-medium text-sm text-gray-900 leading-snug">${result.title}</h5>
<div class="flex items-center gap-2 flex-shrink-0 ml-2">
<span class="text-xs text-gray-500">${score}%</span>
<a href="${result.url}" target="_blank" class="text-gray-400 hover:text-gray-600 transition-colors" aria-label="Open ${result.title}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
</svg>
</a>
</div>
</div>
<div class="flex items-center gap-2 mb-2 text-xs text-gray-600">
<span class="px-2 py-0.5 bg-gray-200 text-gray-700 rounded font-medium">${result.type}</span>
<span class="text-gray-500">${result.date}</span>
</div>
<div class="flex flex-wrap gap-1.5">
${areaLinks}
${subAreaLinks}
</div>
</div>
`;
});
html += `</div>`;
}
document.getElementById('search-results').innerHTML = html;
} |