File size: 6,270 Bytes
8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 98f8ae8 8ed13f7 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 176 177 178 179 180 |
// 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) {
// Assign the passed artifactsData to allArtifacts
allArtifacts = artifactsData;
// The fetch for artifacts.json is removed from here since it's now loaded in main.js
// 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).then(() => {
console.log('Search initialized');
});
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">
<div class="flex items-center justify-between mb-2">
<h5 class="font-medium text-sm text-gray-900">${result.title}</h5>
<div class="flex items-center gap-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">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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-1 bg-gray-200 text-gray-700 rounded">${result.type}</span>
<span>${result.date}</span>
</div>
<div class="flex flex-wrap gap-1">
${areaLinks}
${subAreaLinks}
</div>
</div>
`;
});
html += `</div>`;
}
document.getElementById('search-results').innerHTML = html;
} |