Working implementation of text search
This commit is contained in:
parent
d5eab71f3a
commit
e2be492667
|
@ -18,6 +18,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^3.1.0",
|
"async": "^3.1.0",
|
||||||
|
"axios": "^0.19.1",
|
||||||
"chokidar": "^3.3.0",
|
"chokidar": "^3.3.0",
|
||||||
"express": "^4.13.4",
|
"express": "^4.13.4",
|
||||||
"express-static-gzip": "^2.0.5",
|
"express-static-gzip": "^2.0.5",
|
||||||
|
|
|
@ -10,8 +10,13 @@ export default class Blog extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
posts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
posts: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
searchString: PropTypes.string.isRequired,
|
searchString: PropTypes.string,
|
||||||
handleChange: PropTypes.func.isRequired
|
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 () {
|
render () {
|
||||||
|
@ -19,7 +24,16 @@ export default class Blog extends Component {
|
||||||
|
|
||||||
if (this.props.isLoading) {
|
if (this.props.isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className={contentStyle.contentWrapper} id="blog">
|
<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 />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -28,7 +42,7 @@ export default class Blog extends Component {
|
||||||
const posts = this.props.posts.sort((a, b) => {
|
const posts = this.props.posts.sort((a, b) => {
|
||||||
return new Date(b.published) - new Date(a.published)
|
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 key={post.title} className={styles.postListItem} role="listitem">
|
||||||
<div className={styles.postHeader} >
|
<div className={styles.postHeader} >
|
||||||
<a href={post.link} className={styles.postTitle}>{post.title}</a>
|
<a href={post.link} className={styles.postTitle}>{post.title}</a>
|
||||||
|
@ -39,16 +53,29 @@ export default class Blog extends Component {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (postsHTML.length < 1) {
|
||||||
|
postsHTML = (
|
||||||
|
<div>
|
||||||
|
<span>No posts found</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${contentStyle.content}`} id="blog" role="region" aria-label="Blog posts">
|
<div className={`${contentStyle.content}`} id="blog" role="region" aria-label="Blog posts">
|
||||||
<div className={styles.headerContainer}>
|
<div className={styles.headerContainer}>
|
||||||
<Header header={'Blog'} role="heading" aria-level="2"/>
|
<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>
|
||||||
<div className={`${styles.postsList}`} role="list">
|
<div className={`${styles.postsList}`} role="list">
|
||||||
{postsHTML}
|
{postsHTML}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,26 @@ import styles from './SearchBox.scss'
|
||||||
export default class SearchBox extends Component {
|
export default class SearchBox extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
handleChange: PropTypes.func.isRequired,
|
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 () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<input placeholder='Search' className={styles.search} type="text" value={this.props.searchString} onChange={this.props.handleChange}/>
|
<input placeholder='Search'
|
||||||
<span>
|
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>
|
<i className={`fa fa-search ${styles.icon}`}></i>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,8 +21,10 @@
|
||||||
transition: width 0.4s ease;
|
transition: width 0.4s ease;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus{ width: 300px; }
|
.expanded {
|
||||||
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon{
|
.icon{
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import { Blog } from '../components'
|
import { Blog } from '../components'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import axios from 'axios'
|
||||||
import '../stylesheets/globals.scss'
|
import '../stylesheets/globals.scss'
|
||||||
|
|
||||||
export default class BlogContainer extends Component {
|
export default class BlogContainer extends Component {
|
||||||
|
@ -13,7 +14,8 @@ export default class BlogContainer extends Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
posts: props.posts,
|
posts: props.posts,
|
||||||
searchString: ''
|
searchString: '',
|
||||||
|
expanded: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +23,53 @@ export default class BlogContainer extends Component {
|
||||||
this.setState({ searchString: event.target.value })
|
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 () {
|
render () {
|
||||||
return (
|
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)} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,7 @@
|
||||||
font-family: $font-header;
|
font-family: $font-header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@-ms-viewport{
|
||||||
|
width: device-width;
|
||||||
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ function renderFullPage (html, head, data, config) {
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>${config.title}</title>
|
<title>${config.title}</title>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
<!-- Google Fonts -->
|
<!-- Google Fonts -->
|
||||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&subset=latin-ext" rel="stylesheet" rel="preload">
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Open+Sans+Condensed:700&subset=latin-ext" rel="stylesheet" rel="preload">
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -1222,6 +1222,13 @@ aws4@^1.8.0:
|
||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
|
||||||
integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==
|
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:
|
babel-eslint@^10.0.1:
|
||||||
version "10.0.3"
|
version "10.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.3.tgz#81a2c669be0f205e19462fed2482d33e4687a88a"
|
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:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0, debug@=3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||||
|
@ -3228,6 +3235,13 @@ flush-write-stream@^1.0.0:
|
||||||
inherits "^2.0.3"
|
inherits "^2.0.3"
|
||||||
readable-stream "^2.3.6"
|
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:
|
for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||||
|
|
Loading…
Reference in New Issue