Lightweight client-side search engine for static sites using Lunr.js. Perfect for Jekyll, Hugo, and other static site generators.
-
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
-
CDN:
-
Link from a jsdelivr CDN
-
Link from a unpkg CDN
If you used a
CDNlink, you're good to go!foxinsearchwill be available in the global scope viawindow.foxinsearchfunctions available in the global scope in browser environment. -
if you use a
CNDwithtype=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
foxinsearchas a devDependency and don't forget to add it in yourpackage.jsondependencies. - Use:
import * as foxinsearch from 'foxinsearch';`
- Add
π Requirements:
Node.js for local builds; runs in modern browsers that provideconsole,localStorage, andCustomEvent.
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 |
-
Vanilla(
UMD(browser)) via<script src="path|url">tag: will be available in the global scope viawindow.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)withtype="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();
| 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
const foxin = new FoxinSearch();
// Set up search
foxin.init();
// search
foxin.search('javascript');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();<!-- 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 -->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 selectoroutputElement- Results container element or selectorinputElement- Search input element or selectorminSearchLength- Minimum search string lengthsearchDataUrl- URL to load search datasearchElement- Search input element or selectorsearchTerm- Initial search term (optional)tag- Filter by tag (optional)category- Filter by category (optional)outputElement- Results container element or selectorloaderElement- Loading indicator element or selector (optional)resultsElement- Results container element or selector (optional)
Returns: Promise<void>
Performs a manual search operation.
Parameters:
term- Search termtag- Filter by tag (optional)category- Filter by category (optional)
Returns: Array - Search results
Clears the current search and results.
Returns: void
Returns the number of items in the search index.
Returns: number
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
}
]title- The title of the contentcontent- Full text content (used for search indexing)url- Link to the contentexcerpt- Short description (displayed in results)
category- Content category for filteringtags- Array of tags for filteringcollection- Content collection for filteringsearch_exclude- Set totrueto exclude from search
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 -%}
]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 -}}- 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.
MIT License - see LICENSE file for details.
Contributions are welcome! Please feel free to submit a Merge Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Merge Request
Made with β€οΈ for static site generators