feat(client): ts-migrate for files under client/src/component/search folder (#42327)
* typescript migration for files under search folder * fixing few more eslint errors and warnings * reverting changes for redux/index.js * fixing merge conflicts with next * deleting redux/index.ts
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
1e86063f04
commit
5ad374cc1a
21641
client/package-lock.json
generated
21641
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,163 +0,0 @@
|
||||
{
|
||||
"name": "@freecodecamp/client",
|
||||
"version": "0.0.1",
|
||||
"description": "The freeCodeCamp.org open-source codebase and curriculum",
|
||||
"license": "BSD-3-Clause",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">= 14.0.0",
|
||||
"npm": "^6.14.12"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/freeCodeCamp/freeCodeCamp/issues"
|
||||
},
|
||||
"homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme",
|
||||
"author": "freeCodeCamp <team@freecodecamp.org>",
|
||||
"main": "none",
|
||||
"scripts": {
|
||||
"prebuild": "node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env production",
|
||||
"build": "node --max_old_space_size=7168 node_modules/gatsby-cli build --prefix-paths",
|
||||
"build:workers": "node --max_old_space_size=7168 node_modules/webpack-cli/bin/cli --config ./webpack-workers.js",
|
||||
"clean": "gatsby clean",
|
||||
"predevelop": "node ../tools/scripts/build/ensure-env.js && npm run build:workers -- --env development",
|
||||
"develop": "node --max_old_space_size=4000 node_modules/gatsby-cli develop --inspect=9230",
|
||||
"format": "npm run format:gatsby && npm run format:src && npm run format:utils",
|
||||
"format:gatsby": "prettier-eslint --write --trailing-comma none --single-quote './gatsby-*.js'",
|
||||
"format:src": "prettier-eslint --write --trailing-comma none --single-quote './src/**/*.js'",
|
||||
"format:utils": "prettier-eslint --write --trailing-comma none --single-quote './utils/**/*.js'",
|
||||
"lint": "node ./i18n/schema-validation.js",
|
||||
"serve": "gatsby serve -p 8000",
|
||||
"prestand-alone": "npm run prebuild",
|
||||
"stand-alone": "gatsby develop",
|
||||
"validate-keys": "node ./i18n/validate-keys.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-export-default-from": "7.14.5",
|
||||
"@babel/plugin-proposal-function-bind": "7.14.5",
|
||||
"@babel/polyfill": "7.12.1",
|
||||
"@babel/preset-env": "7.14.7",
|
||||
"@babel/preset-react": "7.14.5",
|
||||
"@babel/standalone": "7.14.7",
|
||||
"@fortawesome/fontawesome": "1.1.8",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
||||
"@fortawesome/free-brands-svg-icons": "5.15.3",
|
||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
||||
"@fortawesome/react-fontawesome": "0.1.14",
|
||||
"@freecodecamp/loop-protect": "2.2.1",
|
||||
"@freecodecamp/react-bootstrap": "0.32.3",
|
||||
"@freecodecamp/react-calendar-heatmap": "1.0.0",
|
||||
"@freecodecamp/strip-comments": "3.0.0",
|
||||
"@loadable/component": "5.15.0",
|
||||
"@reach/router": "1.3.4",
|
||||
"algoliasearch": "4.10.2",
|
||||
"assert": "2.0.0",
|
||||
"babel-plugin-preval": "5.0.0",
|
||||
"babel-plugin-prismjs": "2.0.1",
|
||||
"bezier-easing": "2.1.0",
|
||||
"browser-cookies": "1.2.0",
|
||||
"buffer": "6.0.3",
|
||||
"chai": "4.3.4",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"csrf": "3.1.0",
|
||||
"date-fns": "2.22.1",
|
||||
"dedent": "0.7.0",
|
||||
"enzyme": "3.11.0",
|
||||
"enzyme-adapter-react-16": "1.15.6",
|
||||
"final-form": "4.20.2",
|
||||
"gatsby": "3.8.1",
|
||||
"gatsby-cli": "3.8.0",
|
||||
"gatsby-plugin-advanced-sitemap": "1.6.0",
|
||||
"gatsby-plugin-create-client-paths": "3.8.0",
|
||||
"gatsby-plugin-manifest": "3.8.0",
|
||||
"gatsby-plugin-postcss": "4.8.0",
|
||||
"gatsby-plugin-react-helmet": "4.8.0",
|
||||
"gatsby-plugin-remove-serviceworker": "1.0.0",
|
||||
"gatsby-remark-prismjs": "5.5.0",
|
||||
"gatsby-source-filesystem": "3.8.0",
|
||||
"gatsby-transformer-remark": "4.5.0",
|
||||
"i18next": "20.3.2",
|
||||
"jquery": "3.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"lodash-es": "4.17.21",
|
||||
"monaco-editor": "0.25.2",
|
||||
"nanoid": "3.1.23",
|
||||
"normalize-url": "4.5.1",
|
||||
"path-browserify": "1.0.1",
|
||||
"postcss": "8.3.5",
|
||||
"prismjs": "1.24.0",
|
||||
"process": "0.11.10",
|
||||
"prop-types": "15.7.2",
|
||||
"psl": "1.8.0",
|
||||
"query-string": "7.0.1",
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"react-final-form": "6.5.3",
|
||||
"react-ga": "3.3.0",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-hotkeys": "2.0.0",
|
||||
"react-i18next": "11.11.0",
|
||||
"react-instantsearch-dom": "6.11.2",
|
||||
"react-lazy-load": "3.1.13",
|
||||
"react-monaco-editor": "0.43.0",
|
||||
"react-redux": "5.1.2",
|
||||
"react-reflex": "4.0.1",
|
||||
"react-responsive": "6.1.2",
|
||||
"react-scrollable-anchor": "0.6.1",
|
||||
"react-spinkit": "3.0.0",
|
||||
"react-tooltip": "4.2.21",
|
||||
"react-transition-group": "4.4.2",
|
||||
"react-youtube": "7.13.1",
|
||||
"redux": "4.1.0",
|
||||
"redux-actions": "2.6.5",
|
||||
"redux-devtools-extension": "2.13.9",
|
||||
"redux-observable": "1.2.0",
|
||||
"redux-saga": "1.1.3",
|
||||
"reselect": "4.0.0",
|
||||
"rxjs": "6.6.7",
|
||||
"sanitize-html": "2.4.0",
|
||||
"sass.js": "0.11.1",
|
||||
"store": "2.0.12",
|
||||
"stream-browserify": "3.0.0",
|
||||
"typescript": "4.3.4",
|
||||
"uuid": "8.3.2",
|
||||
"validator": "13.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "7.14.5",
|
||||
"@codesee/babel-plugin-instrument": "0.43.1",
|
||||
"@codesee/tracker": "0.43.1",
|
||||
"@testing-library/jest-dom": "5.14.1",
|
||||
"@testing-library/react": "12.0.0",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/loadable__component": "^5.13.3",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/react-dom": "^17.0.8",
|
||||
"@types/react-helmet": "^6.1.1",
|
||||
"@types/react-instantsearch-dom": "^6.10.0",
|
||||
"@types/react-monaco-editor": "^0.16.0",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"@types/react-responsive": "^8.0.2",
|
||||
"@types/react-spinkit": "^3.0.6",
|
||||
"@types/react-test-renderer": "^17.0.1",
|
||||
"@types/react-transition-group": "4.4.1",
|
||||
"@types/redux-actions": "2.6.1",
|
||||
"@types/sanitize-html": "^2.3.1",
|
||||
"@types/store": "^2.0.2",
|
||||
"@types/validator": "^13.1.4",
|
||||
"autoprefixer": "10.2.6",
|
||||
"babel-plugin-transform-imports": "2.0.0",
|
||||
"chokidar": "3.5.2",
|
||||
"copy-webpack-plugin": "9.0.1",
|
||||
"gatsby-plugin-webpack-bundle-analyser-v2": "1.1.24",
|
||||
"jest-json-schema-extended": "1.0.0",
|
||||
"monaco-editor-webpack-plugin": "4.0.0",
|
||||
"react-test-renderer": "16.14.0",
|
||||
"redux-saga-test-plan": "4.0.1",
|
||||
"webpack": "5.41.1",
|
||||
"webpack-cli": "4.7.2"
|
||||
}
|
||||
}
|
@ -8,14 +8,13 @@ import { Link, SkeletonSprite } from '../../helpers';
|
||||
import NavLogo from './nav-logo';
|
||||
import MenuButton from './menu-button';
|
||||
import NavLinks from './nav-links';
|
||||
import './universalNav.css';
|
||||
import { isLanding } from '../../../utils/path-parsers';
|
||||
import Loadable from '@loadable/component';
|
||||
|
||||
import './universal-nav.css';
|
||||
|
||||
const SearchBar = Loadable(() => import('../../search/searchBar/SearchBar'));
|
||||
const SearchBarOptimized = Loadable(
|
||||
() => import('../../search/searchBar/search-bar-optimized')
|
||||
const SearchBar = Loadable(() => import('../../search/searchBar/search-bar'));
|
||||
const SearchBarOptimized = Loadable(() =>
|
||||
import('../../search/searchBar/search-bar-optimized')
|
||||
);
|
||||
|
||||
export interface UniversalNavProps {
|
||||
|
@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const NoHitsSuggestion = ({ title }) => {
|
||||
return (
|
||||
<div className={'no-hits-footer fcc_suggestion_item'} role='region'>
|
||||
<span className='hit-name'>{title}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
NoHitsSuggestion.propTypes = {
|
||||
handleMouseEnter: PropTypes.func.isRequired,
|
||||
handleMouseLeave: PropTypes.func.isRequired,
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
export default NoHitsSuggestion;
|
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import ShallowRenderer from 'react-test-renderer/shallow';
|
||||
|
||||
import { SearchBar } from './SearchBar';
|
||||
import { SearchBar } from './search-bar';
|
||||
|
||||
describe('<SearchBar />', () => {
|
||||
it('renders to the DOM', () => {
|
||||
const shallow = new ShallowRenderer();
|
||||
const shallow = ShallowRenderer.createRenderer();
|
||||
shallow.render(<SearchBar {...searchBarProps} />);
|
||||
const result = shallow.getRenderOutput();
|
||||
expect(result).toBeTruthy();
|
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
interface noHitsSuggestionPropType {
|
||||
handleMouseEnter: (e: React.ChangeEvent<HTMLElement>) => void;
|
||||
handleMouseLeave: (e: React.ChangeEvent<HTMLElement>) => void;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const NoHitsSuggestion = ({ title }: noHitsSuggestionPropType) => {
|
||||
return (
|
||||
<div className={'no-hits-footer fcc_suggestion_item'} role='region'>
|
||||
<span className='hit-name'>{title}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoHitsSuggestion;
|
@ -1,7 +1,6 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { AnyAction, bindActionCreators, Dispatch } from 'redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { SearchBox } from 'react-instantsearch-dom';
|
||||
import { HotKeys, ObserveKeys } from 'react-hotkeys';
|
||||
@ -11,6 +10,7 @@ import { searchPageUrl } from '../../../utils/algolia-locale-setup';
|
||||
|
||||
import WithInstantSearch from '../WithInstantSearch';
|
||||
|
||||
import { Hit } from 'react-instantsearch-core';
|
||||
import {
|
||||
isSearchDropdownEnabledSelector,
|
||||
isSearchBarFocusedSelector,
|
||||
@ -18,21 +18,12 @@ import {
|
||||
toggleSearchFocused,
|
||||
updateSearchQuery
|
||||
} from '../redux';
|
||||
import SearchHits from './SearchHits';
|
||||
import SearchHits from './search-hits';
|
||||
|
||||
import './searchbar-base.css';
|
||||
import './searchbar.css';
|
||||
|
||||
const propTypes = {
|
||||
innerRef: PropTypes.object,
|
||||
isDropdownEnabled: PropTypes.bool,
|
||||
isSearchFocused: PropTypes.bool,
|
||||
t: PropTypes.func.isRequired,
|
||||
toggleSearchDropdown: PropTypes.func.isRequired,
|
||||
toggleSearchFocused: PropTypes.func.isRequired,
|
||||
updateSearchQuery: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
const searchUrl: string = searchPageUrl as string;
|
||||
const mapStateToProps = createSelector(
|
||||
isSearchDropdownEnabledSelector,
|
||||
isSearchBarFocusedSelector,
|
||||
@ -42,14 +33,29 @@ const mapStateToProps = createSelector(
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
const mapDispatchToProps = (dispatch: Dispatch<AnyAction>) =>
|
||||
bindActionCreators(
|
||||
{ toggleSearchDropdown, toggleSearchFocused, updateSearchQuery },
|
||||
dispatch
|
||||
);
|
||||
|
||||
export class SearchBar extends Component {
|
||||
constructor(props) {
|
||||
type searchBarPropType = {
|
||||
innerRef?: React.RefObject<HTMLDivElement>;
|
||||
toggleSearchDropdown: typeof toggleSearchDropdown;
|
||||
toggleSearchFocused: typeof toggleSearchFocused;
|
||||
updateSearchQuery: typeof updateSearchQuery;
|
||||
isDropdownEnabled?: boolean;
|
||||
isSearchFocused?: boolean;
|
||||
t?: (label: string) => string;
|
||||
};
|
||||
type classState = {
|
||||
index: number;
|
||||
hits: Array<Hit>;
|
||||
};
|
||||
|
||||
export class SearchBar extends Component<searchBarPropType, classState> {
|
||||
static displayName: string;
|
||||
constructor(props: searchBarPropType) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
@ -64,15 +70,15 @@ export class SearchBar extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
componentDidMount(): void {
|
||||
document.addEventListener('click', this.handleFocus);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
componentWillUnmount(): void {
|
||||
document.removeEventListener('click', this.handleFocus);
|
||||
}
|
||||
|
||||
handleChange() {
|
||||
handleChange = (): void => {
|
||||
const { isSearchFocused, toggleSearchFocused } = this.props;
|
||||
if (!isSearchFocused) {
|
||||
toggleSearchFocused(true);
|
||||
@ -81,20 +87,25 @@ export class SearchBar extends Component {
|
||||
this.setState({
|
||||
index: -1
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus(e) {
|
||||
handleFocus = (e: React.FocusEvent<Node> | Event): AnyAction | void => {
|
||||
const { toggleSearchFocused } = this.props;
|
||||
const isSearchFocused = this.props.innerRef.current.contains(e.target);
|
||||
const isSearchFocused = this.props.innerRef?.current?.contains(
|
||||
e.target as HTMLElement
|
||||
);
|
||||
if (!isSearchFocused) {
|
||||
// Reset if user clicks outside of
|
||||
// search bar / closes dropdown
|
||||
this.setState({ index: -1 });
|
||||
}
|
||||
return toggleSearchFocused(isSearchFocused);
|
||||
}
|
||||
};
|
||||
|
||||
handleSearch(e, query) {
|
||||
handleSearch = (
|
||||
e: React.SyntheticEvent<HTMLFormElement, Event>,
|
||||
query?: string
|
||||
): boolean | void => {
|
||||
e.preventDefault();
|
||||
const { toggleSearchDropdown, updateSearchQuery } = this.props;
|
||||
const { index, hits } = this.state;
|
||||
@ -107,7 +118,7 @@ export class SearchBar extends Component {
|
||||
return window.location.assign(selectedHit.url);
|
||||
} else if (!query) {
|
||||
// Set query to value in search bar if enter is pressed
|
||||
query = e.currentTarget.children[0].value;
|
||||
query = (e.currentTarget?.children?.[0] as HTMLInputElement).value;
|
||||
}
|
||||
updateSearchQuery(query);
|
||||
|
||||
@ -119,12 +130,12 @@ export class SearchBar extends Component {
|
||||
// are hits besides the footer
|
||||
return query && hits.length > 1
|
||||
? window.location.assign(
|
||||
`${searchPageUrl}?query=${encodeURIComponent(query)}`
|
||||
`${searchUrl}?query=${encodeURIComponent(query)}`
|
||||
)
|
||||
: false;
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseEnter(e) {
|
||||
handleMouseEnter = (e: React.SyntheticEvent<HTMLElement, Event>): void => {
|
||||
e.persist();
|
||||
const hoveredText = e.currentTarget.innerText;
|
||||
|
||||
@ -134,15 +145,15 @@ export class SearchBar extends Component {
|
||||
|
||||
return { index: hoveredIndex };
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseLeave() {
|
||||
handleMouseLeave = (): void => {
|
||||
this.setState({
|
||||
index: -1
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleHits(currHits) {
|
||||
handleHits = (currHits: Array<Hit>): void => {
|
||||
const { hits } = this.state;
|
||||
|
||||
if (!isEqual(hits, currHits)) {
|
||||
@ -151,7 +162,7 @@ export class SearchBar extends Component {
|
||||
hits: currHits
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
keyMap = {
|
||||
INDEX_UP: ['up'],
|
||||
@ -159,14 +170,14 @@ export class SearchBar extends Component {
|
||||
};
|
||||
|
||||
keyHandlers = {
|
||||
INDEX_UP: e => {
|
||||
e.preventDefault();
|
||||
INDEX_UP: (e: KeyboardEvent | undefined): void => {
|
||||
e?.preventDefault();
|
||||
this.setState(({ index, hits }) => ({
|
||||
index: index === -1 ? hits.length - 1 : index - 1
|
||||
}));
|
||||
},
|
||||
INDEX_DOWN: e => {
|
||||
e.preventDefault();
|
||||
INDEX_DOWN: (e: KeyboardEvent | undefined): void => {
|
||||
e?.preventDefault();
|
||||
this.setState(({ index, hits }) => ({
|
||||
index: index === hits.length - 1 ? -1 : index + 1
|
||||
}));
|
||||
@ -176,7 +187,7 @@ export class SearchBar extends Component {
|
||||
render() {
|
||||
const { isDropdownEnabled, isSearchFocused, innerRef, t } = this.props;
|
||||
const { index } = this.state;
|
||||
const placeholder = t('search.placeholder');
|
||||
const placeholder = t ? t('search.placeholder') : '';
|
||||
|
||||
return (
|
||||
<WithInstantSearch>
|
||||
@ -188,17 +199,20 @@ export class SearchBar extends Component {
|
||||
<HotKeys handlers={this.keyHandlers} keyMap={this.keyMap}>
|
||||
<div className='fcc_search_wrapper'>
|
||||
<label className='fcc_sr_only' htmlFor='fcc_instantsearch'>
|
||||
{t('search.label')}
|
||||
{t ? t('search.label') : ''}
|
||||
</label>
|
||||
<ObserveKeys except={['Space']}>
|
||||
<SearchBox
|
||||
focusShortcuts={[83, 191]}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onSubmit={this.handleSearch}
|
||||
showLoadingIndicator={false}
|
||||
translations={{ placeholder }}
|
||||
/>
|
||||
<div onFocus={this.handleFocus} role='textbox'>
|
||||
<SearchBox
|
||||
focusShortcuts={['83', '191']}
|
||||
onChange={this.handleChange}
|
||||
onSubmit={e => {
|
||||
this.handleSearch(e);
|
||||
}}
|
||||
showLoadingIndicator={false}
|
||||
translations={{ placeholder }}
|
||||
/>
|
||||
</div>
|
||||
</ObserveKeys>
|
||||
{isDropdownEnabled && isSearchFocused && (
|
||||
<SearchHits
|
||||
@ -217,8 +231,6 @@ export class SearchBar extends Component {
|
||||
}
|
||||
|
||||
SearchBar.displayName = 'SearchBar';
|
||||
SearchBar.propTypes = propTypes;
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
@ -1,13 +1,28 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connectStateResults, connectHits } from 'react-instantsearch-dom';
|
||||
import { SearchState, Hit } from 'react-instantsearch-core';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { searchPageUrl } from '../../../utils/algolia-locale-setup';
|
||||
import Suggestion from './search-suggestion';
|
||||
import NoHitsSuggestion from './no-hits-suggestion';
|
||||
|
||||
import Suggestion from './SearchSuggestion';
|
||||
import NoHitsSuggestion from './NoHitsSuggestion';
|
||||
|
||||
const searchUrl = searchPageUrl as string;
|
||||
interface customHitsPropTypes {
|
||||
hits: Array<any>;
|
||||
searchQuery: string;
|
||||
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
selectedIndex: number;
|
||||
handleHits: (currHits: Array<Hit>) => void;
|
||||
}
|
||||
interface searchHitsPropTypes {
|
||||
searchState: SearchState;
|
||||
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
selectedIndex: number;
|
||||
handleHits: (currHits: Array<Hit>) => void;
|
||||
}
|
||||
const CustomHits = connectHits(
|
||||
({
|
||||
hits,
|
||||
@ -16,7 +31,7 @@ const CustomHits = connectHits(
|
||||
handleMouseLeave,
|
||||
selectedIndex,
|
||||
handleHits
|
||||
}) => {
|
||||
}: customHitsPropTypes) => {
|
||||
const { t } = useTranslation();
|
||||
const noHits = isEmpty(hits);
|
||||
const noHitsTitle = t('search.no-tutorials');
|
||||
@ -26,7 +41,7 @@ const CustomHits = connectHits(
|
||||
query: searchQuery,
|
||||
url: noHits
|
||||
? null
|
||||
: `${searchPageUrl}?query=${encodeURIComponent(searchQuery)}`,
|
||||
: `${searchUrl}?query=${encodeURIComponent(searchQuery)}`,
|
||||
title: t('search.see-results', { searchQuery: searchQuery }),
|
||||
_highlightResult: {
|
||||
query: {
|
||||
@ -47,7 +62,7 @@ const CustomHits = connectHits(
|
||||
return (
|
||||
<div className='ais-Hits'>
|
||||
<ul className='ais-Hits-list' data-cy='ais-Hits-list'>
|
||||
{allHits.map((hit, i) => (
|
||||
{allHits.map((hit: Hit, i: number) => (
|
||||
<li
|
||||
className={
|
||||
!noHits && i === selectedIndex
|
||||
@ -85,7 +100,7 @@ const SearchHits = connectStateResults(
|
||||
handleMouseLeave,
|
||||
selectedIndex,
|
||||
handleHits
|
||||
}) => {
|
||||
}: searchHitsPropTypes) => {
|
||||
return isEmpty(searchState) || !searchState.query ? null : (
|
||||
<CustomHits
|
||||
handleHits={handleHits}
|
||||
@ -98,8 +113,4 @@ const SearchHits = connectStateResults(
|
||||
}
|
||||
);
|
||||
|
||||
CustomHits.propTypes = {
|
||||
handleHits: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SearchHits;
|
@ -1,8 +1,18 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Highlight } from 'react-instantsearch-dom';
|
||||
import { Hit } from 'react-instantsearch-core';
|
||||
|
||||
const Suggestion = ({ hit, handleMouseEnter, handleMouseLeave }) => {
|
||||
interface suggestionPropTypes {
|
||||
hit: Hit;
|
||||
handleMouseEnter: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
handleMouseLeave: (e: React.SyntheticEvent<HTMLElement, Event>) => void;
|
||||
}
|
||||
|
||||
const Suggestion = ({
|
||||
hit,
|
||||
handleMouseEnter,
|
||||
handleMouseLeave
|
||||
}: suggestionPropTypes) => {
|
||||
const dropdownFooter = hit.objectID.includes('footer-');
|
||||
return (
|
||||
<a
|
||||
@ -28,10 +38,4 @@ const Suggestion = ({ hit, handleMouseEnter, handleMouseLeave }) => {
|
||||
);
|
||||
};
|
||||
|
||||
Suggestion.propTypes = {
|
||||
handleMouseEnter: PropTypes.func.isRequired,
|
||||
handleMouseLeave: PropTypes.func.isRequired,
|
||||
hit: PropTypes.object
|
||||
};
|
||||
|
||||
export default Suggestion;
|
@ -1,16 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
const propTypes = {
|
||||
query: PropTypes.string
|
||||
};
|
||||
|
||||
function NoResults({ query }) {
|
||||
function NoResults({ query }: { query: string }) {
|
||||
return (
|
||||
<div className='no-results-wrapper'>
|
||||
<p>
|
||||
<Trans i18nKey='search.no-results' query={query}>
|
||||
<Trans i18nKey='search.no-results' {...{ query: query }}>
|
||||
<em>{{ query }}</em>
|
||||
</Trans>
|
||||
</p>
|
||||
@ -19,6 +14,5 @@ function NoResults({ query }) {
|
||||
}
|
||||
|
||||
NoResults.displayName = 'NoResults';
|
||||
NoResults.propTypes = propTypes;
|
||||
|
||||
export default NoResults;
|
68
client/src/components/search/searchPage/search-page-hits.tsx
Normal file
68
client/src/components/search/searchPage/search-page-hits.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { EventHandler, SyntheticEvent } from 'react';
|
||||
import { AutocompleteExposed, SearchState } from 'react-instantsearch-core';
|
||||
import {
|
||||
Highlight,
|
||||
connectStateResults,
|
||||
connectAutoComplete
|
||||
} from 'react-instantsearch-dom';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import EmptySearch from './empty-search';
|
||||
import NoResults from './no-results';
|
||||
|
||||
import './search-page-hits.css';
|
||||
|
||||
type allHitType = {
|
||||
handleClick?: EventHandler<SyntheticEvent>;
|
||||
};
|
||||
|
||||
const AllHits: React.ComponentClass<AutocompleteExposed & allHitType, any> =
|
||||
connectAutoComplete(({ hits, currentRefinement }) => {
|
||||
const isHitsEmpty = !hits.length;
|
||||
|
||||
return currentRefinement && !isHitsEmpty ? (
|
||||
<div className='ais-Hits search-page'>
|
||||
<ul className='ais-Hits-list'>
|
||||
{hits.map(result => (
|
||||
<a
|
||||
href={result.url}
|
||||
key={result.objectID}
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
<li className='ais-Hits-item dataset-node'>
|
||||
<p>
|
||||
<Highlight attribute='title' hit={result} />
|
||||
</p>
|
||||
</li>
|
||||
</a>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<NoResults query={currentRefinement} />
|
||||
);
|
||||
});
|
||||
|
||||
AllHits.displayName = 'AllHits';
|
||||
|
||||
const SearchHits = connectStateResults(
|
||||
({
|
||||
handleClick,
|
||||
searchState
|
||||
}: {
|
||||
handleClick: EventHandler<SyntheticEvent>;
|
||||
searchState: SearchState;
|
||||
}) => {
|
||||
const isSearchEmpty = isEmpty(searchState) || isEmpty(searchState.query);
|
||||
|
||||
return isSearchEmpty ? (
|
||||
<EmptySearch />
|
||||
) : (
|
||||
<AllHits handleClick={handleClick} />
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SearchHits.displayName = 'SearchHits';
|
||||
|
||||
export default SearchHits;
|
Reference in New Issue
Block a user