From 2fdf1c578159d349c3c452c19f20ae40abc68eff Mon Sep 17 00:00:00 2001 From: AlejandroAkbal <37181533+AlejandroAkbal@users.noreply.github.com> Date: Sat, 14 Mar 2026 04:30:04 -0700 Subject: [PATCH 1/3] feat: support OR keyword in search tags --- assets/js/SearchTagInputHelper.ts | 15 ++++++++++++ components/pages/home/SimpleSearch.vue | 10 +++----- .../posts/navigation/search/SearchMenu.vue | 10 +++----- test/assets/search-tag-input-helper.test.ts | 24 +++++++++++++++++++ 4 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 assets/js/SearchTagInputHelper.ts create mode 100644 test/assets/search-tag-input-helper.test.ts diff --git a/assets/js/SearchTagInputHelper.ts b/assets/js/SearchTagInputHelper.ts new file mode 100644 index 00000000..d5c57bfd --- /dev/null +++ b/assets/js/SearchTagInputHelper.ts @@ -0,0 +1,15 @@ +export function normalizeSearchTagInput(rawValue: string) { + if (!rawValue) { + return '' + } + + let value = rawValue.trim() + + // Map typed OR separators to existing pipe semantics + value = value.replace(/\s+or\s+/gi, '|') + + // Replace spaces inside tags 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..4b700011 --- /dev/null +++ b/test/assets/search-tag-input-helper.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest' +import { normalizeSearchTagInput } from '../../assets/js/SearchTagInputHelper' + +describe('normalizeSearchTagInput', () => { + it('maps typed OR separators to pipes 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 repeated typed OR separators to existing pipe semantics', () => { + 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') + }) +}) From 0c909c16f313850973a34a055008c9b8a63cf30e Mon Sep 17 00:00:00 2001 From: AlejandroAkbal <37181533+AlejandroAkbal@users.noreply.github.com> Date: Sat, 14 Mar 2026 04:46:15 -0700 Subject: [PATCH 2/3] fix: collapse adjacent OR tokens and add edge-case tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace \s+or\s+ with a regex that collapses one-or-more adjacent 'or' tokens (e.g. 'cat OR OR dog' → 'cat|dog') per CodeRabbit feedback - Add test cases for repeated/adjacent separators --- assets/js/SearchTagInputHelper.ts | 4 ++-- test/assets/search-tag-input-helper.test.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/js/SearchTagInputHelper.ts b/assets/js/SearchTagInputHelper.ts index d5c57bfd..a04d1de2 100644 --- a/assets/js/SearchTagInputHelper.ts +++ b/assets/js/SearchTagInputHelper.ts @@ -5,8 +5,8 @@ export function normalizeSearchTagInput(rawValue: string) { let value = rawValue.trim() - // Map typed OR separators to existing pipe semantics - value = value.replace(/\s+or\s+/gi, '|') + // Map typed OR separators (including adjacent/repeated "or" tokens) to existing pipe semantics + value = value.replace(/\s*(?:\bor\b(?:\s+)?)+\s*/gi, '|') // Replace spaces inside tags with underscores value = value.replace(/\s/g, '_') diff --git a/test/assets/search-tag-input-helper.test.ts b/test/assets/search-tag-input-helper.test.ts index 4b700011..43a4939b 100644 --- a/test/assets/search-tag-input-helper.test.ts +++ b/test/assets/search-tag-input-helper.test.ts @@ -21,4 +21,10 @@ describe('normalizeSearchTagInput', () => { it('trims surrounding whitespace', () => { expect(normalizeSearchTagInput(' cat OR dog ')).toBe('cat|dog') }) + + it('collapses adjacent/repeated OR separators into a single pipe', () => { + 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') + }) }) From 5c22ff0a710b4ca4a33c99eb28f58865e1212cb6 Mon Sep 17 00:00:00 2001 From: AlejandroAkbal <37181533+AlejandroAkbal@users.noreply.github.com> Date: Sat, 14 Mar 2026 04:57:27 -0700 Subject: [PATCH 3/3] fix(search): use comma as OR-group delimiter instead of pipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using | as the OR-group separator conflicted with the existing use of | as the AND tag separator throughout the pipeline. Switch to , which is not used in booru tag names and survives the full App → API → Wrapper pipeline unmodified. --- assets/js/SearchTagInputHelper.ts | 6 ++--- test/assets/search-tag-input-helper.test.ts | 26 ++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/assets/js/SearchTagInputHelper.ts b/assets/js/SearchTagInputHelper.ts index a04d1de2..c042a3b9 100644 --- a/assets/js/SearchTagInputHelper.ts +++ b/assets/js/SearchTagInputHelper.ts @@ -5,10 +5,10 @@ export function normalizeSearchTagInput(rawValue: string) { let value = rawValue.trim() - // Map typed OR separators (including adjacent/repeated "or" tokens) to existing pipe semantics - value = value.replace(/\s*(?:\bor\b(?:\s+)?)+\s*/gi, '|') + // 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 spaces inside tags with underscores + // Replace remaining spaces (within individual tag names) with underscores value = value.replace(/\s/g, '_') return value diff --git a/test/assets/search-tag-input-helper.test.ts b/test/assets/search-tag-input-helper.test.ts index 43a4939b..d346ad57 100644 --- a/test/assets/search-tag-input-helper.test.ts +++ b/test/assets/search-tag-input-helper.test.ts @@ -2,29 +2,29 @@ import { describe, expect, it } from 'vitest' import { normalizeSearchTagInput } from '../../assets/js/SearchTagInputHelper' describe('normalizeSearchTagInput', () => { - it('maps typed OR separators to pipes 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 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 repeated typed OR separators to existing pipe semantics', () => { - expect(normalizeSearchTagInput('cat OR dog or fish')).toBe('cat|dog|fish') + 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') + expect(normalizeSearchTagInput('black hair OR blue eyes')).toBe('black_hair,blue_eyes') }) it('trims surrounding whitespace', () => { - expect(normalizeSearchTagInput(' cat OR dog ')).toBe('cat|dog') + expect(normalizeSearchTagInput(' cat OR dog ')).toBe('cat,dog') }) - it('collapses adjacent/repeated OR separators into a single pipe', () => { - 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') + 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') }) })