You've already forked AstralRinth
forked from didirus/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>
|
></div>
|
||||||
<button
|
<button
|
||||||
v-if="supportsNegativeFilter && !excluded"
|
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]"
|
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)"
|
@click="() => emit('toggleExclude', option)"
|
||||||
>
|
>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { BanIcon, CheckIcon } from '@modrinth/assets'
|
import { BanIcon, CheckIcon } from '@modrinth/assets'
|
||||||
|
|
||||||
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import type { FilterOption } from '../../utils/search'
|
import type { FilterOption } from '../../utils/search'
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
@@ -49,10 +50,19 @@ withDefaults(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
toggle: [option: FilterOption]
|
toggle: [option: FilterOption]
|
||||||
toggleExclude: [option: FilterOption]
|
toggleExclude: [option: FilterOption]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
excludeTooltip: {
|
||||||
|
id: 'search.filter.option.exclusion.add.tooltip',
|
||||||
|
defaultMessage: 'Exclude',
|
||||||
|
},
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.search-filter-option:hover,
|
.search-filter-option:hover,
|
||||||
|
|||||||
@@ -71,10 +71,15 @@
|
|||||||
v-model="query"
|
v-model="query"
|
||||||
class="!min-h-9 text-sm"
|
class="!min-h-9 text-sm"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="`Search...`"
|
:placeholder="formatMessage(messages.searchPlaceholder)"
|
||||||
autocomplete="off"
|
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" />
|
<XIcon aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,9 +100,17 @@
|
|||||||
@toggle-exclude="toggleNegativeFilter"
|
@toggle-exclude="toggleNegativeFilter"
|
||||||
>
|
>
|
||||||
<slot name="option" :filter="filterType" :option="option">
|
<slot name="option" :filter="filterType" :option="option">
|
||||||
<div v-if="typeof option.icon === 'string'" class="h-4 w-4" v-html="option.icon" />
|
<span
|
||||||
<component :is="option.icon" v-else-if="option.icon" class="h-4 w-4" />
|
v-if="option.icon"
|
||||||
<span class="truncate text-sm">{{ option.formatted_name ?? option.id }}</span>
|
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>
|
</slot>
|
||||||
</SearchFilterOption>
|
</SearchFilterOption>
|
||||||
<button
|
<button
|
||||||
@@ -109,7 +122,9 @@
|
|||||||
class="h-4 w-4 transition-transform"
|
class="h-4 w-4 transition-transform"
|
||||||
:class="{ 'rotate-180': showMore }"
|
: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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ScrollablePanel>
|
</ScrollablePanel>
|
||||||
@@ -234,6 +249,22 @@ const scrollable = computed(
|
|||||||
() => visibleOptions.value.length >= 10 && props.filterType.display === 'scrollable',
|
() => 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) {
|
function groupEnabled(group: string) {
|
||||||
return toggledGroups.value.includes(group)
|
return toggledGroups.value.includes(group)
|
||||||
}
|
}
|
||||||
@@ -315,6 +346,22 @@ function clearFilters() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const messages = defineMessages({
|
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: {
|
unlockFilterButton: {
|
||||||
id: 'search.filter.locked.default.unlock',
|
id: 'search.filter.locked.default.unlock',
|
||||||
defaultMessage: 'Unlock filter',
|
defaultMessage: 'Unlock filter',
|
||||||
|
|||||||
@@ -920,6 +920,21 @@
|
|||||||
"search.filter.locked.default.unlock": {
|
"search.filter.locked.default.unlock": {
|
||||||
"defaultMessage": "Unlock filter"
|
"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": {
|
"search.filter_type.environment": {
|
||||||
"defaultMessage": "Environment"
|
"defaultMessage": "Environment"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -558,32 +558,45 @@ export function useSearch(
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const key of Object.keys(route.query).filter((key) => !readParams.has(key))) {
|
for (const key of Object.keys(route.query).filter((key) => !readParams.has(key))) {
|
||||||
const type = filters.value.find((type) => type.query_param === key)
|
const types = filters.value.filter((type) => type.query_param === key)
|
||||||
if (type) {
|
if (types.length === 0) {
|
||||||
const values = getParamValuesAsArray(route.query[key])
|
console.error(`Unknown filter type: ${key}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for (const value of values) {
|
const values = getParamValuesAsArray(route.query[key])
|
||||||
const negative = !value.includes(':') && value.includes('!=')
|
|
||||||
|
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)
|
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({
|
currentFilters.value.push({
|
||||||
type: type.id,
|
type: customType.id,
|
||||||
option: value.replace('!=', ':'),
|
option: value.replace('!=', ':'),
|
||||||
negative: negative,
|
negative: negative,
|
||||||
})
|
})
|
||||||
} else if (option) {
|
|
||||||
currentFilters.value.push({
|
|
||||||
type: type.id,
|
|
||||||
option: option.id,
|
|
||||||
negative: negative,
|
|
||||||
})
|
|
||||||
} else {
|
} 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