diff --git a/ghost/sodo-search/src/App.js b/ghost/sodo-search/src/App.js index 434107b298..41e946df22 100644 --- a/ghost/sodo-search/src/App.js +++ b/ghost/sodo-search/src/App.js @@ -2,23 +2,24 @@ import React from 'react'; import './App.css'; import AppContext from './AppContext'; import PopupModal from './components/PopupModal'; -import {init as initSearchIndex} from './search-index.js'; +import SearchIndex from './search-index.js'; export default class App extends React.Component { constructor(props) { super(props); - this.state = { + const searchIndex = new SearchIndex({ apiUrl: props.apiUrl, apiKey: props.apiKey + }); + + this.state = { + searchIndex }; } componentDidMount() { - initSearchIndex({ - apiUrl: this.state.apiUrl, - apiKey: this.state.apiKey - }); + this.state.searchIndex.init(); } render() { diff --git a/ghost/sodo-search/src/search-index.js b/ghost/sodo-search/src/search-index.js index 757671cb00..8222cfb0a5 100644 --- a/ghost/sodo-search/src/search-index.js +++ b/ghost/sodo-search/src/search-index.js @@ -1,57 +1,68 @@ import elasticlunr from 'elasticlunr'; -let index; +export default class SearchIndex { + constructor({apiUrl, apiKey, storage = localStorage}) { + this.apiUrl = apiUrl; + this.apiKey = apiKey; + this.storage = storage; -export const init = async function ({apiUrl, apiKey}) { - // remove default stop words to search of *any* word - elasticlunr.clearStopWords(); + this.index = null; - const url = `${apiUrl}/posts/?key=${apiKey}&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext`; + this.init = this.init.bind(this); + this.search = this.search.bind(this); + } - const indexDump = JSON.parse(localStorage.getItem('ease_search_index')); - - localStorage.removeItem('ease_index'); - localStorage.removeItem('ease_last'); - - function update(data) { - data.posts.forEach(function (post) { - index.addDoc(post); + #updateIndex(data) { + data.posts.forEach((post) => { + this.index.addDoc(post); }); - localStorage.setItem('ease_search_index', JSON.stringify(index)); - localStorage.setItem('ease_search_last', data.posts[0].updated_at); + this.storage.setItem('ease_search_index', JSON.stringify(this.index)); + this.storage.setItem('ease_search_last', data.posts[0].updated_at); } - if ( - !indexDump - ) { - return fetch(url) - .then(response => response.json()) - .then((data) => { - if (data.posts.length > 0) { - index = elasticlunr(function () { - this.addField('title'); - this.addField('plaintext'); - this.setRef('id'); - }); + async init() { + // remove default stop words to search of *any* word + elasticlunr.clearStopWords(); - update(data); - } - }); - } else { - index = elasticlunr.Index.load(indexDump); + const url = `${this.apiUrl}/posts/?key=${this.apiKey}&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext`; - return fetch(`${url}&filter=updated_at:>'${localStorage.getItem('ease_search_last').replace(/\..*/, '').replace(/T/, ' ')}'` - ) - .then(response => response.json()) - .then((data) => { - if (data.posts.length > 0) { - update(data); - } - }); + const indexDump = JSON.parse(this.storage.getItem('ease_search_index')); + + this.storage.removeItem('ease_index'); + this.storage.removeItem('ease_last'); + + if ( + !indexDump + ) { + return fetch(url) + .then(response => response.json()) + .then((data) => { + if (data.posts.length > 0) { + this.index = elasticlunr(function () { + this.addField('title'); + this.addField('plaintext'); + this.setRef('id'); + }); + + this.#updateIndex(data); + } + }); + } else { + this.index = elasticlunr.Index.load(indexDump); + + return fetch(`${url}&filter=updated_at:>'${this.storage.getItem('ease_search_last').replace(/\..*/, '').replace(/T/, ' ')}'` + ) + .then(response => response.json()) + .then((data) => { + if (data.posts.length > 0) { + this.#updateIndex(data); + } + }); + } } -}; -export const search = function (value) { - return index.search(value, {expand: true}); -}; + search(value) { + return this.index.search(value, {expand: true}); + } +} diff --git a/ghost/sodo-search/src/search-index.test.js b/ghost/sodo-search/src/search-index.test.js index d7af0046d1..8abdd2e3be 100644 --- a/ghost/sodo-search/src/search-index.test.js +++ b/ghost/sodo-search/src/search-index.test.js @@ -1,10 +1,15 @@ -import {init} from './search-index'; +import SearchIndex from './search-index'; import nock from 'nock'; describe('search index', function () { + afterEach(function () { + localStorage.clear(); + }); + test('initializes search index', async () => { const apiUrl = 'http://localhost/ghost/api/content'; const apiKey = 'secret_key'; + const searchIndex = new SearchIndex({apiUrl, apiKey, storage: localStorage}); const scope = nock('http://localhost/ghost/api/content') .get('/posts/?key=secret_key&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext') @@ -12,8 +17,30 @@ describe('search index', function () { posts: [{}] }); - await init({apiUrl, apiKey}); + await searchIndex.init({apiUrl, apiKey}); expect(scope.isDone()).toBeTruthy(); }); + + test('allows to search for indexed posts', async () => { + const apiUrl = 'http://localhost/ghost/api/content'; + const apiKey = 'secret_key'; + const searchIndex = new SearchIndex({apiUrl, apiKey, storage: localStorage}); + + nock('http://localhost/ghost/api/content') + .get('/posts/?key=secret_key&limit=all&fields=id,title,excerpt,url,updated_at,visibility&order=updated_at%20desc&formats=plaintext') + .reply(200, { + posts: [{ + id: 'sounique', + title: 'Amazing Barcelona Life', + plaintext: 'We are sitting by the pool and smashing out search features' + }] + }); + + await searchIndex.init({apiUrl, apiKey}); + + const searchResults = searchIndex.search('Barcelona'); + + expect(searchResults.length).toEqual(1); + }); });