Skip to content

Commit da25a97

Browse files
committed
feat(search): adds recent search and allow type search
1 parent 1605ba2 commit da25a97

File tree

4 files changed

+83
-41
lines changed

4 files changed

+83
-41
lines changed

src/components/common/list/ListItem.vue

-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ const overOpen = (e: MouseEvent) => {
156156
157157
const onHover = (_event: MouseEvent, _hover: boolean) => {
158158
emit('onHover', { item: item?.value, hover: _hover });
159-
itemRef.value?.$el?.focus({ preventScroll: true });
160159
open.value = _hover && (_event.altKey || _event.ctrlKey);
161160
if (!_hover) dragged.value = 0;
162161
if (!_hover) return clearTimeout(hoverTimeout.value);

src/components/views/search/SearchNavbar.vue

+66-35
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ import IconChevronUp from '~/components/icons/IconChevronUpSmall.vue';
2323
import IconList from '~/components/icons/IconList.vue';
2424
import IconLoop from '~/components/icons/IconLoop.vue';
2525
import IconMovie from '~/components/icons/IconMovie.vue';
26+
import IconRestore from '~/components/icons/IconRestore.vue';
2627
import IconScreen from '~/components/icons/IconScreen.vue';
2728
import IconYoutube from '~/components/icons/IconYoutube.vue';
28-
2929
import { ResolveExternalLinks } from '~/settings/external.links';
3030
import { SupportedSearchType, useSearchStoreRefs } from '~/stores/data/search.store';
3131
import { debounce } from '~/utils/debounce.utils';
@@ -34,34 +34,6 @@ import { useDebouncedSearch } from '~/utils/store.utils';
3434
3535
const i18n = useI18n('navbar', 'search');
3636
37-
const { search, types, query, pageSize, loading, history } = useSearchStoreRefs();
38-
39-
const typeOptions = ref<TraktSearchType[]>(SupportedSearchType);
40-
41-
const inputFocus = ref(false);
42-
const toggleFocus = (focus: boolean) => {
43-
inputFocus.value = focus;
44-
};
45-
46-
const debouncedSearch = useDebouncedSearch(search, 1000);
47-
const external = computed(() => ResolveExternalLinks.trakt.query(debouncedSearch.value));
48-
49-
const filteredHistory = computed(() =>
50-
[...history.value]
51-
.filter(val => {
52-
const _search = debouncedSearch.value?.toLowerCase().trim();
53-
if (!_search) return false;
54-
const _val = val.toLowerCase().trim();
55-
if (_val === _search) return false;
56-
return _val.includes(_search) || _search.includes(_val);
57-
})
58-
.slice(0, 10),
59-
);
60-
61-
const historyOptions = computed(() =>
62-
filteredHistory.value.map(value => ({ value, label: value })),
63-
);
64-
6537
const { reverse } = defineProps({
6638
parentElement: {
6739
type: HTMLElement,
@@ -74,6 +46,63 @@ const { reverse } = defineProps({
7446
},
7547
});
7648
49+
const { search, types, query, pageSize, loading, history } = useSearchStoreRefs();
50+
51+
const typeOptions = ref<TraktSearchType[]>(SupportedSearchType);
52+
53+
const debouncedSearch = useDebouncedSearch(search, 1000);
54+
const external = computed(() => ResolveExternalLinks.trakt.query(debouncedSearch.value));
55+
56+
const filteredHistory = computed(() => {
57+
const _search = debouncedSearch.value?.toLowerCase().trim();
58+
59+
if (!_search || !history.value) {
60+
return { filter: [], recent: Array.from(history.value) };
61+
}
62+
63+
const filter: string[] = [];
64+
const recent: string[] = [];
65+
66+
history.value.forEach(value => {
67+
const _val = value.toLowerCase().trim();
68+
if (_val === _search) return;
69+
if (_val.includes(_search) || _search.includes(_val)) {
70+
filter.push(value);
71+
} else if (recent.length < 5) {
72+
recent.push(value);
73+
}
74+
});
75+
76+
return { filter, recent };
77+
});
78+
const historyOptions = computed(() => {
79+
const results = [];
80+
const filter = {
81+
type: 'group',
82+
label: i18n('option_group_filter'),
83+
key: 'filter',
84+
children: filteredHistory.value.filter.map(value => ({
85+
value,
86+
label: value,
87+
key: `filter-${value}`,
88+
})),
89+
};
90+
if (filter.children.length) results.push(filter);
91+
const recent = {
92+
type: 'group',
93+
label: i18n('option_group_recent'),
94+
key: 'recent',
95+
children: filteredHistory.value.recent.map(value => ({
96+
value,
97+
label: value,
98+
key: `recent-${value}`,
99+
icon: IconRestore,
100+
})),
101+
};
102+
if (recent.children.length) results.push(recent);
103+
return results;
104+
});
105+
77106
const selectedValues = computed({
78107
get: () => types.value,
79108
set: selected => {
@@ -98,8 +127,11 @@ const getIcon = (type: TraktSearchType) => {
98127
99128
const open = ref(false);
100129
101-
type SearchOption = SelectOption & { value: TraktSearchType; icon: Component };
102-
const searchOptions = computed<SearchOption[]>(() =>
130+
type SearchOption<T extends string = string> = SelectOption & {
131+
value: T;
132+
icon: Component;
133+
};
134+
const searchOptions = computed<SearchOption<TraktSearchType>[]>(() =>
103135
typeOptions.value.map(type => ({
104136
label: i18n(type, 'navbar', 'search', 'type'),
105137
value: type,
@@ -169,7 +201,9 @@ const placement = computed(() => (reverse ? 'top' : 'bottom'));
169201
170202
onActivated(() => {
171203
if (!search.value) inputRef.value?.focus();
172-
if (typeof route?.query?.search === 'string') search.value = route.query.search;
204+
const _search = route?.query?.search;
205+
if (typeof _search !== 'string') return;
206+
search.value = _search.trim();
173207
});
174208
</script>
175209

@@ -218,12 +252,9 @@ onActivated(() => {
218252
v-model:value="debouncedSearch"
219253
class="search-input"
220254
:loading="loading"
221-
:disabled="loading"
222255
:placeholder="i18n('search', 'navbar')"
223256
autosize
224257
clearable
225-
:on-input-focus="() => toggleFocus(true)"
226-
:on-input-blur="() => toggleFocus(false)"
227258
:options="historyOptions"
228259
:to="parentElement"
229260
>

src/i18n/en/navbar/navbar-search.json

+8
Original file line numberDiff line numberDiff line change
@@ -82,5 +82,13 @@
8282
"navbar__search__tooltip_regex": {
8383
"message": "Used for searching using regular expressions.",
8484
"description": "Search query tooltip for the regex keywords (/)"
85+
},
86+
"navbar__search__option_group_filter": {
87+
"message": "Filter",
88+
"description": "Label for the search option group filter"
89+
},
90+
"navbar__search__option_group_recent": {
91+
"message": "Recent",
92+
"description": "Label for the search option group recent"
8593
}
8694
}

src/stores/data/search.store.ts

+9-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export type SearchResult = Omit<TraktSearchResult, 'type'> & {
2929

3030
export const SupportedSearchType: TraktSearchType[] = ['episode', 'show', 'movie', 'person'];
3131
export const DefaultSearchType: TraktSearchType[] = ['show', 'movie'];
32+
export const minSearchLength = 3;
3233

3334
const getSearchId = (result: TraktSearchResult) => {
3435
if (!result) return;
@@ -74,16 +75,17 @@ export const useSearchStore = defineStore(SearchStoreConstants.Store, () => {
7475
const saveHistory = debounce(() => storage.local.set(SearchStoreConstants.LocalHistory, [...history.value]), 1000);
7576
const saveSearch = debounce(() => storage.local.set(SearchStoreConstants.LocalLast, { value: search.value, date: Date.now() }), 1000);
7677

77-
const addToHistory = (value: string = search.value) => {
78-
const newArray = [...history.value, value].filter(Boolean);
78+
const addToHistory = debounce((value: string = search.value) => {
79+
if (!value || value.trim().length < minSearchLength) return;
80+
const newArray = [value, ...history.value].map(v => v.trim()).filter(v => v && v.length > 3);
7981
// Keep only the last 100 elements
8082
if (newArray.length > 100) {
81-
history.value = new Set(newArray.slice(-100));
83+
history.value = new Set(newArray.slice(0, 100));
8284
} else {
8385
history.value = new Set(newArray);
8486
}
8587
return Promise.all([saveHistory(), saveSearch()]);
86-
};
88+
}, 3000);
8789

8890
const clearState = () => {
8991
types.value = DefaultSearchType;
@@ -186,8 +188,9 @@ export const useSearchStore = defineStore(SearchStoreConstants.Store, () => {
186188
await restoreState();
187189

188190
watch(search, async () => {
191+
if (!search.value || search.value.length < minSearchLength) return;
189192
await fetchSearchResults();
190-
await addToHistory().catch(e => Logger.error('Failed to save search history', e));
193+
addToHistory().catch(e => Logger.error('Failed to save search history', e));
191194
});
192195

193196
watch([pageSize, types], async () => {
@@ -201,6 +204,7 @@ export const useSearchStore = defineStore(SearchStoreConstants.Store, () => {
201204
query,
202205
search,
203206
history,
207+
addToHistory,
204208
clearState,
205209
initSearchStore,
206210
fetchSearchResults,

0 commit comments

Comments
 (0)