diff --git a/frontend/src/Components/Page/Header/AuthorSearchInput.js b/frontend/src/Components/Page/Header/AuthorSearchInput.js
index 0b6f33d5d..7c767e6e7 100644
--- a/frontend/src/Components/Page/Header/AuthorSearchInput.js
+++ b/frontend/src/Components/Page/Header/AuthorSearchInput.js
@@ -6,7 +6,9 @@ import Icon from 'Components/Icon';
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { icons } from 'Helpers/Props';
+import translate from 'Utilities/String/translate';
import AuthorSearchResult from './AuthorSearchResult';
+import BookSearchResult from './BookSearchResult';
import FuseWorker from './fuse.worker';
import styles from './AuthorSearchInput.css';
@@ -96,17 +98,43 @@ class AuthorSearchInput extends Component {
);
}
- return (
-
- );
+ if (item.item.type === 'author') {
+ return (
+
+ );
+ }
+
+ if (item.item.type === 'book') {
+ return (
+
+ );
+ }
}
- goToAuthor(item) {
+ goToItem(item) {
+ const {
+ onGoToAuthor,
+ onGoToBook
+ } = this.props;
+
this.setState({ value: '' });
- this.props.onGoToAuthor(item.item.titleSlug);
+
+ const {
+ type,
+ titleSlug
+ } = item.item;
+
+ if (type === 'author') {
+ onGoToAuthor(titleSlug);
+ } else if (type === 'book') {
+ onGoToBook(titleSlug);
+ }
}
reset() {
@@ -164,9 +192,9 @@ class AuthorSearchInput extends Component {
// otherwise go to the selected author.
if (highlightedSuggestionIndex == null) {
- this.goToAuthor(suggestions[0]);
+ this.goToItem(suggestions[0]);
} else {
- this.goToAuthor(suggestions[highlightedSuggestionIndex]);
+ this.goToItem(suggestions[highlightedSuggestionIndex]);
}
this._autosuggest.input.blur();
@@ -202,7 +230,7 @@ class AuthorSearchInput extends Component {
if (!requestLoading) {
const payload = {
value,
- authors: this.props.authors
+ items: this.props.items
};
this.getWorker().postMessage(payload);
@@ -235,7 +263,7 @@ class AuthorSearchInput extends Component {
const payload = {
value: this.state.requestValue,
- authors: this.props.authors
+ items: this.props.items
};
this.getWorker().postMessage(payload);
@@ -253,7 +281,7 @@ class AuthorSearchInput extends Component {
if (suggestion.type === ADD_NEW_TYPE) {
this.props.onGoToAddNewAuthor(this.state.value);
} else {
- this.goToAuthor(suggestion);
+ this.goToItem(suggestion);
}
}
@@ -271,14 +299,14 @@ class AuthorSearchInput extends Component {
if (suggestions.length || loading) {
suggestionGroups.push({
- title: 'Existing Author',
+ title: translate('ExistingItems'),
loading,
suggestions
});
}
suggestionGroups.push({
- title: 'Add New Item',
+ title: translate('AddNewItem'),
suggestions: [
{
type: ADD_NEW_TYPE,
@@ -292,7 +320,7 @@ class AuthorSearchInput extends Component {
className: styles.input,
name: 'authorSearch',
value,
- placeholder: 'Search',
+ placeholder: translate('Search'),
autoComplete: 'off',
spellCheck: false,
onChange: this.onChange,
@@ -336,8 +364,9 @@ class AuthorSearchInput extends Component {
}
AuthorSearchInput.propTypes = {
- authors: PropTypes.arrayOf(PropTypes.object).isRequired,
+ items: PropTypes.arrayOf(PropTypes.object).isRequired,
onGoToAuthor: PropTypes.func.isRequired,
+ onGoToBook: PropTypes.func.isRequired,
onGoToAddNewAuthor: PropTypes.func.isRequired,
bindShortcut: PropTypes.func.isRequired
};
diff --git a/frontend/src/Components/Page/Header/AuthorSearchInputConnector.js b/frontend/src/Components/Page/Header/AuthorSearchInputConnector.js
index 50b10e450..0b244db02 100644
--- a/frontend/src/Components/Page/Header/AuthorSearchInputConnector.js
+++ b/frontend/src/Components/Page/Header/AuthorSearchInputConnector.js
@@ -21,7 +21,8 @@ function createCleanAuthorSelector() {
} = author;
return {
- authorName,
+ type: 'author',
+ name: authorName,
sortName,
titleSlug,
images,
@@ -40,12 +41,41 @@ function createCleanAuthorSelector() {
);
}
+function createCleanBookSelector() {
+ return createSelector(
+ (state) => state.books.items,
+ (allBooks) => {
+ return allBooks.map((book) => {
+ const {
+ title,
+ images,
+ titleSlug
+ } = book;
+
+ return {
+ type: 'book',
+ name: title,
+ sortName: title,
+ titleSlug,
+ images,
+ tags: []
+ };
+ });
+ }
+ );
+}
+
function createMapStateToProps() {
return createDeepEqualSelector(
createCleanAuthorSelector(),
- (authors) => {
+ createCleanBookSelector(),
+ (authors, books) => {
+ const items = [
+ ...authors,
+ ...books
+ ];
return {
- authors
+ items
};
}
);
@@ -57,6 +87,10 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(push(`${window.Readarr.urlBase}/author/${titleSlug}`));
},
+ onGoToBook(titleSlug) {
+ dispatch(push(`${window.Readarr.urlBase}/book/${titleSlug}`));
+ },
+
onGoToAddNewAuthor(query) {
dispatch(push(`${window.Readarr.urlBase}/add/search?term=${encodeURIComponent(query)}`));
}
diff --git a/frontend/src/Components/Page/Header/AuthorSearchResult.js b/frontend/src/Components/Page/Header/AuthorSearchResult.js
index 2b5a41293..2fb7a6457 100644
--- a/frontend/src/Components/Page/Header/AuthorSearchResult.js
+++ b/frontend/src/Components/Page/Header/AuthorSearchResult.js
@@ -8,7 +8,7 @@ import styles from './AuthorSearchResult.css';
function AuthorSearchResult(props) {
const {
match,
- authorName,
+ name,
images,
tags
} = props;
@@ -31,7 +31,7 @@ function AuthorSearchResult(props) {
- {authorName}
+ {name}
{
@@ -52,7 +52,7 @@ function AuthorSearchResult(props) {
}
AuthorSearchResult.propTypes = {
- authorName: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
tags: PropTypes.arrayOf(PropTypes.object).isRequired,
match: PropTypes.object.isRequired
diff --git a/frontend/src/Components/Page/Header/BookSearchResult.css b/frontend/src/Components/Page/Header/BookSearchResult.css
new file mode 100644
index 000000000..4fba173b6
--- /dev/null
+++ b/frontend/src/Components/Page/Header/BookSearchResult.css
@@ -0,0 +1,39 @@
+.result {
+ display: flex;
+ padding: 3px;
+ cursor: pointer;
+}
+
+.poster {
+ width: 35px;
+ height: 35px;
+ object-fit: contain;
+}
+
+.titles {
+ flex: 1 1 1px;
+}
+
+.title {
+ flex: 1 1 1px;
+ margin-left: 5px;
+}
+
+.alternateTitle {
+ composes: title;
+
+ color: $disabledColor;
+ font-size: $smallFontSize;
+}
+
+.tagContainer {
+ composes: title;
+}
+
+@media only screen and (max-width: $breakpointSmall) {
+ .titles,
+ .title,
+ .alternateTitle {
+ @add-mixin truncate;
+ }
+}
diff --git a/frontend/src/Components/Page/Header/BookSearchResult.js b/frontend/src/Components/Page/Header/BookSearchResult.js
new file mode 100644
index 000000000..4fc427865
--- /dev/null
+++ b/frontend/src/Components/Page/Header/BookSearchResult.js
@@ -0,0 +1,39 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import AuthorPoster from 'Author/AuthorPoster';
+import styles from './BookSearchResult.css';
+
+function BookSearchResult(props) {
+ const {
+ name,
+ images
+ } = props;
+
+ return (
+
+ );
+}
+
+BookSearchResult.propTypes = {
+ name: PropTypes.string.isRequired,
+ images: PropTypes.arrayOf(PropTypes.object).isRequired,
+ tags: PropTypes.arrayOf(PropTypes.object).isRequired,
+ match: PropTypes.object.isRequired
+};
+
+export default BookSearchResult;
diff --git a/frontend/src/Components/Page/Header/fuse.worker.js b/frontend/src/Components/Page/Header/fuse.worker.js
index 8d0f06c60..5ddb505f4 100644
--- a/frontend/src/Components/Page/Header/fuse.worker.js
+++ b/frontend/src/Components/Page/Header/fuse.worker.js
@@ -8,43 +8,16 @@ const fuseOptions = {
distance: 100,
minMatchCharLength: 1,
keys: [
- 'authorName',
+ 'name',
'tags.label'
]
};
-function getSuggestions(authors, value) {
+function getSuggestions(items, value) {
const limit = 10;
- let suggestions = [];
- if (value.length === 1) {
- for (let i = 0; i < authors.length; i++) {
- const s = authors[i];
- if (s.firstCharacter === value.toLowerCase()) {
- suggestions.push({
- item: authors[i],
- indices: [
- [0, 0]
- ],
- matches: [
- {
- value: s.title,
- key: 'title'
- }
- ],
- arrayIndex: 0
- });
- if (suggestions.length > limit) {
- break;
- }
- }
- }
- } else {
- const fuse = new Fuse(authors, fuseOptions);
- suggestions = fuse.search(value, { limit });
- }
-
- return suggestions;
+ const fuse = new Fuse(items, fuseOptions);
+ return fuse.search(value, { limit });
}
onmessage = function(e) {
@@ -53,16 +26,20 @@ onmessage = function(e) {
}
const {
- authors,
+ items,
value
} = e.data;
- const suggestions = getSuggestions(authors, value);
+ console.log(`got search request ${value} with ${items.length} items`);
+
+ const suggestions = getSuggestions(items, value);
const results = {
value,
suggestions
};
+ console.log(`return ${suggestions.length} results for search ${value}`);
+
self.postMessage(results);
};
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 507a8b0b6..0d3ebd1b3 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -217,6 +217,7 @@
"ErrorLoadingPreviews": "Error loading previews",
"Exception": "Exception",
"ExistingBooks": "Existing Books",
+ "ExistingItems": "Existing Items",
"ExistingTagsScrubbed": "Existing tags scrubbed",
"ExtraFileExtensionsHelpTexts1": "Comma separated list of extra files to import (.nfo will be imported as .nfo-orig)",
"ExtraFileExtensionsHelpTexts2": "Examples: \".sub, .nfo\" or \"sub,nfo\"",