Working implementation of text search
This commit is contained in:
parent
d5eab71f3a
commit
e2be492667
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
transition: width 0.4s ease;
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus{ width: 300px; }
|
||||
}
|
||||
|
||||
.expanded {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.icon{
|
||||
|
|
|
@ -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)} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,3 +16,7 @@
|
|||
font-family: $font-header;
|
||||
}
|
||||
}
|
||||
|
||||
@-ms-viewport{
|
||||
width: device-width;
|
||||
}
|
||||
|
|
|
@ -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&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&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>
|
||||
|
|
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"
|
||||
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"
|
||||
|
|
Loading…
Reference in New Issue