Move archive to Whoosh and add search

This commit is contained in:
2019-10-12 05:32:17 +00:00
parent 45b75b420b
commit 7cb87b59fe
14 changed files with 328 additions and 63 deletions

View File

@@ -1,11 +1,13 @@
import React from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom';
import './Style-light.css';
import './Style-dark.css';
import './fonts/Fonts.css';
import Feed from './Feed.js';
import Article from './Article.js';
import Comments from './Comments.js';
import Search from './Search.js';
import Results from './Results.js';
import ScrollToTop from './ScrollToTop.js';
class App extends React.Component {
@@ -41,10 +43,15 @@ class App extends React.Component {
<br />
<span className='slogan'>Reddit, Hacker News, and Tildes combined, then pre-rendered in reader mode.</span>
</p>
<Route path='/(|search)' component={Search} />
</div>
<Route path='/' exact component={Feed} />
<Switch>
<Route path='/search' component={Results} />
<Route path='/:id' exact component={Article} />
</Switch>
<Route path='/:id/c' exact component={Comments} />
<Route path='/:id' exact component={Article} />
<ScrollToTop />
</Router>

View File

@@ -15,20 +15,20 @@ class Article extends React.Component {
};
}
componentDidMount() {
componentDidMount() {
const id = this.props.match.params.id;
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
this.setState({ story: result.story });
localStorage.setItem(id, JSON.stringify(result.story));
},
(error) => {
this.setState({ error: true });
}
);
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
this.setState({ story: result.story });
localStorage.setItem(id, JSON.stringify(result.story));
},
(error) => {
this.setState({ error: true });
}
);
}
render() {

View File

@@ -17,25 +17,25 @@ class Article extends React.Component {
};
}
componentDidMount() {
componentDidMount() {
const id = this.props.match.params.id;
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
localStorage.setItem(id, JSON.stringify(result.story));
this.setState({ story: result.story }, () => {
fetch('/api/' + id)
.then(res => res.json())
.then(
(result) => {
localStorage.setItem(id, JSON.stringify(result.story));
this.setState({ story: result.story }, () => {
const hash = window.location.hash.substring(1);
if (hash) {
document.getElementById(hash).scrollIntoView();
}
});
},
(error) => {
this.setState({ error: true });
}
);
},
(error) => {
this.setState({ error: true });
}
);
}
displayComment(story, c, level) {

View File

@@ -14,14 +14,14 @@ class Feed extends React.Component {
};
}
componentDidMount() {
fetch('/api')
.then(res => res.json())
.then(
(result) => {
this.setState({ stories: result.stories });
componentDidMount() {
fetch('/api')
.then(res => res.json())
.then(
(result) => {
this.setState({ stories: result.stories });
clearStorage();
localStorage.setItem('stories', JSON.stringify(result.stories));
localStorage.setItem('stories', JSON.stringify(result.stories));
result.stories.filter(x => x.score >= 20).slice(0, 25).forEach(x => {
fetch('/api/' + x.id)
.then(res => res.json())
@@ -31,11 +31,11 @@ class Feed extends React.Component {
}, error => {}
);
});
},
(error) => {
this.setState({ error: true });
}
);
},
(error) => {
this.setState({ error: true });
}
);
}
render() {
@@ -46,13 +46,12 @@ class Feed extends React.Component {
<div className='container'>
<Helmet>
<title>Feed - QotNews</title>
<meta name="description" content="Reddit, Hacker News, and Tildes combined, then pre-rendered in reader mode" />
</Helmet>
{error && <p>Connection error?</p>}
{stories ?
<div>
{stories.map((x, i) =>
<div className='item'>
<div className='item' key={i}>
<div className='num'>
{i+1}.
</div>

83
webclient/src/Results.js Normal file
View File

@@ -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'>
&#8203;({sourceLink(x)})
</span>
</div>
{infoLine(x)}
</div>
)
:
<p>no results</p>
}
</div>
:
<p>loading...</p>
}
</div>
);
}
}
export default Results;

53
webclient/src/Search.js Normal file
View File

@@ -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);

View File

@@ -6,6 +6,11 @@
color: #ddd;
}
.dark input {
color: #ddd;
border: 1px solid #828282;
}
.dark .item {
color: #828282;
}

View File

@@ -11,6 +11,14 @@ a {
outline: none;
}
input {
font-size: 1.05rem;
background-color: transparent;
border: 1px solid #828282;
padding: 6px;
border-radius: 4px;
}
.container {
margin: 1rem auto;
max-width: 64rem;