Working implementation of text search

This commit is contained in:
LordMathis 2020-01-11 15:51:14 +01:00
parent d5eab71f3a
commit e2be492667
No known key found for this signature in database
GPG Key ID: 575849FD91CE470C
8 changed files with 131 additions and 24 deletions

View File

@ -18,6 +18,7 @@
"license": "MIT",
"dependencies": {
"async": "^3.1.0",
"axios": "^0.19.1",
"chokidar": "^3.3.0",
"express": "^4.13.4",
"express-static-gzip": "^2.0.5",

View File

@ -10,8 +10,13 @@ export default class Blog extends Component {
static propTypes = {
isLoading: PropTypes.bool.isRequired,
posts: PropTypes.arrayOf(PropTypes.object).isRequired,
searchString: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired
searchString: PropTypes.string,
expanded: PropTypes.bool.isRequired,
handleChange: PropTypes.func.isRequired,
handleFocus: PropTypes.func.isRequired,
handleBlur: PropTypes.func.isRequired,
handleEnter: PropTypes.func.isRequired,
handleSearch: PropTypes.func.isRequired
}
render () {
@ -19,8 +24,17 @@ export default class Blog extends Component {
if (this.props.isLoading) {
return (
<div className={contentStyle.contentWrapper} id="blog">
<Spinner/>
<div className={`${contentStyle.content}`} id="blog" role="region" aria-label="Blog posts">
<div className={styles.headerContainer}>
<Header header={'Blog'} role="heading" aria-level="2"/>
<SearchBox searchString={this.props.searchString}
expanded={this.props.expanded}
handleChange={this.props.handleChange}
handleFocus={this.props.handleFocus}
handleBlur={this.props.handleBlur}
handleSearch={this.props.handleSearch} />
</div>
<Spinner />
</div>
)
}
@ -28,7 +42,7 @@ export default class Blog extends Component {
const posts = this.props.posts.sort((a, b) => {
return new Date(b.published) - new Date(a.published)
})
const postsHTML = posts.map((post) =>
let postsHTML = posts.map((post) =>
<div key={post.title} className={styles.postListItem} role="listitem">
<div className={styles.postHeader} >
<a href={post.link} className={styles.postTitle}>{post.title}</a>
@ -39,16 +53,29 @@ export default class Blog extends Component {
</div>
)
if (postsHTML.length < 1) {
postsHTML = (
<div>
<span>No posts found</span>
</div>
)
}
return (
<div className={`${contentStyle.content}`} id="blog" role="region" aria-label="Blog posts">
<div className={styles.headerContainer}>
<Header header={'Blog'} role="heading" aria-level="2"/>
<SearchBox searchString={this.props.searchString} handleChange={this.props.handleChange} />
<SearchBox searchString={this.props.searchString}
expanded={this.props.expanded}
handleChange={this.props.handleChange}
handleFocus={this.props.handleFocus}
handleBlur={this.props.handleBlur}
handleEnter={this.props.handleEnter}
handleSearch={this.props.handleSearch} />
</div>
<div className={`${styles.postsList}`} role="list">
{postsHTML}
</div>
</div>
)
}

View File

@ -6,14 +6,26 @@ import styles from './SearchBox.scss'
export default class SearchBox extends Component {
static propTypes = {
handleChange: PropTypes.func.isRequired,
searchString: PropTypes.string
handleFocus: PropTypes.func.isRequired,
handleBlur: PropTypes.func.isRequired,
handleEnter: PropTypes.func.isRequired,
handleSearch: PropTypes.func.isRequired,
searchString: PropTypes.string,
expanded: PropTypes.bool.isRequired
}
render () {
return (
<div className={styles.container}>
<input placeholder='Search' className={styles.search} type="text" value={this.props.searchString} onChange={this.props.handleChange}/>
<span>
<input placeholder='Search'
className={`${styles.search} ${this.props.expanded ? styles.expanded : ''}`}
type="text"
value={this.props.searchString}
onChange={this.props.handleChange}
onFocus={this.props.handleFocus}
onBlur={this.props.handleBlur}
onKeyDown={this.props.handleEnter} />
<span onClick={this.props.handleSearch} >
<i className={`fa fa-search ${styles.icon}`}></i>
</span>
</div>

View File

@ -21,8 +21,10 @@
transition: width 0.4s ease;
outline: none;
box-sizing: border-box;
&:focus{ width: 300px; }
}
.expanded {
width: 300px;
}
.icon{

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react'
import { Blog } from '../components'
import PropTypes from 'prop-types'
import axios from 'axios'
import '../stylesheets/globals.scss'
export default class BlogContainer extends Component {
@ -13,7 +14,8 @@ export default class BlogContainer extends Component {
this.state = {
isLoading: false,
posts: props.posts,
searchString: ''
searchString: '',
expanded: false
}
}
@ -21,9 +23,53 @@ export default class BlogContainer extends Component {
this.setState({ searchString: event.target.value })
}
handleFocus () {
this.setState({ expanded: true })
}
handleBlur () {
this.setState({ expanded: false })
}
handleEnter (event) {
if (event.key === 'Enter') {
this.handleSearch()
}
}
handleSearch () {
if (this.state.expanded && this.state.searchString) {
this.search()
} else {
this.setState({ expanded: true })
}
}
search () {
this.setState({
isLoading: true
}, () => {
axios.get(`/api/v1/posts?search=${this.state.searchString}`)
.then((data) => {
this.setState({
isLoading: false,
posts: data.data
})
})
})
}
render () {
return (
<Blog isLoading={ this.state.isLoading } posts={ this.state.posts } searchString={this.state.searchString} handleChange={this.handleChange.bind(this)} />
<Blog isLoading={ this.state.isLoading }
posts={ this.state.posts }
searchString={this.state.searchString}
expanded={this.state.expanded}
handleChange={this.handleChange.bind(this)}
handleFocus={this.handleFocus.bind(this)}
handleBlur={this.handleBlur.bind(this)}
handleEnter={this.handleEnter.bind(this)}
handleSearch={this.handleSearch.bind(this)} />
)
}
}

View File

@ -16,3 +16,7 @@
font-family: $font-header;
}
}
@-ms-viewport{
width: device-width;
}

View File

@ -50,15 +50,16 @@ function renderFullPage (html, head, data, config) {
<html lang="en">
<head>
<title>${config.title}</title>
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&amp;subset=latin-ext" rel="stylesheet" rel="preload">
<!-- Font Awesome -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" rel="preload" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
<!-- Stylesheet -->
<link href=${manifest['bundle.css']} rel="stylesheet" rel="preload">
<!-- Initial Data -->
<script>window.__INITIAL_DATA__ = ${serialize(initialData)}</script>
${head.scripts.join('\n')}
<meta name="viewport" content="width=device-width">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&amp;subset=latin-ext" rel="stylesheet" rel="preload">
<!-- Font Awesome -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" rel="preload" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
<!-- Stylesheet -->
<link href=${manifest['bundle.css']} rel="stylesheet" rel="preload">
<!-- Initial Data -->
<script>window.__INITIAL_DATA__ = ${serialize(initialData)}</script>
${head.scripts.join('\n')}
</head>
<body>
<div id="root">${html}</div>

View File

@ -1222,6 +1222,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==
axios@^0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.1.tgz#8a6a04eed23dfe72747e1dd43c604b8f1677b5aa"
integrity sha512-Yl+7nfreYKaLRvAvjNPkvfjnQHJM1yLBY3zhqAwcJSwR/6ETkanUgylgtIvkvz0xJ+p/vZuNw8X7Hnb7Whsbpw==
dependencies:
follow-redirects "1.5.10"
babel-eslint@^10.0.1:
version "10.0.3"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
@ -2352,7 +2359,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@3.1.0:
debug@3.1.0, debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@ -3228,6 +3235,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"