Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run tests
run: yarn test
- name: Run unit tests
run: yarn run test:unit
- name: Build application
run: yarn run build
59 changes: 53 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,24 +103,41 @@ Here is a table for the available options, usage questions, troubleshooting & gu
### templateMiddleware (Function) [optional]

A function that will be called whenever a match in the template is found.
It gets passed the current property name, property value, and the template.
It gets passed the current property name, property value, template, query, and match information.
If the function returns a non-undefined value, it gets replaced in the template.

This can be potentially useful for manipulating URLs etc.
**New Interface:**
```js
templateMiddleware(prop, value, template, query?, matchInfo?)
```

Example:
- `prop`: The property name being processed from the JSON data.
- `value`: The property value
- `template`: The template string
- `query`: The search query (optional)
- `matchInfo`: Array of match information objects with start/end positions and match types (optional)

This can be useful for manipulating URLs, highlighting search terms, or custom formatting.

**Basic Example:**
```js
SimpleJekyllSearch({
// ...other config
templateMiddleware: function(prop, value, template) {
if (prop === 'bar') {
return value.replace(/^\//, '')
searchResultTemplate: '<li>{title}</li>',
templateMiddleware: function(prop, value, template, query, matchInfo) {
if (prop === 'title') {
return value.toUpperCase()
}
},
})
```

**How it works:**
- Template: `'<li>{title}</li>'`
- When processing `{title}`: `prop = 'title'`, `value = 'my post'` → returns `'MY POST'`
- Final result: `'<li>MY POST</li>'`


### sortMiddleware (Function) [optional]

A function that will be used to sort the filtered results.
Expand All @@ -139,3 +156,33 @@ SimpleJekyllSearch({
},
})
```

### Built-in Highlight Middleware (Function) [optional]

Simple-Jekyll-Search now includes built-in highlighting functionality that can be easily integrated:

```js
import { createHighlightTemplateMiddleware } from 'simple-jekyll-search/middleware';

SimpleJekyllSearch({
// ...other config
templateMiddleware: createHighlightTemplateMiddleware({
className: 'search-highlight', // CSS class for highlighted text
maxLength: 200, // Maximum length of highlighted content
contextLength: 30 // Characters of context around matches
}),
})
```

**Highlight Options:**
- `className`: CSS class name for highlighted spans (default: 'search-highlight')
- `maxLength`: Maximum length of content to display (truncates with ellipsis)
- `contextLength`: Number of characters to show around matches when truncating

**CSS Styling:**
```css
.search-highlight {
background-color: yellow;
font-weight: bold;
}
```
94 changes: 92 additions & 2 deletions cypress/e2e/simple-jekyll-search.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ describe('Simple Jekyll Search', () => {

it('No results found', () => {
cy.get('#search-input')
.type('random');
.type('xyzabc123notfound');

cy.get('#results-container')
.contains('No results found');
.should('contain', 'No results found');
});

describe('Search Functionality Edge cases', () => {
Expand Down Expand Up @@ -60,4 +60,94 @@ describe('Simple Jekyll Search', () => {
.should('exist');
});
});

describe('Hybrid Strategy with Highlighting', () => {
it('should use literal search and highlight exact matches', () => {
cy.get('#search-input')
.type('Lorem');

cy.get('#results-container')
.should('be.visible');

// Should find the "This is just a test" post
cy.get('#results-container').contains('This is just a test').should('exist');

cy.get('#results-container .search-desc .search-highlight')
.should('exist')
.should('have.css', 'background-color', 'rgb(255, 255, 0)');

// Find highlights that contain Lorem (may be in multiple results)
cy.get('#results-container .search-desc .search-highlight')
.filter(':contains("Lorem")')
.should('have.length.at.least', 1);
});

it('should use literal search for multi-word queries and highlight', () => {
cy.get('#search-input')
.type('Lorem ipsum');

cy.get('#results-container')
.should('be.visible');

// Should find the "This is just a test" post
cy.get('#results-container').contains('This is just a test').should('exist');

cy.get('#results-container .search-desc .search-highlight')
.should('have.length.at.least', 1);

// Check that Lorem is highlighted somewhere in the results
cy.get('#results-container .search-desc .search-highlight')
.filter(':contains("Lorem")')
.should('exist');
});

it('should handle different search patterns with hybrid strategy', () => {
// Test single word search (uses fuzzy/literal)
cy.get('#search-input')
.clear()
.type('ipsum');

cy.get('#results-container li')
.should('have.length.at.least', 1);

cy.get('#results-container')
.should('contain.text', 'ipsum');
});

it('should handle partial matches with hybrid strategy', () => {
// Test another single word
cy.get('#search-input')
.clear()
.type('technical');

cy.get('#results-container li')
.should('have.length.at.least', 1);

cy.get('#results-container')
.should('contain.text', 'Technical');
});

it('should highlight multiple occurrences in literal search', () => {
cy.get('#search-input')
.type('test');

cy.get('#results-container')
.should('be.visible');

cy.get('#results-container .search-desc .search-highlight')
.should('have.length.at.least', 1);
});

it('should escape HTML in search results', () => {
cy.get('#search-input')
.type('sed');

cy.get('#results-container')
.should('be.visible');

cy.get('#results-container .search-desc')
.should('exist')
.and('not.contain', '<script>');
});
});
});
Loading