Suche auf Startseite (#13)

* Add search function

* Persist search input to URL query string

* Catch condition where URL searchParams is undefined
main
Marian Steinbach 4 years ago committed by GitHub
parent 5a8a9db884
commit 8d1d5ca87c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      package.json
  2. 45
      src/ResultsList.css
  3. 185
      src/ResultsList.js
  4. 2
      src/history.js
  5. 33
      src/index.js
  6. 5
      yarn.lock

@ -27,8 +27,10 @@
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.5",
"fs-extra": "3.0.1",
"history": "^4.7.2",
"html-webpack-plugin": "2.29.0",
"jest": "20.0.4",
"lunr": "^2.3.4",
"object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8",

@ -29,4 +29,49 @@ a.ResultsList:hover {
display: block;
font-size: 90%;
color: #999;
}
.searchInputRow {
background-color: #4cb4e7;
}
.searchInputRow label {
font-family: Arvo, sans-serif;
font-weight: 400;
color: #ffee00;
}
.searchInputRow label {
margin-top: 1rem;
}
.searchInputRow input {
width: 100%;
}
.searchInputRow small.form-text {
position: relative;
top: 6px;
}
input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: searchfield-cancel-button;
}
.row.placeholder {
color: #e6007e;
background-color: #d4edfc;
font-family: Arvo, sans-serif;
font-size: 2rem;
padding: 2rem 2rem;
}
.row.improve {
padding: 2rem 2rem;
font-family: Arvo, sans-serif;
font-size: 1.2rem;
}
.row.results {
margin-top: 1rem;
}

@ -8,7 +8,89 @@ import LocationLabel from './LocationLabel';
import ScoreField from './ScoreField';
import './ResultsList.css';
import punycode from 'punycode';
import history from './history';
class SearchField extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
lastQuery: '',
hits: 0,
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.doSearch = this.doSearch.bind(this);
}
componentDidMount() {
// init search from URL
let params = (new URL(document.location)).searchParams;
if (typeof params === 'object') {
let q = params.get('q');
if (q !== null && q !== '') {
this.doSearch(q);
}
}
}
doSearch(q) {
var minTermLength = 1;
if (q === '') {
history.push(`/`);
} else {
history.push(`/?q=${q}`);
}
this.setState({value: q});
if (q.length > minTermLength && q !== this.state.lastQuery) {
var searchResult = this.props.searchIndex.search(q + "*");
this.setState({
lastQuery: q,
hits: searchResult.length,
});
this.props.callback(searchResult);
} else if (q.length <= minTermLength) {
this.setState({
lastQuery: q,
hits: 0,
});
this.props.callback(null);
}
}
handleChange(event) {
var q = event.target.value;
this.doSearch(q);
}
handleSubmit(event) {
console.log('A name was submitted:', this.state.value);
event.preventDefault();
}
render() {
var hitsInfo = <span>&nbsp;</span>;
if (this.state.lastQuery !== '') {
hitsInfo = <span>{this.state.hits} Treffer</span>;
}
return (
<div className='col-12'>
<form onSubmit={this.handleSubmit}>
<div className='form-group'>
<label htmlFor='queryInput'>Finde Deine Site</label>
<input className='form-control' type='search' name='query' placeholder="Finde Deine Site" value={this.state.value} onChange={this.handleChange} id='queryInput' />
<small className='form-text'>{hitsInfo}</small>
</div>
</form>
</div>
);
}
}
class URLField extends Component {
displayURL(url) {
@ -27,39 +109,88 @@ class URLField extends Component {
class ResultsList extends Component {
render() {
// sort results by score (descending)
this.props.results.sort((a, b) => {
// if score is the same, use response time as tie breaker
if (a.score === b.score &&
typeof a.rating.HTTP_RESPONSE_DURATION.value === 'number' &&
typeof b.rating.HTTP_RESPONSE_DURATION.value === 'number') {
return a.rating.HTTP_RESPONSE_DURATION.value - b.rating.HTTP_RESPONSE_DURATION.value;
}
return b.score - a.score;
constructor(props) {
super(props);
var sitesHash = {};
for (var site of props.results) {
sitesHash[site.input_url] = site;
}
this.state = {
sitesHash: sitesHash,
searchResult: null,
};
this.searchResultCallback = this.searchResultCallback.bind(this);
}
searchResultCallback(result) {
this.setState({
searchResult: result,
});
}
render() {
var rows = [];
this.props.results.forEach((element, index) => {
var row = (
<Link key={element.input_url} to={`/sites/${ encodeURIComponent(element.input_url) }`} className='ResultsList'>
<div className='ResultsList row'>
<div className='col-9 col-sm-10 col-md-10'>
<LocationLabel level={element.meta.level} type={element.meta.type} district={element.meta.district} city={element.meta.city} state={element.meta.state} truncate={true} />
<URLField inputURL={element.input_url} canonicalURLs={element.resulting_urls} />
</div>
<div className='col-3 col-sm-2 col-md-2 d-flex'>
<ScoreField score={element.score} maxScore={13} />
if (this.state.searchResult) {
for (var site of this.state.searchResult) {
var element = this.state.sitesHash[site.ref];
var row = (
<Link key={element.input_url} to={`/sites/${ encodeURIComponent(element.input_url) }`} className='ResultsList'>
<div className='ResultsList row'>
<div className='col-9 col-sm-10 col-md-10'>
<LocationLabel level={element.meta.level} type={element.meta.type} district={element.meta.district} city={element.meta.city} state={element.meta.state} truncate={true} />
<URLField inputURL={element.input_url} canonicalURLs={element.resulting_urls} />
</div>
<div className='col-3 col-sm-2 col-md-2 d-flex'>
<ScoreField score={element.score} maxScore={13} />
</div>
</div>
</div>
</Link>
);
</Link>
);
rows.push(row);
});
rows.push(row);
}
}
return rows;
var placeholder = (
<div className='row placeholder' key='placeholder'>
<div className='col-12 text-center'>
Vergleiche Deine GRÜNE Website mit {this.props.results.length} anderen und erfahre, was Du verbessern kannst.
</div>
</div>
);
var improve = (
<div className='row improve' key='improve'>
<div className='col-12 text-center'>
GREEN SPIDER ist freie Software. <a href='https://github.com/netzbegruenung/green-spider/'>Hilf mit, sie zu verbessern!</a>
</div>
</div>
);
var noresult = [placeholder, improve];
var resultFound = (
<div className='row results'>
<div className='col-12'>
{rows}
</div>
</div>
);
return (
<div>
<div className='row searchInputRow'>
<div className='col-12'>
<SearchField searchIndex={this.props.searchIndex} callback={this.searchResultCallback} />
</div>
</div>
{ rows.length ? resultFound : noresult }
</div>
);
}
}

@ -0,0 +1,2 @@
import { createBrowserHistory } from 'history';
export default createBrowserHistory();

@ -1,27 +1,46 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from "react-router-dom";
import { Router, Route } from "react-router-dom";
import '../node_modules/bootstrap/dist/css/bootstrap.css';
import './index.css';
import results from './spider_result_compact.json';
import sitesData from './spider_result_compact.json';
import screenshots from './screenshots.json';
import StatusInfo from './StatusInfo';
import ResultsList from './ResultsList';
import SiteDetailsPage from './SiteDetailsPage';
import registerServiceWorker from './registerServiceWorker';
import lunr from 'lunr';
import history from './history'
let searchIndex = lunr(function() {
this.field('url');
this.field('state');
this.field('district');
this.field('city');
for (var site of sitesData) {
this.add({
"id": site.input_url,
"url": [site.input_url],
"state": site.meta.state,
"district": site.meta.district,
"city": site.meta.city,
});
}
});
const Home = () => (
<ResultsList results={results} />
<ResultsList results={sitesData} searchIndex={searchIndex} />
);
const SiteDetails = ({ match }) => (
<SiteDetailsPage sites={results} screenshots={screenshots} match={match} />
<SiteDetailsPage sites={sitesData} screenshots={screenshots} match={match} />
);
const AppMainContent = () => (
<Router>
<Router history={history}>
<div className='row'>
<div className='col-lg'></div>
<div className='col-lg'></div>
<div className='col-lg-8 col-sm-12'>
<Route exact path="/" component={Home} />
<Route path="/sites/:siteId" component={SiteDetails} />
@ -33,5 +52,5 @@ const AppMainContent = () => (
ReactDOM.render(<AppMainContent />, document.getElementById('root'));
ReactDOM.render(<StatusInfo results={results}/>, document.getElementById('status'));
ReactDOM.render(<StatusInfo results={sitesData}/>, document.getElementById('status'));
registerServiceWorker();

@ -4922,6 +4922,11 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lunr@^2.3.4:
version "2.3.4"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.4.tgz#ecc045a48a6ecd96f1bb812fff70b33731753412"
integrity sha512-o0D846XyAlPkBMVK3ZgVYrLHho3yhJHgpm0BxZT3dGdFa+tpQwdQdI4EUihsmWz8Fr3aaux4eahO9Ih7Z3e1eQ==
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"

Loading…
Cancel
Save