2021-06-25 10:42:44 +02:00
|
|
|
import { useRef, useEffect, KeyboardEvent } from 'react';
|
|
|
|
|
|
|
|
// Redux
|
2021-11-09 14:33:51 +01:00
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
2021-06-25 10:42:44 +02:00
|
|
|
|
|
|
|
// Typescript
|
2021-11-09 14:33:51 +01:00
|
|
|
import { App, Category } from '../../interfaces';
|
2021-06-25 10:42:44 +02:00
|
|
|
|
|
|
|
// CSS
|
|
|
|
import classes from './SearchBar.module.css';
|
|
|
|
|
|
|
|
// Utils
|
2021-10-06 12:01:07 +02:00
|
|
|
import { searchParser, urlParser, redirectUrl } from '../../utility';
|
2021-11-09 14:33:51 +01:00
|
|
|
import { State } from '../../store/reducers';
|
|
|
|
import { bindActionCreators } from 'redux';
|
|
|
|
import { actionCreators } from '../../store';
|
2021-06-25 10:42:44 +02:00
|
|
|
|
2021-11-09 14:33:51 +01:00
|
|
|
interface Props {
|
2021-09-06 12:24:01 +02:00
|
|
|
setLocalSearch: (query: string) => void;
|
2021-10-27 11:52:57 +02:00
|
|
|
appSearchResult: App[] | null;
|
|
|
|
bookmarkSearchResult: Category[] | null;
|
2021-06-25 10:42:44 +02:00
|
|
|
}
|
|
|
|
|
2021-11-09 14:33:51 +01:00
|
|
|
export const SearchBar = (props: Props): JSX.Element => {
|
|
|
|
const { config, loading } = useSelector((state: State) => state.config);
|
|
|
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
const { createNotification } = bindActionCreators(actionCreators, dispatch);
|
|
|
|
|
|
|
|
const { setLocalSearch, appSearchResult, bookmarkSearchResult } = props;
|
2021-09-06 12:24:01 +02:00
|
|
|
|
2021-06-25 10:42:44 +02:00
|
|
|
const inputRef = useRef<HTMLInputElement>(document.createElement('input'));
|
|
|
|
|
2021-10-26 14:37:01 +02:00
|
|
|
// Search bar autofocus
|
2021-06-25 10:42:44 +02:00
|
|
|
useEffect(() => {
|
2021-10-26 13:09:42 +02:00
|
|
|
if (!loading && !config.disableAutofocus) {
|
|
|
|
inputRef.current.focus();
|
|
|
|
}
|
|
|
|
}, [config]);
|
2021-06-25 10:42:44 +02:00
|
|
|
|
2021-10-26 14:37:01 +02:00
|
|
|
// Listen for keyboard events outside of search bar
|
|
|
|
useEffect(() => {
|
|
|
|
const keyOutsideFocus = (e: any) => {
|
|
|
|
const { key } = e as KeyboardEvent;
|
|
|
|
|
|
|
|
if (key === 'Escape') {
|
|
|
|
clearSearch();
|
2021-11-14 23:20:37 +01:00
|
|
|
} else if (document.activeElement !== inputRef.current) {
|
|
|
|
if (key === '`') {
|
|
|
|
inputRef.current.focus();
|
|
|
|
clearSearch();
|
|
|
|
}
|
2021-10-26 14:37:01 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-14 23:20:37 +01:00
|
|
|
window.addEventListener('keyup', keyOutsideFocus);
|
2021-10-26 14:37:01 +02:00
|
|
|
|
2021-11-14 23:20:37 +01:00
|
|
|
return () => window.removeEventListener('keyup', keyOutsideFocus);
|
2021-10-26 14:37:01 +02:00
|
|
|
}, []);
|
|
|
|
|
2021-10-13 13:31:01 +02:00
|
|
|
const clearSearch = () => {
|
|
|
|
inputRef.current.value = '';
|
|
|
|
setLocalSearch('');
|
|
|
|
};
|
|
|
|
|
2021-06-25 10:42:44 +02:00
|
|
|
const searchHandler = (e: KeyboardEvent<HTMLInputElement>) => {
|
2021-10-06 12:01:07 +02:00
|
|
|
const { isLocal, search, query, isURL, sameTab } = searchParser(
|
|
|
|
inputRef.current.value
|
|
|
|
);
|
2021-09-06 12:47:17 +02:00
|
|
|
|
2021-10-06 12:01:07 +02:00
|
|
|
if (isLocal) {
|
2022-02-14 13:22:41 +01:00
|
|
|
setLocalSearch(search);
|
2021-09-06 12:47:17 +02:00
|
|
|
}
|
2021-06-25 10:42:44 +02:00
|
|
|
|
2021-10-13 13:31:01 +02:00
|
|
|
if (e.code === 'Enter' || e.code === 'NumpadEnter') {
|
2021-10-06 12:01:07 +02:00
|
|
|
if (!query.prefix) {
|
2021-10-13 13:31:01 +02:00
|
|
|
// Prefix not found -> emit notification
|
2021-09-06 12:24:01 +02:00
|
|
|
createNotification({
|
2021-06-25 10:42:44 +02:00
|
|
|
title: 'Error',
|
2021-09-06 12:24:01 +02:00
|
|
|
message: 'Prefix not found',
|
|
|
|
});
|
2021-10-06 12:01:07 +02:00
|
|
|
} else if (isURL) {
|
2021-10-13 13:31:01 +02:00
|
|
|
// URL or IP passed -> redirect
|
2021-10-06 12:01:07 +02:00
|
|
|
const url = urlParser(inputRef.current.value)[1];
|
|
|
|
redirectUrl(url, sameTab);
|
|
|
|
} else if (isLocal) {
|
2021-10-27 11:52:57 +02:00
|
|
|
// Local query -> redirect if at least 1 result found
|
|
|
|
if (appSearchResult?.length) {
|
|
|
|
redirectUrl(appSearchResult[0].url, sameTab);
|
2021-11-08 13:22:11 +01:00
|
|
|
} else if (bookmarkSearchResult?.[0]?.bookmarks?.length) {
|
2021-10-27 11:52:57 +02:00
|
|
|
redirectUrl(bookmarkSearchResult[0].bookmarks[0].url, sameTab);
|
2021-11-08 13:22:11 +01:00
|
|
|
} else {
|
|
|
|
// no local results -> search the internet with the default search provider
|
|
|
|
let template = query.template;
|
|
|
|
|
|
|
|
if (query.prefix === 'l') {
|
|
|
|
template = 'https://duckduckgo.com/?q=';
|
|
|
|
}
|
|
|
|
|
|
|
|
const url = `${template}${search}`;
|
|
|
|
redirectUrl(url, sameTab);
|
2021-10-27 11:52:57 +02:00
|
|
|
}
|
2021-09-06 12:47:17 +02:00
|
|
|
} else {
|
2021-10-13 13:31:01 +02:00
|
|
|
// Valid query -> redirect to search results
|
2021-10-06 12:01:07 +02:00
|
|
|
const url = `${query.template}${search}`;
|
|
|
|
redirectUrl(url, sameTab);
|
2021-06-25 10:42:44 +02:00
|
|
|
}
|
2021-10-13 13:31:01 +02:00
|
|
|
} else if (e.code === 'Escape') {
|
|
|
|
clearSearch();
|
2021-06-25 10:42:44 +02:00
|
|
|
}
|
2021-09-06 12:24:01 +02:00
|
|
|
};
|
2021-06-25 10:42:44 +02:00
|
|
|
|
|
|
|
return (
|
2021-10-13 13:31:01 +02:00
|
|
|
<div className={classes.SearchContainer}>
|
|
|
|
<input
|
|
|
|
ref={inputRef}
|
|
|
|
type="text"
|
|
|
|
className={classes.SearchBar}
|
|
|
|
onKeyUp={(e) => searchHandler(e)}
|
2021-10-22 15:51:11 +02:00
|
|
|
onDoubleClick={clearSearch}
|
2021-10-13 13:31:01 +02:00
|
|
|
/>
|
|
|
|
</div>
|
2021-09-06 12:24:01 +02:00
|
|
|
);
|
|
|
|
};
|