You've already forked AstralRinth
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 <178587183+Creeperkatze@users.noreply.github.com> Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
></div>
|
||||
<button
|
||||
v-if="supportsNegativeFilter && !excluded"
|
||||
v-tooltip="excluded ? 'Remove exclusion' : 'Exclude'"
|
||||
v-tooltip="formatMessage(messages.excludeTooltip)"
|
||||
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]"
|
||||
@click="() => emit('toggleExclude', option)"
|
||||
>
|
||||
@@ -35,6 +35,7 @@
|
||||
<script setup lang="ts">
|
||||
import { BanIcon, CheckIcon } from '@modrinth/assets'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import type { FilterOption } from '../../utils/search'
|
||||
|
||||
withDefaults(
|
||||
@@ -49,10 +50,19 @@ withDefaults(
|
||||
},
|
||||
)
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggle: [option: FilterOption]
|
||||
toggleExclude: [option: FilterOption]
|
||||
}>()
|
||||
|
||||
const messages = defineMessages({
|
||||
excludeTooltip: {
|
||||
id: 'search.filter.option.exclusion.add.tooltip',
|
||||
defaultMessage: 'Exclude',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.search-filter-option:hover,
|
||||
|
||||
@@ -71,10 +71,15 @@
|
||||
v-model="query"
|
||||
class="!min-h-9 text-sm"
|
||||
type="text"
|
||||
:placeholder="`Search...`"
|
||||
:placeholder="formatMessage(messages.searchPlaceholder)"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<Button v-if="query" class="r-btn" aria-label="Clear search" @click="() => (query = '')">
|
||||
<Button
|
||||
v-if="query"
|
||||
class="r-btn"
|
||||
:aria-label="formatMessage(messages.clearSearchAriaLabel)"
|
||||
@click="() => (query = '')"
|
||||
>
|
||||
<XIcon aria-hidden="true" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -95,9 +100,17 @@
|
||||
@toggle-exclude="toggleNegativeFilter"
|
||||
>
|
||||
<slot name="option" :filter="filterType" :option="option">
|
||||
<div v-if="typeof option.icon === 'string'" class="h-4 w-4" v-html="option.icon" />
|
||||
<component :is="option.icon" v-else-if="option.icon" class="h-4 w-4" />
|
||||
<span class="truncate text-sm">{{ option.formatted_name ?? option.id }}</span>
|
||||
<span
|
||||
v-if="option.icon"
|
||||
class="inline-flex items-center justify-center shrink-0 h-4 w-4"
|
||||
:style="iconStyle(option)"
|
||||
>
|
||||
<div v-if="typeof option.icon === 'string'" class="h-4 w-4" v-html="option.icon" />
|
||||
<component :is="option.icon" v-else class="h-4 w-4" />
|
||||
</span>
|
||||
<span class="truncate text-sm" :style="iconStyle(option)">
|
||||
{{ option.formatted_name ?? option.id }}
|
||||
</span>
|
||||
</slot>
|
||||
</SearchFilterOption>
|
||||
<button
|
||||
@@ -109,7 +122,9 @@
|
||||
class="h-4 w-4 transition-transform"
|
||||
:class="{ 'rotate-180': showMore }"
|
||||
/>
|
||||
<span class="truncate text-sm">{{ showMore ? 'Show fewer' : 'Show more' }}</span>
|
||||
<span class="truncate text-sm">
|
||||
{{ showMore ? formatMessage(messages.showFewer) : formatMessage(messages.showMore) }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</ScrollablePanel>
|
||||
@@ -234,6 +249,22 @@ const scrollable = computed(
|
||||
() => visibleOptions.value.length >= 10 && props.filterType.display === 'scrollable',
|
||||
)
|
||||
|
||||
function iconStyle(option: FilterOption) {
|
||||
// Match project page platform coloring (Forge/Fabric/Velocity/etc.) while leaving other
|
||||
// filter icons unchanged.
|
||||
if (
|
||||
props.filterType.id === 'mod_loader' ||
|
||||
props.filterType.id === 'modpack_loader' ||
|
||||
props.filterType.id === 'plugin_loader' ||
|
||||
props.filterType.id === 'plugin_platform' ||
|
||||
props.filterType.id === 'shader_loader'
|
||||
) {
|
||||
return { color: `var(--color-platform-${option.id})` }
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function groupEnabled(group: string) {
|
||||
return toggledGroups.value.includes(group)
|
||||
}
|
||||
@@ -315,6 +346,22 @@ function clearFilters() {
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
searchPlaceholder: {
|
||||
id: 'search.filter.option.search.placeholder',
|
||||
defaultMessage: 'Search...',
|
||||
},
|
||||
clearSearchAriaLabel: {
|
||||
id: 'search.filter.option.search.clear.aria_label',
|
||||
defaultMessage: 'Clear search',
|
||||
},
|
||||
showFewer: {
|
||||
id: 'search.filter.option.show_fewer',
|
||||
defaultMessage: 'Show fewer',
|
||||
},
|
||||
showMore: {
|
||||
id: 'search.filter.option.show_more',
|
||||
defaultMessage: 'Show more',
|
||||
},
|
||||
unlockFilterButton: {
|
||||
id: 'search.filter.locked.default.unlock',
|
||||
defaultMessage: 'Unlock filter',
|
||||
|
||||
@@ -920,6 +920,21 @@
|
||||
"search.filter.locked.default.unlock": {
|
||||
"defaultMessage": "Unlock filter"
|
||||
},
|
||||
"search.filter.option.exclusion.add.tooltip": {
|
||||
"defaultMessage": "Exclude"
|
||||
},
|
||||
"search.filter.option.search.clear.aria_label": {
|
||||
"defaultMessage": "Clear search"
|
||||
},
|
||||
"search.filter.option.search.placeholder": {
|
||||
"defaultMessage": "Search..."
|
||||
},
|
||||
"search.filter.option.show_fewer": {
|
||||
"defaultMessage": "Show fewer"
|
||||
},
|
||||
"search.filter.option.show_more": {
|
||||
"defaultMessage": "Show more"
|
||||
},
|
||||
"search.filter_type.environment": {
|
||||
"defaultMessage": "Environment"
|
||||
},
|
||||
|
||||
@@ -558,32 +558,45 @@ export function useSearch(
|
||||
})
|
||||
|
||||
for (const key of Object.keys(route.query).filter((key) => !readParams.has(key))) {
|
||||
const type = filters.value.find((type) => type.query_param === key)
|
||||
if (type) {
|
||||
const values = getParamValuesAsArray(route.query[key])
|
||||
const types = filters.value.filter((type) => type.query_param === key)
|
||||
if (types.length === 0) {
|
||||
console.error(`Unknown filter type: ${key}`)
|
||||
continue
|
||||
}
|
||||
|
||||
for (const value of values) {
|
||||
const negative = !value.includes(':') && value.includes('!=')
|
||||
const values = getParamValuesAsArray(route.query[key])
|
||||
|
||||
for (const value of values) {
|
||||
const negative = !value.includes(':') && value.includes('!=')
|
||||
let matched = false
|
||||
|
||||
for (const type of types) {
|
||||
const option = type.options.find((option) => getOptionValue(option, negative) === value)
|
||||
if (!option) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!option && type.allows_custom_options) {
|
||||
currentFilters.value.push({
|
||||
type: type.id,
|
||||
option: option.id,
|
||||
negative: negative,
|
||||
})
|
||||
matched = true
|
||||
break
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
const customType = types.find((type) => type.allows_custom_options)
|
||||
if (customType) {
|
||||
currentFilters.value.push({
|
||||
type: type.id,
|
||||
type: customType.id,
|
||||
option: value.replace('!=', ':'),
|
||||
negative: negative,
|
||||
})
|
||||
} else if (option) {
|
||||
currentFilters.value.push({
|
||||
type: type.id,
|
||||
option: option.id,
|
||||
negative: negative,
|
||||
})
|
||||
} else {
|
||||
console.error(`Unknown filter option: ${value}`)
|
||||
console.error(`Unknown filter option for ${key}: ${value}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error(`Unknown filter type: ${key}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user