Skip to content

Latest commit

Β 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FoxinSearch

Lightweight client-side search engine for static sites using Lunr.js. Perfect for Jekyll, Hugo, and other static site generators.

gitlab-license gitlab-pipeline gitlab-issues npm-version jsr-version jsdelivr-hitsperweek npm-version npm-sizeMin npm-sizeZipped


Features

  • Search Functionality

    • Uses Lunr.js for full-text search
    • Filters by tag, category, title, excerpt, collection, nav_enabled
    • Real-time search as you type
    • Configurable minimum search length
    • Score-based ranking for relevant results
  • Index Management

    • Displays index count in search input placeholder
    • Loads search data from JSON file
    • Builds search index on initialization
    • Efficient client-side indexing
  • Result Display

    • Highlights search terms in results
    • Shows title, excerpt, category, and tags
    • Configurable maximum results
    • Clean, customizable output
  • Debug Logging

    • Comprehensive console.log statements throughout using custom debug utils
    • Tracks initialization, search operations, and results
    • Easy debugging and development

Installation

  • CDN:

    • Link from a jsdelivr CDN

    • Link from a unpkg CDN

      If you used a CDN link, you're good to go! foxinsearch will be available in the global scope via window.foxinsearch functions available in the global scope in browser environment.

    • if you use a CND with type=module:

      import * as foxinsearch from 'https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.js';
      // foxinsearch is now available in the global scope via `window.foxinsearch`
  • NPM: npm install foxinsearch@latest --save --save-dev

    • Add foxinsearch as a devDependency and don't forget to add it in your package.json dependencies.
    • Use:
      import * as foxinsearch from 'foxinsearch';`


πŸ›‘ Requirements:
Node.js for local builds; runs in modern browsers that provide console, localStorage, and CustomEvent.

🌐 CDNS

Packages are available on npm by related jsdilver and unpkg cdns.

JSDELIVR LINK
USD https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.js
USD minified https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.min.js
ESM Module https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch/+esm'
ESM Module minified https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.min.js
UNPKG LINK
USD https://unpkg.com/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.js
USD minified https://unpkg.com/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.min.js
ESM Module minified https://unpkg.com/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.min.js
ESM Module https://unpkg.com/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.min.js

🟒 Quick Start

  • Vanilla(UMD(browser)) via <script src="path|url"> tag: will be available in the global scope via window.foxinsearch.

    <script src="path/to/foxinsearch.js"></script>
    <!-- or via cdn: jsdelivr -->
    <script src="https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@0.3.0/dist/foxinsearch.js"></script>
    <!-- or via cdn: unpkg -->
    <script src="https://unpkg.com/@staticcanvas/foxinsearch@0.3.0/dist/foxinsearch.js"></script>
    // Initialize
    const foxin = new FoxinSearch({
        searchElement: '#foxin-input',    // search input element
        outputElement: '#foxin-results',  // results output element
        loaderElement: '#foxin-loader',   // loading indicator element
        searchDataUrl: 'search.json',
        tag: '',
        category: '',
        searchTerm: '',
        minSearchLength: 1,
        maxResults: 20,
        highlightTag: 'mark',
        poweredbyBadge: true,  // Disable powered by badge
    });
    
    // Set up search
    foxin.init();
  • ESM6 Module(ESM6(browser) with type="module"): import from file or cdn and walla!

    import * as foxinsearch from 'path/to/foxinsearch.esm.js';
    // or
    import * as foxinsearch from 'https://cdn.jsdelivr.net/npm/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.js';
    // or 
    import * as foxinsearch from 'https://cdn.unpkg.com/@staticcanvas/foxinsearch@{version}/dist/foxinsearch.esm.js';
    
    // Initialize
    const foxin = new FoxinSearch({
        searchElement: '#foxin-input',    // search input element
        outputElement: '#foxin-results',  // results output element
        loaderElement: '#foxin-loader',   // loading indicator element
        searchDataUrl: 'search.json',
        tag: '',
        category: '',
        searchTerm: '',
        minSearchLength: 1,
        maxResults: 20,
        highlightTag: 'mark',
        poweredbyBadge: true,  // Disable powered by badge
    });
    
    // Set up search
    foxin.init();

Usage

Constructor Defaults

Default constructor options Options Description
searchElement #foxin-input The search input element
outputElement #foxin-results The results output element
loaderElement #foxin-loader The loading indicator element
tag '' default tag to filter by
category '' default catagory to filter by
collection '' default collection to filder by
searchTerm '' initial search term
searchDataUrl /search-data.json url to load search data
minSearchLength 2 Minimum search string length
maxResults 20 Maximum number of search results
highlightTag mark HTML tag to use for highlighting search terms
poweredbyBadge true Display powered by badge
debug false Enable debug mode

Initialize with defaults

// Initialize with 
const foxin = new FoxinSearch();
// Set up search
foxin.init();
// search 
foxin.search('javascript');

Advanced Example

const foxin = new FoxinSearch({
    searchElement: '#foxin-input',    // search input element
    outputElement: '#foxin-results',  // results output element
    loaderElement: '#foxin-loader',   // loading indicator element
    searchDataUrl: 'search.json',
    tag: '',
    category: '',
    searchTerm: '',
    minSearchLength: 1,
    maxResults: 20,
    highlightTag: 'mark',
    poweredbyBadge: true,  // Disable powered by badge
});

// Initialize with filters
foxin.init(
    '#search-input',
    'react',
    'tutorial',
    'frontend',
    '#results',
    '#loader'
);

// Perform manual search
search.search('javascript', 'beginner', 'programming');

// Clear search
search.clear();

Target HTML structure:

<!-- FOXIN SEARCH CONTAINER -->
<div class="foxin-container" id="foxin-container">
    <div class="foxin-overlay" id="foxin-overlay"></div>
    <input class="foxin-input" type="text" id="foxin-input" placeholder="Foxin Search...">
    <div class="foxin-loader" id="foxin-loader" style="display: none;">loading...</div>
    <div class="foxin-results" id="foxin-results"></div>
</div>
<!-- FOXIN SEARCH CONTAINER -->

πŸ“š API Reference

Methods

constructor( {options} )

Initializes the search functionality and sets up event listeners, loading the search data from the specified URL.

!TODO: move init method to constructor allowing single line initialization

Parameters:

  • loaderElement - Loading indicator element or selector
  • outputElement - Results container element or selector
  • inputElement - Search input element or selector
  • minSearchLength - Minimum search string length
  • searchDataUrl - URL to load search data
  • searchElement - Search input element or selector
  • searchTerm - Initial search term (optional)
  • tag - Filter by tag (optional)
  • category - Filter by category (optional)
  • outputElement - Results container element or selector
  • loaderElement - Loading indicator element or selector (optional)
  • resultsElement - Results container element or selector (optional)

Returns: Promise<void>

search(term, tag, category)

Performs a manual search operation.

Parameters:

  • term - Search term
  • tag - Filter by tag (optional)
  • category - Filter by category (optional)

Returns: Array - Search results

clear()

Clears the current search and results.

Returns: void

getIndexCount()

Returns the number of items in the search index.

Returns: number


πŸ“„ JSON Data Format

Your search data JSON file should follow this format:

[
  {
    "title": "How to Use JavaScript",
    "content": "Full content text for indexing...",
    "excerpt": "Brief description of the content...",
    "url": "/posts/javascript-guide/",
    "category": "programming",
    "tags": ["javascript", "tutorial", "beginner"],
    "collection": "docs",
    "nav_enabled" : true
  },
  {
    "title": "Advanced React Patterns",
    "content": "Complete article content...",
    "excerpt": "Learn advanced React patterns...",
    "url": "/posts/react-patterns/",
    "category": "frontend",
    "tags": ["react", "javascript", "advanced"],
    "collection": "docs",
    "nav_enabled" : true
  }
]

Required Fields

  • title - The title of the content
  • content - Full text content (used for search indexing)
  • url - Link to the content
  • excerpt - Short description (displayed in results)

Optional Fields

  • category - Content category for filtering
  • tags - Array of tags for filtering
  • collection - Content collection for filtering
  • search_exclude - Set to true to exclude from search

πŸ’‘ Examples

Jekyll Integration

Create a search.json file in your Jekyll site:

_data/search.json

---
layout: none
permalink: /search.json
---

{%- comment -%}
This template generates a JSON array of all indexable site content:
- Posts
- Pages
- All custom collections defined in _config.yml
Items can be excluded by setting `search_exclude: true` in front matter.
{%- endcomment -%}

{%- assign collections = site.collections | map: "label" -%}
{%- assign documents = site.posts | where_exp: "item", "item.search_exclude != true" -%}

{%- comment -%}
Iterate through all collections dynamically (excluding 'posts' and 'data')
and merge their documents into a single array.
{%- endcomment -%}
{%- for collection in collections -%}
  {%- assign coll = site[collection] -%}
  {%- if coll and collection != "posts" and collection != "data" -%}
    {%- assign valid_items = coll | where_exp: "item", "item.search_exclude != true" -%}
    {%- assign documents = documents | concat: valid_items -%}
  {%- endif -%}
{%- endfor -%}

{%- comment -%}
Add HTML pages as well, excluding the search file itself and excluded pages.
{%- endcomment -%}
{%- assign html_pages = site.html_pages | where_exp: "item", "item.search_exclude != true and item.url != '/search.json'" -%}
{%- assign documents = documents | concat: html_pages -%}

[
{%- for item in documents -%}
  {
    "title": {{ item.title | default: item.name | jsonify }},
    "url": {{ item.url | relative_url | jsonify }},
    "collection": {{ item.collection | default: "pages" | jsonify }},
    "excerpt": {{ item.content | strip_html | strip_newlines | replace: '\n', ' ' | normalize_whitespace | jsonify }},
    "tags": {{ item.tags | default: empty | jsonify }},
    "category": {{ item.category | default: empty | jsonify }}
  }{% unless forloop.last %},{% endunless %}
{%- endfor -%}
]

Hugo Integration

Create a search.json template:

{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
{{- $list := slice -}}
{{- range $pages -}}
  {{- $list = $list | append (dict
    "title" .Title
    "content" .Plain
    "excerpt" .Summary
    "url" .Permalink
    "category" .Section
    "tags" .Params.tags
  ) -}}
{{- end -}}
{{- $list | jsonify -}}

Roadmap

TODO TASKS:

  • Add Lunr * search filter for more results https://lunrjs.com/guides/searching.html#scoring
  • Add Function search to split search content into multiple fields searchterm, tag, category
  • move init method to constuctor for cleaner code
  • add themes to foxinsearch package for auto cdn support
  • refactory code for better maintainability and usability make all constuctor options optional and all constructor methods pull from options object with is auto populated on constructor call
  • add logcad for foxinsearch console log output and debugging, also add helper function to use console.log if logcad is not available, just incase logcad is not needed etc.

πŸ“ License

MIT License - see LICENSE file for details.


🀝 Contributing

Contributions are welcome! Please feel free to submit a Merge Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Merge Request

πŸ”— Links


Made with ❀️ for static site generators

About

Lightweight client-side search engine for static sites using Lunr.js. Perfect for Jekyll, Hugo, and other static site generators. [Hydrozoa endpoint - gitlab/staticcanvas](https://gitlab.com/staticcanvas/foxinsearch)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages