├── .npmignore ├── .babelrc ├── example.gif ├── src ├── index.js ├── actions.js ├── constants.js ├── reducer.js └── middleware.js ├── webworker └── worker.wrk.js ├── webpack.config.development.js ├── webpack.config.production.js ├── webpack.config.base.js ├── lib ├── index.js ├── actions.js ├── constants.js ├── reducer.js └── middleware.js ├── test ├── reducer.test.js ├── actions.test.js └── middleware.test.js ├── package.json ├── readme.md └── dist └── reduxLunr.js /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": "0" 3 | } -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swennemans/redux-lunr/HEAD/example.gif -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | loadDocsIntoIndex, 3 | loadStateIntoIndex, 4 | lunrStartSearch, 5 | lunrResetSearchResults 6 | } from './actions.js' 7 | 8 | import createLunrMiddleware from './middleware.js'; 9 | import lunrReducer from './reducer.js'; 10 | 11 | export { 12 | loadDocsIntoIndex, 13 | loadStateIntoIndex, 14 | lunrStartSearch, 15 | lunrResetSearchResults, 16 | createLunrMiddleware, 17 | lunrReducer 18 | } 19 | -------------------------------------------------------------------------------- /webworker/worker.wrk.js: -------------------------------------------------------------------------------- 1 | import lunr from 'lunr'; 2 | 3 | self.onmessage = function(event) { 4 | //var lunr = require('lunr'); 5 | 6 | const {options: {index}, _toIndex} = JSON.parse(event.data); 7 | 8 | /* Dynamically create lunr.js index */ 9 | let idx = lunr(function() { 10 | Object.keys(index).forEach((key) => { 11 | if (key === 'ref') { 12 | console.log('key is', index[key]); 13 | this.ref(index[key]) 14 | } else { 15 | this.field(key, index[key]) 16 | } 17 | }) 18 | }); 19 | 20 | /* index passed docs */ 21 | _toIndex.forEach(function(doc) { 22 | idx.add(doc) 23 | }); 24 | 25 | /* Return created index */ 26 | self.postMessage(JSON.stringify(idx.toJSON())) 27 | }; -------------------------------------------------------------------------------- /webpack.config.development.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | module: { 8 | loaders: [ 9 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/} 10 | ], 11 | postLoaders: [{ 12 | test: /.wrk\.js$/, 13 | include: path.join(__dirname, 'src'), 14 | loader: 'worker-loader', 15 | }] 16 | }, 17 | output: { 18 | library: 'ReduxLunr', 19 | libraryTarget: 'umd' 20 | }, 21 | resolve: { 22 | extensions: ['', '.js'] 23 | }, 24 | plugins: [ 25 | new webpack.optimize.OccurenceOrderPlugin(), 26 | new webpack.DefinePlugin({ 27 | 'process.env.NODE_ENV': JSON.stringify('development') 28 | }) 29 | ] 30 | }; -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | module: { 8 | loaders: [ 9 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/} 10 | ] 11 | }, 12 | output: { 13 | library: 'ReduxLunr', 14 | libraryTarget: 'umd' 15 | }, 16 | resolve: { 17 | extensions: ['', '.js'] 18 | }, 19 | plugins: [ 20 | new webpack.optimize.OccurenceOrderPlugin(), 21 | new webpack.DefinePlugin({ 22 | 'process.env.NODE_ENV': JSON.stringify('production') 23 | }), 24 | new webpack.optimize.UglifyJsPlugin({ 25 | compressor: { 26 | screw_ie8: true, 27 | warnings: false 28 | } 29 | }) 30 | ] 31 | }; -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | 6 | module.exports = { 7 | module: { 8 | loaders: [ 9 | {test: /\.js$/, loaders: ['babel-loader'], exclude: /node_modules/} 10 | ], 11 | postLoaders: [{ 12 | test: /.wrk\.js$/, 13 | //exclude: /node_modules/, 14 | include: path.join(__dirname, 'src'), 15 | loaders: ['worker-loader'], 16 | }] 17 | }, 18 | output: { 19 | library: 'ReduxLunr', 20 | libraryTarget: 'umd' 21 | }, 22 | resolve: { 23 | extensions: ['', '.js'] 24 | }, 25 | plugins: [ 26 | new webpack.optimize.OccurenceOrderPlugin(), 27 | new webpack.DefinePlugin({ 28 | 'process.env.NODE_ENV': JSON.stringify('development') 29 | }) 30 | ] 31 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _actionsJs = require('./actions.js'); 10 | 11 | var _middlewareJs = require('./middleware.js'); 12 | 13 | var _middlewareJs2 = _interopRequireDefault(_middlewareJs); 14 | 15 | var _reducerJs = require('./reducer.js'); 16 | 17 | var _reducerJs2 = _interopRequireDefault(_reducerJs); 18 | 19 | exports.loadDocsIntoIndex = _actionsJs.loadDocsIntoIndex; 20 | exports.loadStateIntoIndex = _actionsJs.loadStateIntoIndex; 21 | exports.lunrStartSearch = _actionsJs.lunrStartSearch; 22 | exports.lunrResetSearchResults = _actionsJs.lunrResetSearchResults; 23 | exports.createLunrMiddleware = _middlewareJs2['default']; 24 | exports.lunrReducer = _reducerJs2['default']; -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | import { 2 | LUNR_INDEX_DOCS, 3 | LUNR_INDEX_STATE, 4 | LUNR_SEARCH_START, 5 | LUNR_SEARCH_RESET, 6 | LUNR_LOAD_SEARCH_INDEX 7 | } from './constants.js' 8 | 9 | import {SEARCH_LUNR} from './middleware.js' 10 | 11 | export function loadDocsIntoIndex(docs) { 12 | return { 13 | type: LUNR_INDEX_DOCS, 14 | _toIndex: docs 15 | } 16 | } 17 | export function loadStateIntoIndex(state) { 18 | return { 19 | type: LUNR_INDEX_STATE 20 | } 21 | } 22 | export function loadPreparedIndexIntoIndex(index) { 23 | return { 24 | type: LUNR_LOAD_SEARCH_INDEX, 25 | _index: index 26 | } 27 | } 28 | export function lunrStartSearch(query, limit) { 29 | return { 30 | type: LUNR_SEARCH_START, 31 | _query: query ? query: false, 32 | _limit: limit ? limit: false 33 | } 34 | } 35 | export function lunrResetSearchResults() { 36 | return { 37 | type: LUNR_SEARCH_RESET 38 | } 39 | } -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | import {SEARCH_LUNR} from './middleware.js' 2 | 3 | /* Load (sub)set of redux state into index. 4 | Which subset is defined by the arguments passed. 5 | * */ 6 | export const LUNR_INDEX_STATE = `${SEARCH_LUNR}INDEX_STATE`; 7 | export const LUNR_INDEX_STATE_SUCCESS = `${SEARCH_LUNR}INDEX_STATE_SUCCESS`; 8 | /* Load predefined (fetched from server?) array of docs into index 9 | * takes this array as argument. 10 | * */ 11 | export const LUNR_INDEX_DOCS = `${SEARCH_LUNR}INDEX_DOCS`; 12 | export const LUNR_INDEX_DOCS_SUCCESS = `${SEARCH_LUNR}LOAD_INDEX_DOCS_SUCCESS`; 13 | 14 | /* Add an prepared index (for example on server) as search index */ 15 | export const LUNR_LOAD_SEARCH_INDEX = `${SEARCH_LUNR}LOAD_SEARCH_INDEX`; 16 | export const LUNR_LOAD_SEARCH_INDEX_SUCCESS = `${SEARCH_LUNR}LOAD_SEARCH_INDEX_SUCCESS `; 17 | 18 | 19 | export const LUNR_SEARCH_START = `${SEARCH_LUNR}SEARCH_START`; 20 | export const LUNR_SEARCH_SUCCESS = `${SEARCH_LUNR}SEARCH_SUCCESS`; 21 | export const LUNR_SEARCH_RESET = `${SEARCH_LUNR}SEARCH_RESET`; -------------------------------------------------------------------------------- /lib/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | exports.loadDocsIntoIndex = loadDocsIntoIndex; 7 | exports.loadStateIntoIndex = loadStateIntoIndex; 8 | exports.loadPreparedIndexIntoIndex = loadPreparedIndexIntoIndex; 9 | exports.lunrStartSearch = lunrStartSearch; 10 | exports.lunrResetSearchResults = lunrResetSearchResults; 11 | 12 | var _constantsJs = require('./constants.js'); 13 | 14 | var _middlewareJs = require('./middleware.js'); 15 | 16 | function loadDocsIntoIndex(docs) { 17 | return { 18 | type: _constantsJs.LUNR_INDEX_DOCS, 19 | _toIndex: docs 20 | }; 21 | } 22 | 23 | function loadStateIntoIndex(state) { 24 | return { 25 | type: _constantsJs.LUNR_INDEX_STATE 26 | }; 27 | } 28 | 29 | function loadPreparedIndexIntoIndex(index) { 30 | return { 31 | type: _constantsJs.LUNR_LOAD_SEARCH_INDEX, 32 | _index: index 33 | }; 34 | } 35 | 36 | function lunrStartSearch(query, limit) { 37 | return { 38 | type: _constantsJs.LUNR_SEARCH_START, 39 | _query: query ? query : false, 40 | _limit: limit ? limit : false 41 | }; 42 | } 43 | 44 | function lunrResetSearchResults() { 45 | return { 46 | type: _constantsJs.LUNR_SEARCH_RESET 47 | }; 48 | } -------------------------------------------------------------------------------- /test/reducer.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import * as types from '../src/constants.js'; 3 | import reducer from '../src/reducer'; 4 | 5 | const getInitialState = () => { 6 | return { 7 | docs: [], 8 | results: [], 9 | loadingStarted: false, 10 | loadingError: false, 11 | loadingSuccess: false, 12 | isSearching: true, 13 | query: '', 14 | searchIndex: undefined 15 | }; 16 | }; 17 | 18 | test('Reducer should return default state', (t) => { 19 | t.deepEqual(reducer(undefined, {}), getInitialState()) 20 | t.end() 21 | }); 22 | 23 | test('Reducer should display search results after succesfull search', (t) => { 24 | 25 | const firstState = reducer(getInitialState(), {type: types.LUNR_SEARCH_START}); 26 | const secondState = reducer(undefined, {type: types.LUNR_SEARCH_SUCCESS, results: ["a", "b", "c"]}); 27 | 28 | t.ok(firstState.isSearching, "isSearching should be true while searching"); 29 | t.notOk(secondState.isSearching, "isSearching should be false with success"); 30 | t.deepEqual(secondState.results, ["a", "b", "c"], "Search results should be displayed"); 31 | 32 | t.end() 33 | }); 34 | 35 | test('Reducer result should be empty atter LUNR_SEARCH_RESET', (t) => { 36 | const firstState = reducer({}, {type: types.LUNR_SEARCH_SUCCESS, results: ["a", "b", "c"]}); 37 | const secondState = reducer({}, {type: types.LUNR_SEARCH_RESET}); 38 | 39 | t.deepEqual(secondState.results, [], 'Results should return an empty array') 40 | t.end() 41 | }) -------------------------------------------------------------------------------- /test/actions.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import * as actions from '../src/actions.js' 3 | import * as types from '../src/constants.js'; 4 | 5 | test('It should return correct actions', (t) => { 6 | 7 | const _toIndex = [ 8 | {id: "1", header: "Header 1", body: "body 1"}, 9 | {id: "2", header: "Header 2", body: "body 2"}, 10 | {id: "3", header: "Header 3", body: "body 3"} 11 | ]; 12 | 13 | const expectedActionIndexDocs = { 14 | type: types.LUNR_INDEX_DOCS, 15 | _toIndex 16 | }; 17 | 18 | const expectedActionIndexState = { 19 | type: types.LUNR_INDEX_STATE, 20 | }; 21 | 22 | const expectedActionSearch = { 23 | type: types.LUNR_SEARCH_START, 24 | _query: "query", 25 | _limit: 10 26 | }; 27 | 28 | const expectedActionLoadPreparedIndex = { 29 | type: types.LUNR_LOAD_SEARCH_INDEX, 30 | _index: "{}" 31 | }; 32 | 33 | const expectedActionResetSearchResults = { 34 | type: types.LUNR_SEARCH_RESET 35 | }; 36 | 37 | t.deepEqual(actions.loadDocsIntoIndex(_toIndex), expectedActionIndexDocs, "loadDocsIntoIndex should return correct action"); 38 | t.deepEqual(actions.loadStateIntoIndex(), expectedActionIndexState, "loadStateIntoIndex should return correct action"); 39 | t.deepEqual(actions.lunrStartSearch("query", 10), expectedActionSearch, "lunrStartSearch should return correct action"); 40 | t.deepEqual(actions.lunrResetSearchResults(), expectedActionResetSearchResults, "lunrResetSearchResults should return correct action"); 41 | t.deepEqual(actions.loadPreparedIndexIntoIndex("{}"), expectedActionLoadPreparedIndex, "loadPreparedIndexIntoIndex should return correct action"); 42 | t.end() 43 | }); 44 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _middlewareJs = require('./middleware.js'); 8 | 9 | /* Load (sub)set of redux state into index. 10 | Which subset is defined by the arguments passed. 11 | * */ 12 | var LUNR_INDEX_STATE = _middlewareJs.SEARCH_LUNR + 'INDEX_STATE'; 13 | exports.LUNR_INDEX_STATE = LUNR_INDEX_STATE; 14 | var LUNR_INDEX_STATE_SUCCESS = _middlewareJs.SEARCH_LUNR + 'INDEX_STATE_SUCCESS'; 15 | exports.LUNR_INDEX_STATE_SUCCESS = LUNR_INDEX_STATE_SUCCESS; 16 | /* Load predefined (fetched from server?) array of docs into index 17 | * takes this array as argument. 18 | * */ 19 | var LUNR_INDEX_DOCS = _middlewareJs.SEARCH_LUNR + 'INDEX_DOCS'; 20 | exports.LUNR_INDEX_DOCS = LUNR_INDEX_DOCS; 21 | var LUNR_INDEX_DOCS_SUCCESS = _middlewareJs.SEARCH_LUNR + 'LOAD_INDEX_DOCS_SUCCESS'; 22 | 23 | exports.LUNR_INDEX_DOCS_SUCCESS = LUNR_INDEX_DOCS_SUCCESS; 24 | /* Add an prepared index (for example on server) as search index */ 25 | var LUNR_LOAD_SEARCH_INDEX = _middlewareJs.SEARCH_LUNR + 'LOAD_SEARCH_INDEX'; 26 | exports.LUNR_LOAD_SEARCH_INDEX = LUNR_LOAD_SEARCH_INDEX; 27 | var LUNR_LOAD_SEARCH_INDEX_SUCCESS = _middlewareJs.SEARCH_LUNR + 'LOAD_SEARCH_INDEX_SUCCESS '; 28 | 29 | exports.LUNR_LOAD_SEARCH_INDEX_SUCCESS = LUNR_LOAD_SEARCH_INDEX_SUCCESS; 30 | var LUNR_SEARCH_START = _middlewareJs.SEARCH_LUNR + 'SEARCH_START'; 31 | exports.LUNR_SEARCH_START = LUNR_SEARCH_START; 32 | var LUNR_SEARCH_SUCCESS = _middlewareJs.SEARCH_LUNR + 'SEARCH_SUCCESS'; 33 | exports.LUNR_SEARCH_SUCCESS = LUNR_SEARCH_SUCCESS; 34 | var LUNR_SEARCH_RESET = _middlewareJs.SEARCH_LUNR + 'SEARCH_RESET'; 35 | exports.LUNR_SEARCH_RESET = LUNR_SEARCH_RESET; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-lunr", 3 | "version": "0.2.0", 4 | "description": "Lunr.js as middleware for Redux", 5 | "main": "lib/index.js", 6 | "jsnext:main": "src/index.js", 7 | "directories": { 8 | "test": "tests" 9 | }, 10 | "watch": { 11 | "test-dev": "*.test.js" 12 | }, 13 | "scripts": { 14 | "clean": "rm -rf dist && rm -rf lib", 15 | "build:lib": "babel src --out-dir lib", 16 | "build:umd": "webpack src/index.js dist/reduxLunr.js --config webpack.config.production.js", 17 | "build:umd:min": "webpack src/index.js dist/reduxLunr.min.js --config webpack.config.production.js", 18 | "build": "npm run build:lib && npm run build:umd", 19 | "test-dev": "NODE_ENV=test node ./test | faucet", 20 | "prepublish": "npm run clean && npm run build", 21 | "watch": "npm-watch", 22 | "test": "npm run watch -s" 23 | }, 24 | "author": "Sven Roeterdink ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/swennemans/redux-lunr/issues" 28 | }, 29 | "homepage": "https://github.com/swennemans/redux-lunr", 30 | "dependencies": { 31 | "lunr": "^0.6.0" 32 | }, 33 | "devDependencies": { 34 | "babel": "^5.8.23", 35 | "babel-core": "^5.8.25", 36 | "babel-loader": "^5.3.2", 37 | "babel-plugin-syntax-object-rest-spread": "^6.0.2", 38 | "babel-preset-es2015": "^6.0.12", 39 | "faucet": "0.0.1", 40 | "npm-watch": "0.0.1", 41 | "rimraf": "^2.4.3", 42 | "sinon": "^1.17.2", 43 | "tape": "^4.2.2", 44 | "webpack": "^1.12.2" 45 | }, 46 | "keywords": [ 47 | "redux", 48 | "redux middleware", 49 | "lunr", 50 | "lunr.js", 51 | "search" 52 | ], 53 | "npmFileMap": [ 54 | { 55 | "basePath": "/dist/", 56 | "files": [ 57 | "*.js" 58 | ] 59 | } 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | LUNR_INDEX_STATE, 3 | LUNR_INDEX_DOCS, 4 | LUNR_INDEX_DOCS_SUCCESS, 5 | LUNR_SEARCH_START, 6 | LUNR_SEARCH_SUCCESS, 7 | LUNR_INDEX_STATE_SUCCESS, 8 | LUNR_SEARCH_RESET 9 | } from './constants.js'; 10 | 11 | import { SEARCH_LUNR } from './middleware.js' 12 | 13 | export default function lunr(state = { 14 | docs: [], 15 | results: [], 16 | query: "", 17 | loadingStarted: false, 18 | loadingError: false, 19 | loadingSuccess: false, 20 | isSearching: true, 21 | searchIndex: undefined 22 | }, action) { 23 | switch (action.type) { 24 | case LUNR_INDEX_DOCS: 25 | return Object.assign({}, state, { 26 | loadingStarted: true, 27 | loadingError: true, 28 | loadingSuccess: true 29 | }); 30 | case LUNR_INDEX_DOCS_SUCCESS: 31 | return Object.assign({}, state, { 32 | loadingStarted: false, 33 | loadingError: false, 34 | loadingSuccess: true, 35 | searchIndex: action.searchIndex, 36 | docs: action.docs 37 | }); 38 | case LUNR_INDEX_STATE: 39 | return Object.assign({}, state, { 40 | loadingStarted: true, 41 | loadingError: false, 42 | }); 43 | case LUNR_INDEX_STATE_SUCCESS: 44 | return Object.assign({}, state, { 45 | loadingStarted: false, 46 | loadingError: false, 47 | searchIndex: action.searchIndex 48 | }); 49 | case LUNR_SEARCH_START: 50 | return Object.assign({}, state, { 51 | query: action._query, 52 | isSearching: true 53 | }); 54 | case LUNR_SEARCH_SUCCESS: 55 | return Object.assign({}, state, { 56 | isSearching: false, 57 | results: action.results 58 | }); 59 | case LUNR_SEARCH_RESET: 60 | return Object.assign({}, state, { 61 | isSearching: false, 62 | results: [] 63 | }); 64 | default: 65 | return state; 66 | } 67 | } -------------------------------------------------------------------------------- /lib/reducer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | exports['default'] = lunr; 7 | 8 | var _constantsJs = require('./constants.js'); 9 | 10 | var _middlewareJs = require('./middleware.js'); 11 | 12 | function lunr(state, action) { 13 | if (state === undefined) state = { 14 | docs: [], 15 | results: [], 16 | query: "", 17 | loadingStarted: false, 18 | loadingError: false, 19 | loadingSuccess: false, 20 | isSearching: true, 21 | searchIndex: undefined 22 | }; 23 | 24 | switch (action.type) { 25 | case _constantsJs.LUNR_INDEX_DOCS: 26 | return Object.assign({}, state, { 27 | loadingStarted: true, 28 | loadingError: true, 29 | loadingSuccess: true 30 | }); 31 | case _constantsJs.LUNR_INDEX_DOCS_SUCCESS: 32 | return Object.assign({}, state, { 33 | loadingStarted: false, 34 | loadingError: false, 35 | loadingSuccess: true, 36 | searchIndex: action.searchIndex, 37 | docs: action.docs 38 | }); 39 | case _constantsJs.LUNR_INDEX_STATE: 40 | return Object.assign({}, state, { 41 | loadingStarted: true, 42 | loadingError: false 43 | }); 44 | case _constantsJs.LUNR_INDEX_STATE_SUCCESS: 45 | return Object.assign({}, state, { 46 | loadingStarted: false, 47 | loadingError: false, 48 | searchIndex: action.searchIndex 49 | }); 50 | case _constantsJs.LUNR_SEARCH_START: 51 | return Object.assign({}, state, { 52 | query: action._query, 53 | isSearching: true 54 | }); 55 | case _constantsJs.LUNR_SEARCH_SUCCESS: 56 | return Object.assign({}, state, { 57 | isSearching: false, 58 | results: action.results 59 | }); 60 | case _constantsJs.LUNR_SEARCH_RESET: 61 | return Object.assign({}, state, { 62 | isSearching: false, 63 | results: [] 64 | }); 65 | default: 66 | return state; 67 | } 68 | } 69 | 70 | module.exports = exports['default']; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![](https://raw.githubusercontent.com/swennemans/redux-lunr/master/example.gif) 2 | 3 | # redux-lunr 4 | 5 | Redux middleware that makes your store searchable with Lunr.js 6 | 7 | ## Project Status 8 | * Fairly stable but API might change a bit, making it more user friendly. 9 | * Example: not yet finished. Will finish when I'm back from my holiday. 10 | 11 | 12 | ## Installation 13 | 14 | ` npm install redux-lunr --save ` 15 | 16 | ## Implementation 17 | 18 | **Step 1** : add the `redux-lunr` reducer to Redux. 19 | ```js 20 | import { combineReducers } from 'redux' 21 | import { lunrReducer } from 'redux-lunr' 22 | ... 23 | const rootReducer = combineReducers({ 24 | //other reducers here 25 | lunr: lunrReducer 26 | }) 27 | ``` 28 | **Step 2** : Define options. 29 | ```js 30 | const lunrOptions = { 31 | index: { 32 | ref: 'id', 33 | name: { boost: 15 }, 34 | bio: { boost: 5 } 35 | }, 36 | mapper: (doc) => { 37 | return { 38 | id: doc.id, 39 | name: doc.profile.name, 40 | bio: doc.profile.bio 41 | } 42 | }, 43 | store: { 44 | existingStore: true, 45 | reducer: 'users', 46 | entity: 'profiles' 47 | } 48 | } 49 | ``` 50 | 51 | * `index`: consists of your [Lunr index](http://lunrjs.com/); 52 | * `mapper` : optional mapper function that you can define if you want to index nested properties. 53 | * `store` : If you want to index an existing store, pass the information here. Define the `reducer` and the `entities`. 54 | 55 | For example, a `users` reducer that would match the above options: 56 | ``` 57 | function users(state = { 58 | //other properties here 59 | profiles: [ 60 | {id: 1, profile: { name: 'Foo', bio: 'Awesome life story' }}, 61 | {id: 2, profile: { name: 'Bar', bio: 'An even better life story' }} 62 | ] 63 | }, actions) { 64 | switch (action.type) { 65 | //etc 66 | } 67 | } 68 | ``` 69 | 70 | **Step 3** : add the `redux-lunr` middleware to Redux. 71 | 72 | 73 | ```js 74 | import { createStore, applyMiddleware, compose } from 'redux' 75 | import { createLunrMiddleware } from 'redux-lunr' 76 | import thunkMiddleware from 'redux-thunk' 77 | 78 | const finalCreateStore = compose( 79 | applyMiddleware(createLogger(),createLunrMiddleware(lunrOptions)) 80 | )(createStore); 81 | ``` 82 | 83 | ## usage 84 | 85 | `Redux-lunr`'s API has a number of actions you can utilize. 86 | 87 | #### `loadDocsIntoIndex(documents: Array)` 88 | ```js 89 | import { loadDocsIntoIndex } from 'redux-lunr' 90 | dispatch(loadDocsIntoIndex([{}, {}, {}]) 91 | ``` 92 | 93 | Pass an array of objects to index and thus make it searchable. 94 | 95 | #### `loadStateIntoIndex()` 96 | ```js 97 | import { loadStateIntoIndex } from 'redux-lunr' 98 | dispatch(loadStateIntoIndex()) 99 | ``` 100 | When you want to search an existing store (defined in the options object), call `loadStateIntoIndex()` 101 | to actually index this store. 102 | 103 | #### `loadPreparedIndexIntoIndex(preparedSearchIndex)` 104 | ```js 105 | import { loadPreparedIndexIntoIndex } from 'redux-lunr' 106 | dispatch(loadPreparedIndexIntoIndex(serializedLunrIndex)) 107 | ``` 108 | 109 | If you want to index a huge number of documents, it's probably smarter to do this indexing on your server. Serialize this index and load the index on the client. See https://github.com/olivernn/lunr.js/issues/60 110 | 111 | You can pass the serialized index to fhis function. 112 | 113 | 114 | #### `lunrStartSearch(query: String, limit: Int)` 115 | Do your searching here. 116 | The results will be saved in in the `lunr Reducer` under the property `results` 117 | 118 | ```js 119 | import { lunrStartSearch } from 'redux-lunr' 120 | dispatch(lunrStartSearch('foo', 10)) 121 | ``` 122 | 123 | 124 | #### `lunrResetSearchResults()` 125 | 126 | ```js 127 | import { lunrResetSearchResults } from 'redux-lunr' 128 | dispatch(lunrResetSearchResults()) 129 | ``` 130 | 131 | Reset the search results. Resets the property results in the `lunr Reducer` 132 | 133 | 134 | ## TODO 135 | * Add pipeline functions 136 | * Add example 137 | 138 | 139 | -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | import test from 'tape'; 2 | import * as types from '../src/constants.js'; 3 | import sinon from 'sinon'; 4 | import createLunrMiddleware from '../src/middleware.js'; 5 | import {SEARCH_LUNR} from '../src/middleware.js' 6 | 7 | import { 8 | loadDocsIntoIndex, 9 | loadStateIntoIndex 10 | } 11 | from '../src/actions.js' 12 | 13 | const getStore = (state) => { 14 | return { 15 | getState: sinon.stub().returns(state || {}), 16 | dispatch: sinon.spy() 17 | }; 18 | }; 19 | 20 | const getState = () => { 21 | return { 22 | lunr: { 23 | searchIndex: { 24 | search: function() { 25 | return [{name: "Sven", bio: "ab"}] 26 | } 27 | }, 28 | docs: [{name: "Sven", bio: "ab"}, {name: "Tesla", bio: "ad"}] 29 | } 30 | } 31 | }; 32 | 33 | const getOptions = () => { 34 | return { 35 | index: { 36 | ref: 'id', 37 | name: {boost: 10}, 38 | bio: {}, 39 | interesses: {}, 40 | city: {}, 41 | types: {} 42 | }, 43 | store: { 44 | existingStore: false, 45 | reducer: "profiles", 46 | entity: "profiles" 47 | } 48 | }; 49 | } 50 | 51 | test('Middleware should return a (middleware) function', (t) => { 52 | t.ok(typeof createLunrMiddleware() === 'function'); 53 | t.end() 54 | }); 55 | 56 | test('Middleware only processes actions prefixed with @@REDUX_LUNR/', (t) => { 57 | const store = getStore(); 58 | const next = sinon.spy(); 59 | 60 | const result = createLunrMiddleware({})(store)(next)({ 61 | type: "FOO_BAR" 62 | }); 63 | 64 | t.equal(next.firstCall.args[0].type, "FOO_BAR", "Non redux-lunr action should be nexted"); 65 | t.end() 66 | }); 67 | 68 | test('Search should work correctly', t => { 69 | 70 | const options = getOptions(); 71 | const state = getState() 72 | const store = getStore(state); 73 | const next = sinon.spy(); 74 | 75 | const _query = "sv"; 76 | const _limit = 1; 77 | 78 | 79 | const result = createLunrMiddleware(options)(store)(next)({ 80 | type: types.LUNR_SEARCH_START, 81 | _query, 82 | _limit 83 | }); 84 | 85 | 86 | t.equal(next.firstCall.args[0].type, types.LUNR_SEARCH_START, "SEARCH_START should be send to reducer"); 87 | t.equal(next.secondCall.args[0].type, types.LUNR_SEARCH_SUCCESS, "SEARCH_SUCCESS should be send to reducer"); 88 | t.equal(next.secondCall.args[0].results.length, _limit, "Returned results should be limited"); 89 | 90 | t.end() 91 | 92 | }); 93 | 94 | test("Middleware should throw errors", t => { 95 | const state = getState() 96 | const store = getStore(state); 97 | const next = sinon.spy(); 98 | const options = getOptions(); 99 | 100 | t.plan(6); 101 | try { 102 | createLunrMiddleware(options)(store)(next)({ 103 | type: types.LUNR_INDEX_DOCS, 104 | _toIndex: [] 105 | }); 106 | } 107 | catch(e) { 108 | t.ok(e instanceof Error, "Throw error when: passed documents array is empty"); 109 | } 110 | 111 | try { 112 | createLunrMiddleware(options)(store)(next)({ 113 | type: types.LUNR_SEARCH_START, 114 | _query: 12, 115 | _limit: 1 116 | }); 117 | } 118 | catch(e) { 119 | t.ok(e instanceof Error, "Throw error when: query is an integer"); 120 | } 121 | 122 | try { 123 | createLunrMiddleware(options)(store)(next)({ 124 | type: types.LUNR_SEARCH_START, 125 | _query: ["foo"], 126 | _limit: 1 127 | }); 128 | } 129 | catch(e) { 130 | t.ok(e instanceof Error, "Throw error when: query is a an array"); 131 | } 132 | 133 | try { 134 | createLunrMiddleware(options)(store)(next)({ 135 | type: types.LUNR_SEARCH_START, 136 | _query: {1: "queryme"}, 137 | _limit: 1 138 | }); 139 | } 140 | catch(e) { 141 | t.ok(e instanceof Error, "Throw error when: when query is a an object"); 142 | } 143 | 144 | try { 145 | createLunrMiddleware(options)(store)(next)({ 146 | type: types.LUNR_SEARCH_START, 147 | _query: "query", 148 | _limit: "5" 149 | }); 150 | } 151 | catch(e) { 152 | t.ok(e instanceof Error, "Throw error when: Limit is a string"); 153 | } 154 | try { 155 | createLunrMiddleware(options)(store)(next)({ 156 | type: types.LUNR_SEARCH_START, 157 | _query: "query", 158 | _limit: 1.5 159 | }); 160 | } 161 | catch(e) { 162 | t.ok(e instanceof Error, "Throw error when: limit is a float"); 163 | } 164 | }); 165 | 166 | 167 | test("Loading passed array of docs should work properly", t => { 168 | 169 | const options = getOptions(); 170 | const state = getState(); 171 | const store = getStore(state); 172 | const next = sinon.spy(); 173 | 174 | createLunrMiddleware(options)(store)(next)({ 175 | type: types.LUNR_INDEX_DOCS, 176 | _toIndex: [{}, {}, {}] 177 | }); 178 | 179 | t.equal(next.firstCall.args[0].type, types.LUNR_INDEX_DOCS, "LUNR_INDEX_DOCS should be send to reducer") 180 | t.equal(next.secondCall.args[0].type, types.LUNR_INDEX_DOCS_SUCCESS, "LUNR_INDEX_DOCS_SUCCESS should be send to reducer") 181 | t.end() 182 | }); 183 | 184 | -------------------------------------------------------------------------------- /src/middleware.js: -------------------------------------------------------------------------------- 1 | import lunr from 'lunr'; 2 | 3 | import { 4 | LUNR_INDEX_STATE, 5 | LUNR_INDEX_DOCS, 6 | LUNR_INDEX_DOCS_SUCCESS, 7 | LUNR_SEARCH_START, 8 | LUNR_SEARCH_SUCCESS, 9 | LUNR_INDEX_STATE_SUCCESS, 10 | LUNR_SEARCH_RESET, 11 | LUNR_LOAD_SEARCH_INDEX, 12 | LUNR_LOAD_SEARCH_INDEX_SUCCESS 13 | } from './constants.js'; 14 | 15 | function UnreconizedActionTypeException(message) { 16 | this.message = message; 17 | this.name = 'UnreconizedActionTypeException'; 18 | } 19 | 20 | /* Simple function that retrieve state based on reducer 21 | and entity. Arguments should be passed when initing 22 | middleware. Usually configureStore.js 23 | */ 24 | function getDataFromState(options, getState) { 25 | const {reducer, entity} = options.store; 26 | return getState()[reducer][entity]; 27 | } 28 | 29 | function createLunrIndex(options) { 30 | let index = options.index; 31 | 32 | return lunr(function() { 33 | Object.keys(index).forEach((key) => { 34 | if (key === 'ref') { 35 | this.ref(index[key]) 36 | } else { 37 | this.field(key, index[key]) 38 | } 39 | }) 40 | }); 41 | } 42 | 43 | /* apply mapper function defined in options, useful when 44 | you work with nested properties. 45 | */ 46 | function applyMapper(_toIndex, mapper) { 47 | return _toIndex.map(mapper) 48 | } 49 | 50 | /* addToIndex creates an index based on the documents passed saved in _toIndex 51 | */ 52 | function addToIndex(_toIndex, options) { 53 | let idx; 54 | 55 | try { 56 | idx = createLunrIndex(options); 57 | _toIndex.forEach((doc) => { 58 | idx.add(doc) 59 | }) 60 | } catch (e) { 61 | throw new Error('Redux-Lunr: Error while indexing. Did you pass an array of valid objects?') 62 | } 63 | return idx; 64 | } 65 | 66 | 67 | /* Add documents to lunr reducer store */ 68 | function addToStore(_toIndex) { 69 | return _toIndex.map((doc) => { 70 | return doc 71 | }); 72 | } 73 | 74 | function retrieveResultsFromStore(getState, results) { 75 | if (results.length > 0) { 76 | const lunrStore = getState().lunr.docs; 77 | return flatten(results.map((result) => { 78 | return lunrStore.filter((doc) => { 79 | return doc.id === result.ref 80 | }) 81 | })); 82 | } else { 83 | return [] 84 | } 85 | } 86 | 87 | function doLunrSearch(getState, _query) { 88 | return getState().lunr.searchIndex.search(_query) 89 | } 90 | 91 | function retrieveResultsFromState(getState, options, results) { 92 | if (results.length > 0) { 93 | const {reducer, entity} = options.store; 94 | const store = getState()[reducer][entity]; 95 | 96 | return flatten(results.map((result) => { 97 | return store.filter((doc) => { 98 | return doc.id === result.ref 99 | }) 100 | })); 101 | } else { 102 | return [] 103 | } 104 | } 105 | 106 | function flatten(results) { 107 | return results.reduce((a, b) => { 108 | return a.concat(b) 109 | }) 110 | } 111 | 112 | function isInt(number) { 113 | if (typeof number === 'string') { 114 | return true 115 | } 116 | else if (!isNaN(number)) { 117 | return number % 1 !== 0 118 | } else { 119 | return true 120 | } 121 | } 122 | 123 | export const SEARCH_LUNR = '@@REDUX_LUNR/'; 124 | 125 | export default function createLunrMiddleware(options) { 126 | 127 | return function({dispatch, getState}) { 128 | return next => action => { 129 | 130 | if (action.type === undefined || action.type.indexOf(SEARCH_LUNR) == -1) { 131 | return next(action); 132 | } 133 | 134 | const searchLunr = action; 135 | 136 | const {_toIndex, _query, _limit, type, _index, ...rest} = searchLunr; 137 | if (!_toIndex instanceof Array) { 138 | throw new Error('Redux-Lunr: passed documents must be an array of objects') 139 | } 140 | if (_toIndex !== undefined && !_toIndex.length > 0) { 141 | throw new Error('Redux-Lunr: passed documents array is empty') 142 | } 143 | if (_query !== undefined && typeof _query !== 'string') { 144 | throw new Error('Redux-Lunr: search query must be a string!') 145 | } 146 | if (_limit !== undefined && isInt(_limit)) { 147 | throw new Error('Redux-Lunr: search limit must be an integer!') 148 | } 149 | if ( options.mapper !== undefined && typeof options.mapper !== 'function') { 150 | throw new Error('Redux-Lunr: mapper function must be a valid function!') 151 | } 152 | if (options.store.existingStore && (options.store.reducer === undefined || options.store.entity === undefined)) { 153 | throw new Error('Redux-Lunr: if using existing Redux Store please define a reducer and an entity') 154 | } 155 | /* Check if there is a mapper function passed */ 156 | const needToMap = options.mapper !== undefined; 157 | 158 | switch (type) { 159 | case LUNR_INDEX_DOCS: 160 | next(searchLunr); 161 | 162 | /* Create and save searchIndex */ 163 | const docsSearchIndex = !needToMap ? 164 | addToIndex(_toIndex, options) : 165 | addToIndex( applyMapper(_toIndex, options.mapper), options); 166 | 167 | next({ 168 | type: LUNR_INDEX_DOCS_SUCCESS, 169 | searchIndex: docsSearchIndex, 170 | docs: _toIndex 171 | }); 172 | break; 173 | case LUNR_INDEX_STATE: 174 | next(searchLunr); 175 | /* Create and save searchIndex */ 176 | const stateSearchIndex = !needToMap ? 177 | addToIndex( getDataFromState(options, getState), options ) : 178 | addToIndex( applyMapper( getDataFromState(options, getState), options.mapper), options); 179 | 180 | next({ 181 | type: LUNR_INDEX_STATE_SUCCESS, 182 | searchIndex: stateSearchIndex 183 | }); 184 | break; 185 | case LUNR_SEARCH_START: 186 | next(searchLunr); 187 | let results = options.store.existingStore ? 188 | retrieveResultsFromState(getState, options, doLunrSearch(getState, _query)) : 189 | retrieveResultsFromStore(getState, doLunrSearch(getState, _query)); 190 | 191 | next({ 192 | type: LUNR_SEARCH_SUCCESS, 193 | results: _limit ? results.slice(0, _limit) : results, 194 | query: _query 195 | }); 196 | break; 197 | case LUNR_LOAD_SEARCH_INDEX: 198 | next(searchLunr); 199 | next({ 200 | type: LUNR_LOAD_SEARCH_INDEX_SUCCESS, 201 | searchIndex: lunr.index.load(JSON.parse(_index)) 202 | }); 203 | break; 204 | case LUNR_SEARCH_RESET: 205 | next(searchLunr); 206 | break; 207 | default: 208 | throw new UnreconizedActionTypeException('Unknown action, ' + type); 209 | } 210 | } 211 | } 212 | } -------------------------------------------------------------------------------- /lib/middleware.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | exports['default'] = createLunrMiddleware; 7 | 8 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9 | 10 | function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } 11 | 12 | var _lunr = require('lunr'); 13 | 14 | var _lunr2 = _interopRequireDefault(_lunr); 15 | 16 | var _constantsJs = require('./constants.js'); 17 | 18 | function UnreconizedActionTypeException(message) { 19 | this.message = message; 20 | this.name = 'UnreconizedActionTypeException'; 21 | } 22 | 23 | /* Simple function that retrieve state based on reducer 24 | and entity. Arguments should be passed when initing 25 | middleware. Usually configureStore.js 26 | */ 27 | function getDataFromState(options, getState) { 28 | var _options$store = options.store; 29 | var reducer = _options$store.reducer; 30 | var entity = _options$store.entity; 31 | 32 | return getState()[reducer][entity]; 33 | } 34 | 35 | function createLunrIndex(options) { 36 | var index = options.index; 37 | 38 | return (0, _lunr2['default'])(function () { 39 | var _this = this; 40 | 41 | Object.keys(index).forEach(function (key) { 42 | if (key === 'ref') { 43 | _this.ref(index[key]); 44 | } else { 45 | _this.field(key, index[key]); 46 | } 47 | }); 48 | }); 49 | } 50 | 51 | /* apply mapper function defined in options, useful when 52 | you work with nested properties. 53 | */ 54 | function applyMapper(_toIndex, mapper) { 55 | return _toIndex.map(mapper); 56 | } 57 | 58 | /* addToIndex creates an index based on the documents passed saved in _toIndex 59 | */ 60 | function addToIndex(_toIndex, options) { 61 | var idx = undefined; 62 | 63 | try { 64 | idx = createLunrIndex(options); 65 | _toIndex.forEach(function (doc) { 66 | idx.add(doc); 67 | }); 68 | } catch (e) { 69 | throw new Error('Redux-Lunr: Error while indexing. Did you pass an array of valid objects?'); 70 | } 71 | return idx; 72 | } 73 | 74 | /* Add documents to lunr reducer store */ 75 | function addToStore(_toIndex) { 76 | return _toIndex.map(function (doc) { 77 | return doc; 78 | }); 79 | } 80 | 81 | function retrieveResultsFromStore(getState, results) { 82 | if (results.length > 0) { 83 | var _ret = (function () { 84 | var lunrStore = getState().lunr.docs; 85 | return { 86 | v: flatten(results.map(function (result) { 87 | return lunrStore.filter(function (doc) { 88 | return doc.id === result.ref; 89 | }); 90 | })) 91 | }; 92 | })(); 93 | 94 | if (typeof _ret === 'object') return _ret.v; 95 | } else { 96 | return []; 97 | } 98 | } 99 | 100 | function doLunrSearch(getState, _query) { 101 | return getState().lunr.searchIndex.search(_query); 102 | } 103 | 104 | function retrieveResultsFromState(getState, options, results) { 105 | if (results.length > 0) { 106 | var _ret2 = (function () { 107 | var _options$store2 = options.store; 108 | var reducer = _options$store2.reducer; 109 | var entity = _options$store2.entity; 110 | 111 | var store = getState()[reducer][entity]; 112 | 113 | return { 114 | v: flatten(results.map(function (result) { 115 | return store.filter(function (doc) { 116 | return doc.id === result.ref; 117 | }); 118 | })) 119 | }; 120 | })(); 121 | 122 | if (typeof _ret2 === 'object') return _ret2.v; 123 | } else { 124 | return []; 125 | } 126 | } 127 | 128 | function flatten(results) { 129 | return results.reduce(function (a, b) { 130 | return a.concat(b); 131 | }); 132 | } 133 | 134 | function isInt(number) { 135 | if (typeof number === 'string') { 136 | return true; 137 | } else if (!isNaN(number)) { 138 | return number % 1 !== 0; 139 | } else { 140 | return true; 141 | } 142 | } 143 | 144 | var SEARCH_LUNR = '@@REDUX_LUNR/'; 145 | 146 | exports.SEARCH_LUNR = SEARCH_LUNR; 147 | 148 | function createLunrMiddleware(options) { 149 | 150 | return function (_ref) { 151 | var dispatch = _ref.dispatch; 152 | var getState = _ref.getState; 153 | 154 | return function (next) { 155 | return function (action) { 156 | 157 | if (action.type === undefined || action.type.indexOf(SEARCH_LUNR) == -1) { 158 | return next(action); 159 | } 160 | 161 | var searchLunr = action; 162 | 163 | var _toIndex = searchLunr._toIndex; 164 | var _query = searchLunr._query; 165 | var _limit = searchLunr._limit; 166 | var type = searchLunr.type; 167 | var _index = searchLunr._index; 168 | 169 | var rest = _objectWithoutProperties(searchLunr, ['_toIndex', '_query', '_limit', 'type', '_index']); 170 | 171 | if (!_toIndex instanceof Array) { 172 | throw new Error('Redux-Lunr: passed documents must be an array of objects'); 173 | } 174 | if (_toIndex !== undefined && !_toIndex.length > 0) { 175 | throw new Error('Redux-Lunr: passed documents array is empty'); 176 | } 177 | if (_query !== undefined && typeof _query !== 'string') { 178 | throw new Error('Redux-Lunr: search query must be a string!'); 179 | } 180 | if (_limit !== undefined && isInt(_limit)) { 181 | throw new Error('Redux-Lunr: search limit must be an integer!'); 182 | } 183 | if (options.mapper !== undefined && typeof options.mapper !== 'function') { 184 | throw new Error('Redux-Lunr: mapper function must be a valid function!'); 185 | } 186 | if (options.store.existingStore && (options.store.reducer === undefined || options.store.entity === undefined)) { 187 | throw new Error('Redux-Lunr: if using existing Redux Store please define a reducer and an entity'); 188 | } 189 | /* Check if there is a mapper function passed */ 190 | var needToMap = options.mapper !== undefined; 191 | 192 | switch (type) { 193 | case _constantsJs.LUNR_INDEX_DOCS: 194 | next(searchLunr); 195 | 196 | /* Create and save searchIndex */ 197 | var docsSearchIndex = !needToMap ? addToIndex(_toIndex, options) : addToIndex(applyMapper(_toIndex, options.mapper), options); 198 | 199 | next({ 200 | type: _constantsJs.LUNR_INDEX_DOCS_SUCCESS, 201 | searchIndex: docsSearchIndex, 202 | docs: _toIndex 203 | }); 204 | break; 205 | case _constantsJs.LUNR_INDEX_STATE: 206 | next(searchLunr); 207 | /* Create and save searchIndex */ 208 | var stateSearchIndex = !needToMap ? addToIndex(getDataFromState(options, getState), options) : addToIndex(applyMapper(getDataFromState(options, getState), options.mapper), options); 209 | 210 | next({ 211 | type: _constantsJs.LUNR_INDEX_STATE_SUCCESS, 212 | searchIndex: stateSearchIndex 213 | }); 214 | break; 215 | case _constantsJs.LUNR_SEARCH_START: 216 | next(searchLunr); 217 | var results = options.store.existingStore ? retrieveResultsFromState(getState, options, doLunrSearch(getState, _query)) : retrieveResultsFromStore(getState, doLunrSearch(getState, _query)); 218 | 219 | next({ 220 | type: _constantsJs.LUNR_SEARCH_SUCCESS, 221 | results: _limit ? results.slice(0, _limit) : results, 222 | query: _query 223 | }); 224 | break; 225 | case _constantsJs.LUNR_LOAD_SEARCH_INDEX: 226 | next(searchLunr); 227 | next({ 228 | type: _constantsJs.LUNR_LOAD_SEARCH_INDEX_SUCCESS, 229 | searchIndex: _lunr2['default'].index.load(JSON.parse(_index)) 230 | }); 231 | break; 232 | case _constantsJs.LUNR_SEARCH_RESET: 233 | next(searchLunr); 234 | break; 235 | default: 236 | throw new UnreconizedActionTypeException('Unknown action, ' + type); 237 | } 238 | }; 239 | }; 240 | }; 241 | } -------------------------------------------------------------------------------- /dist/reduxLunr.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ReduxLunr=t():e.ReduxLunr=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(3),o=n(1),s=r(o),a=n(4),u=r(a);t.loadDocsIntoIndex=i.loadDocsIntoIndex,t.loadStateIntoIndex=i.loadStateIntoIndex,t.lunrStartSearch=i.lunrStartSearch,t.lunrResetSearchResults=i.lunrResetSearchResults,t.createLunrMiddleware=s.default,t.lunrReducer=u.default},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function i(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}function o(e){this.message=e,this.name="UnreconizedActionTypeException"}function s(e,t){var n=e.store,r=n.reducer,i=n.entity;return t()[r][i]}function a(e){var t=e.index;return g.default(function(){var e=this;Object.keys(t).forEach(function(n){"ref"===n?e.ref(t[n]):e.field(n,t[n])})})}function u(e,t){return e.map(t)}function c(e,t){var n=void 0;try{n=a(t),e.forEach(function(e){n.add(e)})}catch(r){throw new Error("Redux-Lunr: Error while indexing. Did you pass an array of valid objects?")}return n}function l(e,t){if(!(t.length>0))return[];var n=function(){var n=e().lunr.docs;return{v:f(t.map(function(e){return n.filter(function(t){return t.id===e.ref})}))}}();return"object"==typeof n?n.v:void 0}function d(e,t){return e().lunr.searchIndex.search(t)}function h(e,t,n){if(!(n.length>0))return[];var r=function(){var r=t.store,i=r.reducer,o=r.entity,s=e()[i][o];return{v:f(n.map(function(e){return s.filter(function(t){return t.id===e.ref})}))}}();return"object"==typeof r?r.v:void 0}function f(e){return e.reduce(function(e,t){return e.concat(t)})}function p(e){return"string"==typeof e?!0:isNaN(e)?!0:e%1!==0}function S(e){return function(t){var n=(t.dispatch,t.getState);return function(t){return function(r){if(void 0===r.type||-1==r.type.indexOf(y))return t(r);var a=r,f=a._toIndex,S=a._query,v=a._limit,m=a.type,E=a._index;i(a,["_toIndex","_query","_limit","type","_index"]);if(!f instanceof Array)throw new Error("Redux-Lunr: passed documents must be an array of objects");if(void 0!==f&&!f.length>0)throw new Error("Redux-Lunr: passed documents array is empty");if(void 0!==S&&"string"!=typeof S)throw new Error("Redux-Lunr: search query must be a string!");if(void 0!==v&&p(v))throw new Error("Redux-Lunr: search limit must be an integer!");if(void 0!==e.mapper&&"function"!=typeof e.mapper)throw new Error("Redux-Lunr: mapper function must be a valid function!");if(e.store.existingStore&&(void 0===e.store.reducer||void 0===e.store.entity))throw new Error("Redux-Lunr: if using existing Redux Store please define a reducer and an entity");var x=void 0!==e.mapper;switch(m){case _.LUNR_INDEX_DOCS:t(a);var R=x?c(u(f,e.mapper),e):c(f,e);t({type:_.LUNR_INDEX_DOCS_SUCCESS,searchIndex:R,docs:f});break;case _.LUNR_INDEX_STATE:t(a);var w=x?c(u(s(e,n),e.mapper),e):c(s(e,n),e);t({type:_.LUNR_INDEX_STATE_SUCCESS,searchIndex:w});break;case _.LUNR_SEARCH_START:t(a);var N=e.store.existingStore?h(n,e,d(n,S)):l(n,d(n,S));t({type:_.LUNR_SEARCH_SUCCESS,results:v?N.slice(0,v):N,query:S});break;case _.LUNR_LOAD_SEARCH_INDEX:t(a),t({type:_.LUNR_LOAD_SEARCH_INDEX_SUCCESS,searchIndex:g.default.index.load(JSON.parse(E))});break;case _.LUNR_SEARCH_RESET:t(a);break;default:throw new o("Unknown action, "+m)}}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=S;var v=n(5),g=r(v),_=n(2),y="@@REDUX_LUNR/";t.SEARCH_LUNR=y},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(1),i=r.SEARCH_LUNR+"INDEX_STATE";t.LUNR_INDEX_STATE=i;var o=r.SEARCH_LUNR+"INDEX_STATE_SUCCESS";t.LUNR_INDEX_STATE_SUCCESS=o;var s=r.SEARCH_LUNR+"INDEX_DOCS";t.LUNR_INDEX_DOCS=s;var a=r.SEARCH_LUNR+"LOAD_INDEX_DOCS_SUCCESS";t.LUNR_INDEX_DOCS_SUCCESS=a;var u=r.SEARCH_LUNR+"LOAD_SEARCH_INDEX";t.LUNR_LOAD_SEARCH_INDEX=u;var c=r.SEARCH_LUNR+"LOAD_SEARCH_INDEX_SUCCESS ";t.LUNR_LOAD_SEARCH_INDEX_SUCCESS=c;var l=r.SEARCH_LUNR+"SEARCH_START";t.LUNR_SEARCH_START=l;var d=r.SEARCH_LUNR+"SEARCH_SUCCESS";t.LUNR_SEARCH_SUCCESS=d;var h=r.SEARCH_LUNR+"SEARCH_RESET";t.LUNR_SEARCH_RESET=h},function(e,t,n){"use strict";function r(e){return{type:u.LUNR_INDEX_DOCS,_toIndex:e}}function i(e){return{type:u.LUNR_INDEX_STATE}}function o(e){return{type:u.LUNR_LOAD_SEARCH_INDEX,_index:e}}function s(e,t){return{type:u.LUNR_SEARCH_START,_query:e?e:!1,_limit:t?t:!1}}function a(){return{type:u.LUNR_SEARCH_RESET}}Object.defineProperty(t,"__esModule",{value:!0}),t.loadDocsIntoIndex=r,t.loadStateIntoIndex=i,t.loadPreparedIndexIntoIndex=o,t.lunrStartSearch=s,t.lunrResetSearchResults=a;var u=n(2);n(1)},function(e,t,n){"use strict";function r(e,t){switch(void 0===e&&(e={docs:[],results:[],query:"",loadingStarted:!1,loadingError:!1,loadingSuccess:!1,isSearching:!0,searchIndex:void 0}),t.type){case i.LUNR_INDEX_DOCS:return Object.assign({},e,{loadingStarted:!0,loadingError:!0,loadingSuccess:!0});case i.LUNR_INDEX_DOCS_SUCCESS:return Object.assign({},e,{loadingStarted:!1,loadingError:!1,loadingSuccess:!0,searchIndex:t.searchIndex,docs:t.docs});case i.LUNR_INDEX_STATE:return Object.assign({},e,{loadingStarted:!0,loadingError:!1});case i.LUNR_INDEX_STATE_SUCCESS:return Object.assign({},e,{loadingStarted:!1,loadingError:!1,searchIndex:t.searchIndex});case i.LUNR_SEARCH_START:return Object.assign({},e,{query:t._query,isSearching:!0});case i.LUNR_SEARCH_SUCCESS:return Object.assign({},e,{isSearching:!1,results:t.results});case i.LUNR_SEARCH_RESET:return Object.assign({},e,{isSearching:!1,results:[]});default:return e}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(2);n(1);e.exports=t.default},function(e,t,n){var r,i;!function(){var o=function(e){var t=new o.Index;return t.pipeline.add(o.trimmer,o.stopWordFilter,o.stemmer),e&&e.call(t,t),t};o.version="0.6.0",/*! 2 | * lunr.utils 3 | * Copyright (C) 2015 Oliver Nightingale 4 | */ 5 | o.utils={},o.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),o.utils.asString=function(e){return void 0===e||null===e?"":e.toString()},/*! 6 | * lunr.EventEmitter 7 | * Copyright (C) 2015 Oliver Nightingale 8 | */ 9 | o.EventEmitter=function(){this.events={}},o.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},o.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);this.events[e].splice(n,1),this.events[e].length||delete this.events[e]}},o.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)})}},o.EventEmitter.prototype.hasHandler=function(e){return e in this.events},/*! 10 | * lunr.tokenizer 11 | * Copyright (C) 2015 Oliver Nightingale 12 | */ 13 | o.tokenizer=function(e){return arguments.length&&null!=e&&void 0!=e?Array.isArray(e)?e.map(function(e){return o.utils.asString(e).toLowerCase()}):e.toString().trim().toLowerCase().split(o.tokenizer.seperator):[]},o.tokenizer.seperator=/[\s\-]+/,/*! 14 | * lunr.Pipeline 15 | * Copyright (C) 2015 Oliver Nightingale 16 | */ 17 | o.Pipeline=function(){this._stack=[]},o.Pipeline.registeredFunctions={},o.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&o.utils.warn("Overwriting existing registered function: "+t),e.label=t,o.Pipeline.registeredFunctions[e.label]=e},o.Pipeline.warnIfFunctionNotRegistered=function(e){var t=e.label&&e.label in this.registeredFunctions;t||o.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},o.Pipeline.load=function(e){var t=new o.Pipeline;return e.forEach(function(e){var n=o.Pipeline.registeredFunctions[e];if(!n)throw new Error("Cannot load un-registered function: "+e);t.add(n)}),t},o.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){o.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},o.Pipeline.prototype.after=function(e,t){o.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");n+=1,this._stack.splice(n,0,t)},o.Pipeline.prototype.before=function(e,t){o.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");this._stack.splice(n,0,t)},o.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},o.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,r=this._stack.length,i=0;n>i;i++){for(var o=e[i],s=0;r>s&&(o=this._stack[s](o,i,e),void 0!==o&&""!==o);s++);void 0!==o&&""!==o&&t.push(o)}return t},o.Pipeline.prototype.reset=function(){this._stack=[]},o.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return o.Pipeline.warnIfFunctionNotRegistered(e),e.label})},/*! 18 | * lunr.Vector 19 | * Copyright (C) 2015 Oliver Nightingale 20 | */ 21 | o.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},o.Vector.Node=function(e,t,n){this.idx=e,this.val=t,this.next=n},o.Vector.prototype.insert=function(e,t){this._magnitude=void 0;var n=this.list;if(!n)return this.list=new o.Vector.Node(e,t,n),this.length++;if(en.idx?n=n.next:(r+=t.val*n.val,t=t.next,n=n.next);return r},o.Vector.prototype.similarity=function(e){return this.dot(e)/(this.magnitude()*e.magnitude())},/*! 22 | * lunr.SortedSet 23 | * Copyright (C) 2015 Oliver Nightingale 24 | */ 25 | o.SortedSet=function(){this.length=0,this.elements=[]},o.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},o.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(o===e)return i;e>o&&(t=i),o>e&&(n=i),r=n-t,i=t+Math.floor(r/2),o=this.elements[i]}return o===e?i:-1},o.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,r=n-t,i=t+Math.floor(r/2),o=this.elements[i];r>1;)e>o&&(t=i),o>e&&(n=i),r=n-t,i=t+Math.floor(r/2),o=this.elements[i];return o>e?i:e>o?i+1:void 0},o.SortedSet.prototype.intersect=function(e){for(var t=new o.SortedSet,n=0,r=0,i=this.length,s=e.length,a=this.elements,u=e.elements;;){if(n>i-1||r>s-1)break;a[n]!==u[r]?a[n]u[r]&&r++:(t.add(a[n]),n++,r++)}return t},o.SortedSet.prototype.clone=function(){var e=new o.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},o.SortedSet.prototype.union=function(e){var t,n,r;return this.length>=e.length?(t=this,n=e):(t=e,n=this),r=t.clone(),r.add.apply(r,n.toArray()),r},o.SortedSet.prototype.toJSON=function(){return this.toArray()},/*! 26 | * lunr.Index 27 | * Copyright (C) 2015 Oliver Nightingale 28 | */ 29 | o.Index=function(){this._fields=[],this._ref="id",this.pipeline=new o.Pipeline,this.documentStore=new o.Store,this.tokenStore=new o.TokenStore,this.corpusTokens=new o.SortedSet,this.eventEmitter=new o.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},o.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},o.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},o.Index.load=function(e){e.version!==o.version&&o.utils.warn("version mismatch: current "+o.version+" importing "+e.version);var t=new this;return t._fields=e.fields,t._ref=e.ref,t.documentStore=o.Store.load(e.documentStore),t.tokenStore=o.TokenStore.load(e.tokenStore),t.corpusTokens=o.SortedSet.load(e.corpusTokens),t.pipeline=o.Pipeline.load(e.pipeline),t},o.Index.prototype.field=function(e,t){var t=t||{},n={name:e,boost:t.boost||1};return this._fields.push(n),this},o.Index.prototype.ref=function(e){return this._ref=e,this},o.Index.prototype.add=function(e,t){var n={},r=new o.SortedSet,i=e[this._ref],t=void 0===t?!0:t;this._fields.forEach(function(t){var i=this.pipeline.run(o.tokenizer(e[t.name]));n[t.name]=i,o.SortedSet.prototype.add.apply(r,i)},this),this.documentStore.set(i,r),o.SortedSet.prototype.add.apply(this.corpusTokens,r.toArray());for(var s=0;s0&&(r=1+Math.log(this.documentStore.length/n)),this._idfCache[t]=r},o.Index.prototype.search=function(e){var t=this.pipeline.run(o.tokenizer(e)),n=new o.Vector,r=[],i=this._fields.reduce(function(e,t){return e+t.boost},0),s=t.some(function(e){return this.tokenStore.has(e)},this);if(!s)return[];t.forEach(function(e,t,s){var a=1/s.length*this._fields.length*i,u=this,c=this.tokenStore.expand(e).reduce(function(t,r){var i=u.corpusTokens.indexOf(r),s=u.idf(r),c=1,l=new o.SortedSet;if(r!==e){var d=Math.max(3,r.length-e.length);c=1/Math.log(d)}i>-1&&n.insert(i,a*s*c);for(var h=u.tokenStore.get(r),f=Object.keys(h),p=f.length,S=0;p>S;S++)l.add(h[f[S]].ref);return t.union(l)},new o.SortedSet);r.push(c)},this);var a=r.reduce(function(e,t){return e.intersect(t)});return a.map(function(e){return{ref:e,score:n.similarity(this.documentVector(e))}},this).sort(function(e,t){return t.score-e.score})},o.Index.prototype.documentVector=function(e){for(var t=this.documentStore.get(e),n=t.length,r=new o.Vector,i=0;n>i;i++){var s=t.elements[i],a=this.tokenStore.get(s)[e].tf,u=this.idf(s);r.insert(this.corpusTokens.indexOf(s),a*u)}return r},o.Index.prototype.toJSON=function(){return{version:o.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},o.Index.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},/*! 30 | * lunr.Store 31 | * Copyright (C) 2015 Oliver Nightingale 32 | */ 33 | o.Store=function(){this.store={},this.length=0},o.Store.load=function(e){var t=new this;return t.length=e.length,t.store=Object.keys(e.store).reduce(function(t,n){return t[n]=o.SortedSet.load(e.store[n]),t},{}),t},o.Store.prototype.set=function(e,t){this.has(e)||this.length++,this.store[e]=t},o.Store.prototype.get=function(e){return this.store[e]},o.Store.prototype.has=function(e){return e in this.store},o.Store.prototype.remove=function(e){this.has(e)&&(delete this.store[e],this.length--)},o.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},/*! 34 | * lunr.stemmer 35 | * Copyright (C) 2015 Oliver Nightingale 36 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 37 | */ 38 | o.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},t={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",r="[aeiouy]",i=n+"[^aeiouy]*",o=r+"[aeiou]*",s="^("+i+")?"+o+i,a="^("+i+")?"+o+i+"("+o+")?$",u="^("+i+")?"+o+i+o+i,c="^("+i+")?"+r,l=new RegExp(s),d=new RegExp(u),h=new RegExp(a),f=new RegExp(c),p=/^(.+?)(ss|i)es$/,S=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,g=/^(.+?)(ed|ing)$/,_=/.$/,y=/(at|bl|iz)$/,m=new RegExp("([^aeiouylsz])\\1$"),E=new RegExp("^"+i+r+"[^aeiouwxy]$"),x=/^(.+?[^aeiou])y$/,R=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,w=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,N=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,C=/^(.+?)(s|t)(ion)$/,A=/^(.+?)e$/,I=/ll$/,b=new RegExp("^"+i+r+"[^aeiouwxy]$"),L=function(n){var r,i,o,s,a,u,c;if(n.length<3)return n;if(o=n.substr(0,1),"y"==o&&(n=o.toUpperCase()+n.substr(1)),s=p,a=S,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=g,s.test(n)){var L=s.exec(n);s=l,s.test(L[1])&&(s=_,n=n.replace(s,""))}else if(a.test(n)){var L=a.exec(n);r=L[1],a=f,a.test(r)&&(n=r,a=y,u=m,c=E,a.test(n)?n+="e":u.test(n)?(s=_,n=n.replace(s,"")):c.test(n)&&(n+="e"))}if(s=x,s.test(n)){var L=s.exec(n);r=L[1],n=r+"i"}if(s=R,s.test(n)){var L=s.exec(n);r=L[1],i=L[2],s=l,s.test(r)&&(n=r+e[i])}if(s=w,s.test(n)){var L=s.exec(n);r=L[1],i=L[2],s=l,s.test(r)&&(n=r+t[i])}if(s=N,a=C,s.test(n)){var L=s.exec(n);r=L[1],s=d,s.test(r)&&(n=r)}else if(a.test(n)){var L=a.exec(n);r=L[1]+L[2],a=d,a.test(r)&&(n=r)}if(s=A,s.test(n)){var L=s.exec(n);r=L[1],s=d,a=h,u=b,(s.test(r)||a.test(r)&&!u.test(r))&&(n=r)}return s=I,a=d,s.test(n)&&a.test(n)&&(s=_,n=n.replace(s,"")),"y"==o&&(n=o.toLowerCase()+n.substr(1)),n};return L}(),o.Pipeline.registerFunction(o.stemmer,"stemmer"),/*! 39 | * lunr.stopWordFilter 40 | * Copyright (C) 2015 Oliver Nightingale 41 | */ 42 | o.generateStopWordFilter=function(e){var t=e.reduce(function(e,t){return e[t]=t,e},{});return function(e){return e&&t[e]!==e?e:void 0}},o.stopWordFilter=o.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),o.Pipeline.registerFunction(o.stopWordFilter,"stopWordFilter"),/*! 43 | * lunr.trimmer 44 | * Copyright (C) 2015 Oliver Nightingale 45 | */ 46 | o.trimmer=function(e){return e.replace(/^\W+/,"").replace(/\W+$/,"")},o.Pipeline.registerFunction(o.trimmer,"trimmer"),/*! 47 | * lunr.stemmer 48 | * Copyright (C) 2015 Oliver Nightingale 49 | * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt 50 | */ 51 | o.TokenStore=function(){this.root={docs:{}},this.length=0},o.TokenStore.load=function(e){var t=new this;return t.root=e.root,t.length=e.length,t},o.TokenStore.prototype.add=function(e,t,n){var n=n||this.root,r=e.charAt(0),i=e.slice(1);return r in n||(n[r]={docs:{}}),0===i.length?(n[r].docs[t.ref]=t,void(this.length+=1)):this.add(i,t,n[r])},o.TokenStore.prototype.has=function(e){if(!e)return!1;for(var t=this.root,n=0;n