2020-09-15 15:01:07 +02:00
|
|
|
import * as React from 'react';
|
2020-11-09 14:07:37 +02:00
|
|
|
import { useState, useCallback, useEffect, useRef } from 'react';
|
2020-11-07 17:59:37 +02:00
|
|
|
import CommandService from '@joplin/lib/services/CommandService';
|
2020-09-15 15:01:07 +02:00
|
|
|
import { Root, SearchInput, SearchButton, SearchButtonIcon } from './styles';
|
2020-11-09 14:07:37 +02:00
|
|
|
import Setting from '@joplin/lib/models/Setting';
|
2020-09-15 15:01:07 +02:00
|
|
|
|
2020-11-07 17:59:37 +02:00
|
|
|
import { _ } from '@joplin/lib/locale';
|
2020-11-09 14:07:37 +02:00
|
|
|
import { stateUtils } from '@joplin/lib/reducer';
|
|
|
|
import BaseModel from '@joplin/lib/BaseModel';
|
|
|
|
import uuid from '@joplin/lib/uuid';
|
2020-10-09 19:35:46 +02:00
|
|
|
const { connect } = require('react-redux');
|
2020-11-09 14:07:37 +02:00
|
|
|
const Note = require('@joplin/lib/models/Note');
|
|
|
|
const debounce = require('debounce');
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
interface Props {
|
2020-11-12 21:29:22 +02:00
|
|
|
inputRef?: any;
|
|
|
|
notesParentType: string;
|
|
|
|
dispatch?: Function;
|
|
|
|
selectedNoteId: string;
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function SearchBar(props: Props) {
|
2020-09-15 15:01:07 +02:00
|
|
|
const [query, setQuery] = useState('');
|
2020-11-09 14:07:37 +02:00
|
|
|
const [searchStarted, setSearchStarted] = useState(false);
|
|
|
|
const iconName = !searchStarted ? CommandService.instance().iconName('search') : 'fa fa-times';
|
|
|
|
const searchId = useRef(uuid.create());
|
|
|
|
|
|
|
|
useEffect(() => {
|
2020-11-12 21:13:28 +02:00
|
|
|
function search(searchId: string, query: string, dispatch: Function) {
|
2020-11-09 14:07:37 +02:00
|
|
|
dispatch({
|
|
|
|
type: 'SEARCH_UPDATE',
|
|
|
|
search: {
|
|
|
|
id: searchId,
|
|
|
|
title: query,
|
|
|
|
query_pattern: query,
|
|
|
|
query_folder_id: null,
|
|
|
|
type_: BaseModel.TYPE_SEARCH,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
dispatch({
|
|
|
|
type: 'SEARCH_SELECT',
|
|
|
|
id: searchId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const debouncedSearch = debounce(search, 500);
|
|
|
|
if (searchStarted) debouncedSearch(searchId.current, query, props.dispatch);
|
|
|
|
return () => {
|
|
|
|
debouncedSearch.clear();
|
|
|
|
};
|
|
|
|
}, [query, searchStarted]);
|
|
|
|
|
|
|
|
const onExitSearch = useCallback(async (navigateAway = true) => {
|
|
|
|
setQuery('');
|
|
|
|
setSearchStarted(false);
|
|
|
|
|
|
|
|
if (navigateAway) {
|
|
|
|
const note = props.selectedNoteId ? await Note.load(props.selectedNoteId) : null;
|
|
|
|
|
|
|
|
if (note) {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'FOLDER_AND_NOTE_SELECT',
|
|
|
|
folderId: note.parent_id,
|
|
|
|
noteId: note.id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const folderId = Setting.value('activeFolderId');
|
|
|
|
if (folderId) {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'FOLDER_SELECT',
|
|
|
|
id: folderId,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [props.selectedNoteId]);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function onChange(event: any) {
|
2020-11-09 14:07:37 +02:00
|
|
|
setSearchStarted(true);
|
2020-09-15 15:01:07 +02:00
|
|
|
setQuery(event.currentTarget.value);
|
2020-09-29 13:31:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function onFocus() {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'FOCUS_SET',
|
|
|
|
field: 'globalSearch',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBlur() {
|
|
|
|
// Do it after a delay so that the "Clear" button
|
|
|
|
// can be clicked on (otherwise the field loses focus
|
|
|
|
// and is resized before the click event has been processed)
|
|
|
|
setTimeout(() => {
|
|
|
|
props.dispatch({
|
|
|
|
type: 'FOCUS_CLEAR',
|
|
|
|
field: 'globalSearch',
|
|
|
|
});
|
|
|
|
}, 300);
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
const onKeyDown = useCallback((event: any) => {
|
2020-09-29 13:31:19 +02:00
|
|
|
if (event.key === 'Escape') {
|
|
|
|
if (document.activeElement) (document.activeElement as any).blur();
|
2020-11-25 16:40:25 +02:00
|
|
|
void onExitSearch();
|
2020-09-29 13:31:19 +02:00
|
|
|
}
|
2020-11-09 14:07:37 +02:00
|
|
|
}, [onExitSearch]);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
const onSearchButtonClick = useCallback(() => {
|
2020-11-25 16:40:25 +02:00
|
|
|
void onExitSearch();
|
2020-11-09 14:07:37 +02:00
|
|
|
}, [onExitSearch]);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (props.notesParentType !== 'Search') {
|
2020-11-25 16:40:25 +02:00
|
|
|
void onExitSearch(false);
|
2020-09-15 15:01:07 +02:00
|
|
|
}
|
2020-11-09 14:07:37 +02:00
|
|
|
}, [props.notesParentType, onExitSearch]);
|
2020-09-15 15:01:07 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<Root>
|
2020-09-29 13:31:19 +02:00
|
|
|
<SearchInput
|
|
|
|
ref={props.inputRef}
|
|
|
|
value={query}
|
|
|
|
type="text"
|
|
|
|
placeholder={_('Search...')}
|
|
|
|
onChange={onChange}
|
|
|
|
onFocus={onFocus}
|
|
|
|
onBlur={onBlur}
|
|
|
|
onKeyDown={onKeyDown}
|
2020-11-16 18:20:26 +02:00
|
|
|
spellCheck={false}
|
2020-09-29 13:31:19 +02:00
|
|
|
/>
|
2020-09-15 15:01:07 +02:00
|
|
|
<SearchButton onClick={onSearchButtonClick}>
|
|
|
|
<SearchButtonIcon className={iconName}/>
|
|
|
|
</SearchButton>
|
|
|
|
</Root>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
const mapStateToProps = (state: any) => {
|
2020-09-15 15:01:07 +02:00
|
|
|
return {
|
|
|
|
notesParentType: state.notesParentType,
|
2020-11-09 14:07:37 +02:00
|
|
|
selectedNoteId: stateUtils.selectedNoteId(state),
|
2020-09-15 15:01:07 +02:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export default connect(mapStateToProps)(SearchBar);
|