diff --git a/assets/js/SearchTagInputHelper.ts b/assets/js/SearchTagInputHelper.ts new file mode 100644 index 00000000..c042a3b9 --- /dev/null +++ b/assets/js/SearchTagInputHelper.ts @@ -0,0 +1,15 @@ +export function normalizeSearchTagInput(rawValue: string) { + if (!rawValue) { + return '' + } + + let value = rawValue.trim() + + // Collapse one or more OR tokens (case-insensitive) into a comma — the OR-group delimiter + value = value.replace(/\s*(?:\bor\b(?:\s+)?)+\s*/gi, ',') + + // Replace remaining spaces (within individual tag names) with underscores + value = value.replace(/\s/g, '_') + + return value +} diff --git a/components/pages/home/SimpleSearch.vue b/components/pages/home/SimpleSearch.vue index 63892f3a..c32d1b10 100644 --- a/components/pages/home/SimpleSearch.vue +++ b/components/pages/home/SimpleSearch.vue @@ -2,6 +2,7 @@ import { CheckIcon, MagnifyingGlassIcon } from '@heroicons/vue/20/solid' import { watchDebounced } from '@vueuse/core' import { abbreviateNumber } from 'js-abbreviation-number' + import { normalizeSearchTagInput } from '~/assets/js/SearchTagInputHelper' import Tag from '~/assets/js/tag.dto' const props = defineProps<{ @@ -22,14 +23,9 @@ // Change event function onComboboxInputChange(event: InputEvent) { - let value = (event.target as HTMLInputElement).value + const value = (event.target as HTMLInputElement).value - value = value.trim() - - // Replace empty spaces with underscores - value = value.replace(/\s/g, '_') - - searchQuery.value = value + searchQuery.value = normalizeSearchTagInput(value) } watchDebounced(searchQuery, (value) => onSearchChange(value), { debounce: 350 }) diff --git a/components/pages/posts/navigation/search/SearchMenu.vue b/components/pages/posts/navigation/search/SearchMenu.vue index 37367efe..76c361e9 100644 --- a/components/pages/posts/navigation/search/SearchMenu.vue +++ b/components/pages/posts/navigation/search/SearchMenu.vue @@ -4,6 +4,7 @@ import { watchDebounced } from '@vueuse/core' import { abbreviateNumber } from 'js-abbreviation-number' import { cloneDeep, unionWith } from 'es-toolkit' + import { normalizeSearchTagInput } from '~/assets/js/SearchTagInputHelper' import Tag from '~/assets/js/tag.dto' import SearchSelect from './SearchSelect.vue' @@ -60,14 +61,9 @@ // Change event function onComboboxInputChange(event: InputEvent) { - let value = (event.target as HTMLInputElement).value + const value = (event.target as HTMLInputElement).value - value = value.trim() - - // Replace empty spaces with underscores - value = value.replace(/\s/g, '_') - - searchQuery.value = value + searchQuery.value = normalizeSearchTagInput(value) } watchDebounced(searchQuery, (value) => onSearchChange(value), { debounce: 350 }) diff --git a/test/assets/search-tag-input-helper.test.ts b/test/assets/search-tag-input-helper.test.ts new file mode 100644 index 00000000..d346ad57 --- /dev/null +++ b/test/assets/search-tag-input-helper.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest' +import { normalizeSearchTagInput } from '../../assets/js/SearchTagInputHelper' + +describe('normalizeSearchTagInput', () => { + it('maps typed OR separators to commas case-insensitively', () => { + expect(normalizeSearchTagInput('cat OR dog')).toBe('cat,dog') + expect(normalizeSearchTagInput('cat or dog')).toBe('cat,dog') + expect(normalizeSearchTagInput('cat Or dog')).toBe('cat,dog') + expect(normalizeSearchTagInput('cat oR dog')).toBe('cat,dog') + }) + + it('maps multiple OR separators to commas', () => { + expect(normalizeSearchTagInput('cat OR dog or fish')).toBe('cat,dog,fish') + }) + + it('preserves existing behavior of converting spaces in tags to underscores', () => { + expect(normalizeSearchTagInput('black hair')).toBe('black_hair') + expect(normalizeSearchTagInput('black hair OR blue eyes')).toBe('black_hair,blue_eyes') + }) + + it('trims surrounding whitespace', () => { + expect(normalizeSearchTagInput(' cat OR dog ')).toBe('cat,dog') + }) + + it('collapses adjacent/repeated OR separators into a single comma', () => { + expect(normalizeSearchTagInput('cat OR OR dog')).toBe('cat,dog') + expect(normalizeSearchTagInput('cat or OR or dog')).toBe('cat,dog') + expect(normalizeSearchTagInput('cat OR OR OR dog')).toBe('cat,dog') + }) +})