Skip to content

Commit b0ed808

Browse files
Fix Discover URL filter parsing, improve search sidebar (#5104)
* fix category parsing on discover * Make categories (loader, platform, etc) colored in discover, also add i18n * fix formatting * add localized strings --------- Co-authored-by: Creeperkatze <[email protected]> Co-authored-by: Prospector <[email protected]>
1 parent 1692245 commit b0ed808

File tree

4 files changed

+108
-23
lines changed

4 files changed

+108
-23
lines changed

packages/ui/src/components/search/SearchFilterOption.vue

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
></div>
2424
<button
2525
v-if="supportsNegativeFilter && !excluded"
26-
v-tooltip="excluded ? 'Remove exclusion' : 'Exclude'"
26+
v-tooltip="formatMessage(messages.excludeTooltip)"
2727
class="flex border-none cursor-pointer items-center justify-center gap-2 rounded-xl bg-transparent px-2 py-1 text-sm font-semibold text-secondary [@media(hover:hover)]:opacity-0 transition-all hover:bg-button-bg hover:text-red focus-visible:bg-button-bg focus-visible:text-red active:scale-[0.96]"
2828
@click="() => emit('toggleExclude', option)"
2929
>
@@ -35,6 +35,7 @@
3535
<script setup lang="ts">
3636
import { BanIcon, CheckIcon } from '@modrinth/assets'
3737
38+
import { defineMessages, useVIntl } from '../../composables/i18n'
3839
import type { FilterOption } from '../../utils/search'
3940
4041
withDefaults(
@@ -49,10 +50,19 @@ withDefaults(
4950
},
5051
)
5152
53+
const { formatMessage } = useVIntl()
54+
5255
const emit = defineEmits<{
5356
toggle: [option: FilterOption]
5457
toggleExclude: [option: FilterOption]
5558
}>()
59+
60+
const messages = defineMessages({
61+
excludeTooltip: {
62+
id: 'search.filter.option.exclusion.add.tooltip',
63+
defaultMessage: 'Exclude',
64+
},
65+
})
5666
</script>
5767
<style scoped lang="scss">
5868
.search-filter-option:hover,

packages/ui/src/components/search/SearchSidebarFilter.vue

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,15 @@
7171
v-model="query"
7272
class="!min-h-9 text-sm"
7373
type="text"
74-
:placeholder="`Search...`"
74+
:placeholder="formatMessage(messages.searchPlaceholder)"
7575
autocomplete="off"
7676
/>
77-
<Button v-if="query" class="r-btn" aria-label="Clear search" @click="() => (query = '')">
77+
<Button
78+
v-if="query"
79+
class="r-btn"
80+
:aria-label="formatMessage(messages.clearSearchAriaLabel)"
81+
@click="() => (query = '')"
82+
>
7883
<XIcon aria-hidden="true" />
7984
</Button>
8085
</div>
@@ -95,9 +100,17 @@
95100
@toggle-exclude="toggleNegativeFilter"
96101
>
97102
<slot name="option" :filter="filterType" :option="option">
98-
<div v-if="typeof option.icon === 'string'" class="h-4 w-4" v-html="option.icon" />
99-
<component :is="option.icon" v-else-if="option.icon" class="h-4 w-4" />
100-
<span class="truncate text-sm">{{ option.formatted_name ?? option.id }}</span>
103+
<span
104+
v-if="option.icon"
105+
class="inline-flex items-center justify-center shrink-0 h-4 w-4"
106+
:style="iconStyle(option)"
107+
>
108+
<div v-if="typeof option.icon === 'string'" class="h-4 w-4" v-html="option.icon" />
109+
<component :is="option.icon" v-else class="h-4 w-4" />
110+
</span>
111+
<span class="truncate text-sm" :style="iconStyle(option)">
112+
{{ option.formatted_name ?? option.id }}
113+
</span>
101114
</slot>
102115
</SearchFilterOption>
103116
<button
@@ -109,7 +122,9 @@
109122
class="h-4 w-4 transition-transform"
110123
:class="{ 'rotate-180': showMore }"
111124
/>
112-
<span class="truncate text-sm">{{ showMore ? 'Show fewer' : 'Show more' }}</span>
125+
<span class="truncate text-sm">
126+
{{ showMore ? formatMessage(messages.showFewer) : formatMessage(messages.showMore) }}
127+
</span>
113128
</button>
114129
</div>
115130
</ScrollablePanel>
@@ -234,6 +249,22 @@ const scrollable = computed(
234249
() => visibleOptions.value.length >= 10 && props.filterType.display === 'scrollable',
235250
)
236251
252+
function iconStyle(option: FilterOption) {
253+
// Match project page platform coloring (Forge/Fabric/Velocity/etc.) while leaving other
254+
// filter icons unchanged.
255+
if (
256+
props.filterType.id === 'mod_loader' ||
257+
props.filterType.id === 'modpack_loader' ||
258+
props.filterType.id === 'plugin_loader' ||
259+
props.filterType.id === 'plugin_platform' ||
260+
props.filterType.id === 'shader_loader'
261+
) {
262+
return { color: `var(--color-platform-${option.id})` }
263+
}
264+
265+
return undefined
266+
}
267+
237268
function groupEnabled(group: string) {
238269
return toggledGroups.value.includes(group)
239270
}
@@ -315,6 +346,22 @@ function clearFilters() {
315346
}
316347
317348
const messages = defineMessages({
349+
searchPlaceholder: {
350+
id: 'search.filter.option.search.placeholder',
351+
defaultMessage: 'Search...',
352+
},
353+
clearSearchAriaLabel: {
354+
id: 'search.filter.option.search.clear.aria_label',
355+
defaultMessage: 'Clear search',
356+
},
357+
showFewer: {
358+
id: 'search.filter.option.show_fewer',
359+
defaultMessage: 'Show fewer',
360+
},
361+
showMore: {
362+
id: 'search.filter.option.show_more',
363+
defaultMessage: 'Show more',
364+
},
318365
unlockFilterButton: {
319366
id: 'search.filter.locked.default.unlock',
320367
defaultMessage: 'Unlock filter',

packages/ui/src/locales/en-US/index.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,21 @@
920920
"search.filter.locked.default.unlock": {
921921
"defaultMessage": "Unlock filter"
922922
},
923+
"search.filter.option.exclusion.add.tooltip": {
924+
"defaultMessage": "Exclude"
925+
},
926+
"search.filter.option.search.clear.aria_label": {
927+
"defaultMessage": "Clear search"
928+
},
929+
"search.filter.option.search.placeholder": {
930+
"defaultMessage": "Search..."
931+
},
932+
"search.filter.option.show_fewer": {
933+
"defaultMessage": "Show fewer"
934+
},
935+
"search.filter.option.show_more": {
936+
"defaultMessage": "Show more"
937+
},
923938
"search.filter_type.environment": {
924939
"defaultMessage": "Environment"
925940
},

packages/ui/src/utils/search.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -558,32 +558,45 @@ export function useSearch(
558558
})
559559

560560
for (const key of Object.keys(route.query).filter((key) => !readParams.has(key))) {
561-
const type = filters.value.find((type) => type.query_param === key)
562-
if (type) {
563-
const values = getParamValuesAsArray(route.query[key])
561+
const types = filters.value.filter((type) => type.query_param === key)
562+
if (types.length === 0) {
563+
console.error(`Unknown filter type: ${key}`)
564+
continue
565+
}
566+
567+
const values = getParamValuesAsArray(route.query[key])
564568

565-
for (const value of values) {
566-
const negative = !value.includes(':') && value.includes('!=')
569+
for (const value of values) {
570+
const negative = !value.includes(':') && value.includes('!=')
571+
let matched = false
572+
573+
for (const type of types) {
567574
const option = type.options.find((option) => getOptionValue(option, negative) === value)
575+
if (!option) {
576+
continue
577+
}
578+
579+
currentFilters.value.push({
580+
type: type.id,
581+
option: option.id,
582+
negative: negative,
583+
})
584+
matched = true
585+
break
586+
}
568587

569-
if (!option && type.allows_custom_options) {
588+
if (!matched) {
589+
const customType = types.find((type) => type.allows_custom_options)
590+
if (customType) {
570591
currentFilters.value.push({
571-
type: type.id,
592+
type: customType.id,
572593
option: value.replace('!=', ':'),
573594
negative: negative,
574595
})
575-
} else if (option) {
576-
currentFilters.value.push({
577-
type: type.id,
578-
option: option.id,
579-
negative: negative,
580-
})
581596
} else {
582-
console.error(`Unknown filter option: ${value}`)
597+
console.error(`Unknown filter option for ${key}: ${value}`)
583598
}
584599
}
585-
} else {
586-
console.error(`Unknown filter type: ${key}`)
587600
}
588601
}
589602
}

0 commit comments

Comments
 (0)