diff --git a/assets/js/search.js b/assets/js/search.js deleted file mode 100644 index d340a61..0000000 --- a/assets/js/search.js +++ /dev/null @@ -1,207 +0,0 @@ -// Configuration -const DEFAULT_CONFIG = { - search: { - minChars: 2, // Minimum characters before searching - maxResults: 5, // Maximum number of results to show - fields: { // Fields to search through - title: true, // Allow searching in title - description: true, // Allow searching in description - section: true // Allow searching in section - } - } -}; - -class FastSearch { - constructor({ - searchInput, resultsContainer, json, - searchResultTemplate = null, - noResultsText = null, - }) { - this.searchInput = searchInput; - this.resultsContainer = resultsContainer; - this.json = json; - this.searchResultTemplate = searchResultTemplate; - this.noResultsText = noResultsText; - - this.init(); - } - - init() { - this.loadSearchIndex(); - // this.initShortcutListener(); - this.searchInput.addEventListener('input', (event) => { - if (!this.searchIndex) { - this.resultsContainer.innerHTML = '
  • Loading search index...
  • '; - return; - } - this.performSearch(this.searchInput.value); - }); - } - - // Load the search index - async loadSearchIndex() { - try { - const response = await fetch(this.json); - if (!response.ok) throw new Error('Failed to load search index'); - const data = await response.json(); - - this.searchIndex = data.map(item => ({ - ...item, - searchableTitle: item.title?.toLowerCase() || '', - searchableDesc: item.desc?.toLowerCase() || '', - searchableSection: item.section?.toLowerCase() || '' - })); - } catch (error) { - console.error('Error loading search index:', error); - this.resultsContainer.innerHTML = '
  • Error loading search index...
  • '; - } - } - - escapeHtml(unsafe) { - if (!unsafe) return ''; - return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); - } - - - // Simple fuzzy match for single words - simpleFuzzyMatch(text, term) { - if (text.includes(term)) return true; - if (term.length < 3) return false; - - let matches = 0; - let lastMatchIndex = -1; - - for (let i = 0; i < term.length; i++) { - const found = text.indexOf(term[i], lastMatchIndex + 1); - if (found > -1) { - matches++; - lastMatchIndex = found; - } - } - - return matches === term.length; - } - - // Check if keyboard event matches shortcut config - matchesShortcut(event, shortcutConfig) { - return event.key === shortcutConfig.key && - event.metaKey === shortcutConfig.metaKey && - event.altKey === shortcutConfig.altKey && - event.ctrlKey === shortcutConfig.ctrlKey && - event.shiftKey === shortcutConfig.shiftKey; - } - - initShortcutListener() { - // Keyboard shortcuts - document.addEventListener('keydown', (event) => { - // ESC to close search - if (event.key === 'Escape' && this.searchInput.focus) { - this.searchInput.blur(); - this.searchInput.value = ''; - this.resultsContainer.innerHTML = ''; - } - }); - } - - - performSearch(term) { - term = term.toLowerCase().trim(); - - if (!term || !this.searchIndex) { - this.resultsContainer.innerHTML = ''; - let resultsAvailable = false; - return; - } - - // Split search into terms - const searchTerms = term.split(/\s+/).filter(t => t.length > 0); - - // Search with scoring - const results = this.searchIndex - .map(item => { - let score = 0; - const matchesAllTerms = searchTerms.every(term => { - let matched = false; - - // Title matches (weighted higher) - if (DEFAULT_CONFIG.search.fields.title) { - if (item.searchableTitle.startsWith(term)) { - score += 3; // Highest score for prefix matches in title - matched = true; - } else if (this.simpleFuzzyMatch(item.searchableTitle, term)) { - score += 2; // Good score for fuzzy matches in title - matched = true; - } - } - - // Other field matches - if (!matched) { - if (DEFAULT_CONFIG.search.fields.description && item.searchableDesc.includes(term)) { - score += 0.5; // Lower score for description matches - matched = true; - } - if (DEFAULT_CONFIG.search.fields.section && item.searchableSection.includes(term)) { - score += 0.5; // Lower score for section matches - matched = true; - } - } - - return matched; - }); - - return { - item, - score: matchesAllTerms ? score : 0 - }; - }) - .filter(result => result.score > 0) - .sort((a, b) => b.score - a.score) - .slice(0, DEFAULT_CONFIG.search.maxResults) - .map(result => result.item); - - let resultsAvailable = results.length > 0; - - if (!resultsAvailable) { - this.resultsContainer.innerHTML = '

    Oops! No results found.

    '; - return; - } - - const searchItems = results.map( (item) => { - let categories = ''; - let tags = ''; - if (item.categories) { - categories = item.categories.join(', '); - categories = `
    ${categories}
    `; - } - if (item.tags) { - tags = item.tags.join(', '); - tags = `
    ${tags}
    ` - } - return ` -
    -
    -

    ${this.escapeHtml(item.title)}

    - -
    -

    ${this.escapeHtml(item.contents)}

    -
    - `; - }).join(''); - - this.resultsContainer.innerHTML = searchItems; - } -} - -const search = new FastSearch({ - searchInput: document.getElementById('search-input'), - resultsContainer: document.getElementById('search-results'), - json: '/index.json' -}); \ No newline at end of file diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 6ef7ee3..2a8c0b7 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -6,7 +6,6 @@ -}} {{- $themeOpts := merge $jsOpts (dict "global" "Theme") -}} {{- $theme := resources.Get "js/modules/theme.js" | js.Build $themeOpts -}} -{{- $search := resources.Get "js/search.js" | js.Build $jsOpts -}} @@ -78,7 +77,6 @@ - {{ partial "js-selector.html" . }} diff --git a/layouts/partials/search-results.html b/layouts/partials/search-results.html index 0819ff8..144b1da 100644 --- a/layouts/partials/search-results.html +++ b/layouts/partials/search-results.html @@ -5,4 +5,242 @@
    - \ No newline at end of file + + + \ No newline at end of file