parent
45b75b420b
commit
7cb87b59fe
14 changed files with 328 additions and 63 deletions
@ -0,0 +1,52 @@ |
||||
from whoosh.analysis import StemmingAnalyzer, CharsetFilter, NgramFilter |
||||
from whoosh.index import create_in, open_dir, exists_in |
||||
from whoosh.fields import * |
||||
from whoosh.qparser import QueryParser |
||||
from whoosh.support.charset import accent_map |
||||
|
||||
analyzer = StemmingAnalyzer() | CharsetFilter(accent_map) | NgramFilter(minsize=3) |
||||
|
||||
title_field = TEXT(analyzer=analyzer, stored=True) |
||||
id_field = ID(unique=True, stored=True) |
||||
|
||||
schema = Schema( |
||||
id=id_field, |
||||
title=title_field, |
||||
story=STORED, |
||||
) |
||||
|
||||
ARCHIVE_LOCATION = 'data/archive' |
||||
|
||||
ix = None |
||||
|
||||
def init(): |
||||
global ix |
||||
|
||||
if exists_in(ARCHIVE_LOCATION): |
||||
ix = open_dir(ARCHIVE_LOCATION) |
||||
else: |
||||
ix = create_in(ARCHIVE_LOCATION, schema) |
||||
|
||||
def update(story): |
||||
writer = ix.writer() |
||||
writer.update_document( |
||||
id=story['id'], |
||||
title=story['title'], |
||||
story=story, |
||||
) |
||||
writer.commit() |
||||
|
||||
def get_story(id): |
||||
with ix.searcher() as searcher: |
||||
result = searcher.document(id=id) |
||||
return result['story'] if result else None |
||||
|
||||
def search(search): |
||||
with ix.searcher() as searcher: |
||||
query = QueryParser('title', ix.schema).parse(search) |
||||
results = searcher.search(query) |
||||
stories = [r['story'] for r in results] |
||||
for s in stories: |
||||
s.pop('text', '') |
||||
s.pop('comments', '') |
||||
return stories |
@ -0,0 +1,26 @@ |
||||
import shelve |
||||
|
||||
import archive |
||||
|
||||
archive.init() |
||||
|
||||
#with shelve.open('data/data') as db: |
||||
# to_delete = [] |
||||
# |
||||
# for s in db.values(): |
||||
# if 'title' in s: |
||||
# archive.update(s) |
||||
# if 'id' in s: |
||||
# to_delete.append(s['id']) |
||||
# |
||||
# for id in to_delete: |
||||
# del db[id] |
||||
# |
||||
# for s in db['news_cache'].values(): |
||||
# if 'title' in s: |
||||
# archive.update(s) |
||||
|
||||
#with shelve.open('data/whoosh') as db: |
||||
# for s in db['news_cache'].values(): |
||||
# if 'title' in s and not archive.get_story(s['id']): |
||||
# archive.update(s) |
@ -0,0 +1,83 @@ |
||||
import React from 'react'; |
||||
import { Link } from 'react-router-dom'; |
||||
import { Helmet } from 'react-helmet'; |
||||
import { siteLogo, sourceLink, infoLine } from './utils.js'; |
||||
|
||||
class Results extends React.Component { |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
stories: false, |
||||
error: false, |
||||
}; |
||||
|
||||
} |
||||
|
||||
performSearch = () => { |
||||
const search = this.props.location.search; |
||||
fetch('/api/search' + search) |
||||
.then(res => res.json()) |
||||
.then( |
||||
(result) => { |
||||
this.setState({ stories: result.results }); |
||||
}, |
||||
(error) => { |
||||
this.setState({ error: true }); |
||||
} |
||||
); |
||||
} |
||||
|
||||
componentDidMount() { |
||||
this.performSearch(); |
||||
} |
||||
|
||||
componentDidUpdate(prevProps) { |
||||
if (this.props.location.search !== prevProps.location.search) { |
||||
this.performSearch(); |
||||
} |
||||
} |
||||
|
||||
render() { |
||||
const stories = this.state.stories; |
||||
const error = this.state.error; |
||||
|
||||
return ( |
||||
<div className='container'> |
||||
<Helmet> |
||||
<title>Feed - QotNews</title> |
||||
</Helmet> |
||||
{error && <p>Connection error?</p>} |
||||
{stories ? |
||||
<div> |
||||
{stories.length ? |
||||
stories.map((x, i) => |
||||
<div className='item' key={i}> |
||||
<div className='num'> |
||||
{i+1}. |
||||
</div> |
||||
|
||||
<div className='title'> |
||||
<Link className='link' to={'/' + x.id}>{siteLogo[x.source]} {x.title}</Link> |
||||
|
||||
<span className='source'> |
||||
​({sourceLink(x)}) |
||||
</span> |
||||
</div> |
||||
|
||||
{infoLine(x)} |
||||
</div> |
||||
) |
||||
: |
||||
<p>no results</p> |
||||
} |
||||
</div> |
||||
: |
||||
<p>loading...</p> |
||||
} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default Results; |
@ -0,0 +1,53 @@ |
||||
import React, { Component } from 'react'; |
||||
import { withRouter } from 'react-router-dom'; |
||||
import queryString from 'query-string'; |
||||
|
||||
const getSearch = props => queryString.parse(props.location.search).q; |
||||
|
||||
class Search extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = {search: getSearch(this.props)}; |
||||
this.inputRef = React.createRef(); |
||||
} |
||||
|
||||
searchArticles = (event) => { |
||||
const search = event.target.value; |
||||
this.setState({search: search}); |
||||
if (search.length >= 3) { |
||||
const searchQuery = queryString.stringify({ 'q': search }); |
||||
this.props.history.replace('/search?' + searchQuery); |
||||
} else { |
||||
this.props.history.replace('/'); |
||||
} |
||||
} |
||||
|
||||
searchAgain = (event) => { |
||||
event.preventDefault(); |
||||
const searchString = queryString.stringify({ 'q': event.target[0].value }); |
||||
this.props.history.push('/search?' + searchString); |
||||
this.inputRef.current.blur(); |
||||
} |
||||
|
||||
render() { |
||||
const search = this.state.search; |
||||
|
||||
return ( |
||||
<div className='search'> |
||||
<div className='search-inside'> |
||||
<form onSubmit={this.searchAgain}> |
||||
<input |
||||
placeholder='Search...' |
||||
value={search} |
||||
onChange={this.searchArticles} |
||||
ref={this.inputRef} |
||||
/> |
||||
</form> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default withRouter(Search); |
Loading…
Reference in new issue